• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package libcore.timezone.testing;
17 
18 import java.io.ByteArrayOutputStream;
19 import java.nio.ByteBuffer;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 
26 /**
27  * Helps with creating valid and invalid test data.
28  */
29 public class ZoneInfoTestHelper {
30 
ZoneInfoTestHelper()31     private ZoneInfoTestHelper() {}
32 
33     /**
34      * Constructs valid and invalid zic data for tests.
35      */
36     public static class ZicDataBuilder {
37 
38         private int magic = 0x545a6966; // Default, valid magic.
39         private long[] transitionTimes64Bit; // Time of each transition, one per transition.
40         private byte[] transitionTypes64Bit; // Type of each transition, one per transition.
41         private int[] isDsts; // Whether a type uses DST, one per type.
42         private int[] offsetsSeconds; // The UTC offset, one per type.
43 
ZicDataBuilder()44         public ZicDataBuilder() {}
45 
setMagic(int magic)46         public ZicDataBuilder setMagic(int magic) {
47             this.magic = magic;
48             return this;
49         }
50 
51         /**
52          * See {@link #setTransitions(long[][])} and {@link #setTypes(int[][])}.
53          */
setTransitionsAndTypes( long[][] transitionPairs, int[][] typePairs)54         public ZicDataBuilder setTransitionsAndTypes(
55                 long[][] transitionPairs, int[][] typePairs) {
56             setTransitions(transitionPairs);
57             setTypes(typePairs);
58             return this;
59         }
60         /**
61          * Sets transition information using an array of pairs of longs. e.g.
62          *
63          * new long[][] {
64          *   { transitionTimeSeconds1, typeIndex1 },
65          *   { transitionTimeSeconds2, typeIndex1 },
66          * }
67          */
setTransitions(long[][] transitionPairs)68         public ZicDataBuilder setTransitions(long[][] transitionPairs) {
69             long[] transitions = new long[transitionPairs.length];
70             byte[] types = new byte[transitionPairs.length];
71             for (int i = 0; i < transitionPairs.length; i++) {
72                 transitions[i] = transitionPairs[i][0];
73                 types[i] = (byte) transitionPairs[i][1];
74             }
75             this.transitionTimes64Bit = transitions;
76             this.transitionTypes64Bit = types;
77             return this;
78         }
79 
80         /**
81          * Sets transition information using an array of pairs of ints. e.g.
82          *
83          * new int[][] {
84          *   { offsetSeconds1, typeIsDst1 },
85          *   { offsetSeconds2, typeIsDst2 },
86          * }
87          */
setTypes(int[][] typePairs)88         public ZicDataBuilder setTypes(int[][] typePairs) {
89             int[] isDsts = new int[typePairs.length];
90             int[] offsetSeconds = new int[typePairs.length];
91             for (int i = 0; i < typePairs.length; i++) {
92                 offsetSeconds[i] = typePairs[i][0];
93                 isDsts[i] = typePairs[i][1];
94             }
95             this.isDsts = isDsts;
96             this.offsetsSeconds = offsetSeconds;
97             return this;
98         }
99 
100         /** Initializes to a minimum viable ZoneInfo data. */
initializeToValid()101         public ZicDataBuilder initializeToValid() {
102             setTransitions(new long[0][0]);
103             setTypes(new int[][] {
104                     { 3600, 0}
105             });
106             return this;
107         }
108 
build()109         public byte[] build() {
110             ByteArrayOutputStream baos = new ByteArrayOutputStream();
111 
112             // Compute the 32-bit transitions / types.
113             List<Integer> transitionTimes32Bit = new ArrayList<>();
114             List<Byte> transitionTypes32Bit = new ArrayList<>();
115             if (transitionTimes64Bit.length > 0) {
116                 // Skip transitions < Integer.MIN_VALUE.
117                 int i = 0;
118                 while (i < transitionTimes64Bit.length
119                         && transitionTimes64Bit[i] < Integer.MIN_VALUE) {
120                     i++;
121                 }
122                 // If we skipped any, add a transition at Integer.MIN_VALUE like zic does.
123                 if (i > 0) {
124                     transitionTimes32Bit.add(Integer.MIN_VALUE);
125                     transitionTypes32Bit.add(transitionTypes64Bit[i - 1]);
126                 }
127                 // Copy remaining transitions / types that fit in the 32-bit range.
128                 while (i < transitionTimes64Bit.length
129                         && transitionTimes64Bit[i] <= Integer.MAX_VALUE) {
130                     transitionTimes32Bit.add((int) transitionTimes64Bit[i]);
131                     transitionTypes32Bit.add(transitionTypes64Bit[i]);
132                     i++;
133                 }
134             }
135 
136             int tzh_timecnt_32 = transitionTimes32Bit.size();
137             int tzh_timecnt_64 = transitionTimes64Bit.length;
138             int tzh_typecnt = offsetsSeconds.length;
139             int tzh_charcnt = 0;
140             int tzh_leapcnt = 0;
141             int tzh_ttisstdcnt = 0;
142             int tzh_ttisgmtcnt = 0;
143 
144             // Write 32-bit data.
145             writeHeader(baos, magic, tzh_timecnt_32, tzh_typecnt, tzh_charcnt, tzh_leapcnt,
146                     tzh_ttisstdcnt, tzh_ttisgmtcnt);
147             writeIntList(baos, transitionTimes32Bit);
148             writeByteList(baos, transitionTypes32Bit);
149             writeTypes(baos, offsetsSeconds, isDsts);
150             writeTrailingUnusued32BitData(baos, tzh_charcnt, tzh_leapcnt, tzh_ttisstdcnt,
151                     tzh_ttisgmtcnt);
152 
153             // 64-bit data.
154             writeHeader(baos, magic, tzh_timecnt_64, tzh_typecnt, tzh_charcnt, tzh_leapcnt,
155                     tzh_ttisstdcnt, tzh_ttisgmtcnt);
156             writeLongArray(baos, transitionTimes64Bit);
157             writeByteArray(baos, transitionTypes64Bit);
158             writeTypes(baos, offsetsSeconds, isDsts);
159 
160             return baos.toByteArray();
161         }
162 
writeTypes( ByteArrayOutputStream baos, int[] offsetsSeconds, int[] isDsts)163         private static void writeTypes(
164                 ByteArrayOutputStream baos, int[] offsetsSeconds, int[] isDsts) {
165             // Offset / DST
166             for (int i = 0; i < offsetsSeconds.length; i++) {
167                 writeInt(baos, offsetsSeconds[i]);
168                 writeByte(baos, isDsts[i]);
169                 // Unused data on Android (abbreviation list index).
170                 writeByte(baos, 0);
171             }
172         }
173 
174         /**
175          * Writes the data after types information Android doesn't currently use but is needed so
176          * that the start of the 64-bit data can be found.
177          */
writeTrailingUnusued32BitData( ByteArrayOutputStream baos, int tzh_charcnt, int tzh_leapcnt, int tzh_ttisstdcnt, int tzh_ttisgmtcnt)178         private static void writeTrailingUnusued32BitData(
179                 ByteArrayOutputStream baos, int tzh_charcnt, int tzh_leapcnt,
180                 int tzh_ttisstdcnt, int tzh_ttisgmtcnt) {
181 
182             // Time zone abbreviations text.
183             writeByteArray(baos, new byte[tzh_charcnt]);
184             // tzh_leapcnt repetitions of leap second transition time + correction.
185             int leapInfoSize = 4 + 4;
186             writeByteArray(baos, new byte[tzh_leapcnt * leapInfoSize]);
187             // tzh_ttisstdcnt bytes
188             writeByteArray(baos, new byte[tzh_ttisstdcnt]);
189             // tzh_ttisgmtcnt bytes
190             writeByteArray(baos, new byte[tzh_ttisgmtcnt]);
191         }
192 
writeHeader(ByteArrayOutputStream baos, int magic, int tzh_timecnt, int tzh_typecnt, int tzh_charcnt, int tzh_leapcnt, int tzh_ttisstdcnt, int tzh_ttisgmtcnt)193         private static void writeHeader(ByteArrayOutputStream baos, int magic, int tzh_timecnt,
194                 int tzh_typecnt, int tzh_charcnt, int tzh_leapcnt, int tzh_ttisstdcnt,
195                 int tzh_ttisgmtcnt) {
196             // Magic number.
197             writeInt(baos, magic);
198             // tzh_version
199             writeByte(baos, '2');
200 
201             // Write out the unused part of the header.
202             for (int i = 0; i < 15; ++i) {
203                 baos.write(i);
204             }
205             // Write out the known header fields.
206             writeInt(baos, tzh_ttisgmtcnt);
207             writeInt(baos, tzh_ttisstdcnt);
208             writeInt(baos, tzh_leapcnt);
209             writeInt(baos, tzh_timecnt);
210             writeInt(baos, tzh_typecnt);
211             writeInt(baos, tzh_charcnt);
212         }
213     }
214 
215     /**
216      * Constructs valid and invalid tzdata files for tests. See also ZoneCompactor class in
217      * system/timezone/zone_compactor which is the real thing.
218      */
219     public static class TzDataBuilder {
220 
221         private String headerMagic;
222         // A list is used in preference to a Map to allow simulation of badly ordered / duplicate
223         // IDs.
224         private List<ZicDatum> zicData = new ArrayList<>();
225         private Integer indexOffsetOverride;
226         private Integer dataOffsetOverride;
227         private Integer finalOffsetOverride;
228 
TzDataBuilder()229         public TzDataBuilder() {}
230 
231         /** Sets the header. A valid header is in the form "tzdata2016g". */
setHeaderMagic(String headerMagic)232         public TzDataBuilder setHeaderMagic(String headerMagic) {
233             this.headerMagic = headerMagic;
234             return this;
235         }
236 
setIndexOffsetOverride(int indexOffset)237         public TzDataBuilder setIndexOffsetOverride(int indexOffset) {
238             this.indexOffsetOverride = indexOffset;
239             return this;
240         }
241 
setDataOffsetOverride(int dataOffset)242         public TzDataBuilder setDataOffsetOverride(int dataOffset) {
243             this.dataOffsetOverride = dataOffset;
244             return this;
245         }
246 
setFinalOffsetOverride(int finalOffset)247         public TzDataBuilder setFinalOffsetOverride(int finalOffset) {
248             this.finalOffsetOverride = finalOffset;
249             return this;
250         }
251 
252         /**
253          * Adds data for a new zone. These must be added in ID string order to generate
254          * a valid file.
255          */
addZicData(String id, byte[] data)256         public TzDataBuilder addZicData(String id, byte[] data) {
257             zicData.add(new ZicDatum(id, data));
258             return this;
259         }
260 
initializeToValid()261         public TzDataBuilder initializeToValid() {
262             setHeaderMagic("tzdata9999a");
263             addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
264             return this;
265         }
266 
clearZicData()267         public TzDataBuilder clearZicData() {
268             zicData.clear();
269             return this;
270         }
271 
build()272         public byte[] build() {
273             ByteArrayOutputStream baos = new ByteArrayOutputStream();
274 
275             byte[] headerMagicBytes = headerMagic.getBytes(StandardCharsets.US_ASCII);
276             baos.write(headerMagicBytes, 0, headerMagicBytes.length);
277             baos.write(0);
278 
279             // Write out the offsets for later manipulation.
280             int indexOffsetOffset = baos.size();
281             writeInt(baos, 0);
282             int dataOffsetOffset = baos.size();
283             writeInt(baos, 0);
284             int finalOffsetOffset = baos.size();
285             writeInt(baos, 0);
286 
287             // Construct the data section in advance, so we know the offsets.
288             ByteArrayOutputStream dataBytes = new ByteArrayOutputStream();
289             Map<String, Integer> offsets = new HashMap<>();
290             for (ZicDatum datum : zicData) {
291                 int offset = dataBytes.size();
292                 offsets.put(datum.id, offset);
293                 writeByteArray(dataBytes, datum.data);
294             }
295 
296             int indexOffset = baos.size();
297 
298             // Write the index section.
299             for (ZicDatum zicDatum : zicData) {
300                 // Write the ID.
301                 String id = zicDatum.id;
302                 byte[] idBytes = id.getBytes(StandardCharsets.US_ASCII);
303                 byte[] paddedIdBytes = new byte[40];
304                 System.arraycopy(idBytes, 0, paddedIdBytes, 0, idBytes.length);
305                 writeByteArray(baos, paddedIdBytes);
306                 // Write offset of zic data in the data section.
307                 Integer offset = offsets.get(id);
308                 writeInt(baos, offset);
309                 // Write the length of the zic data.
310                 writeInt(baos, zicDatum.data.length);
311                 // Write a filler value (not used)
312                 writeInt(baos, 0);
313             }
314 
315             // Write the data section.
316             int dataOffset = baos.size();
317             writeByteArray(baos, dataBytes.toByteArray());
318 
319             int finalOffset = baos.size();
320 
321             byte[] bytes = baos.toByteArray();
322             setInt(bytes, indexOffsetOffset,
323                     indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
324             setInt(bytes, dataOffsetOffset,
325                     dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
326             setInt(bytes, finalOffsetOffset,
327                     finalOffsetOverride != null ? finalOffsetOverride : finalOffset);
328             return bytes;
329         }
330 
331         private static class ZicDatum {
332             public final String id;
333             public final byte[] data;
334 
ZicDatum(String id, byte[] data)335             ZicDatum(String id, byte[] data) {
336                 this.id = id;
337                 this.data = data;
338             }
339         }
340     }
341 
writeByteArray(ByteArrayOutputStream baos, byte[] array)342     static void writeByteArray(ByteArrayOutputStream baos, byte[] array) {
343         baos.write(array, 0, array.length);
344     }
345 
writeByteList(ByteArrayOutputStream baos, List<Byte> list)346     static void writeByteList(ByteArrayOutputStream baos, List<Byte> list) {
347         for (byte value : list) {
348             baos.write(value);
349         }
350     }
351 
writeByte(ByteArrayOutputStream baos, int value)352     static void writeByte(ByteArrayOutputStream baos, int value) {
353         baos.write(value);
354     }
355 
writeLongArray(ByteArrayOutputStream baos, long[] array)356     static void writeLongArray(ByteArrayOutputStream baos, long[] array) {
357         for (long value : array) {
358             writeLong(baos, value);
359         }
360     }
361 
writeIntList(ByteArrayOutputStream baos, List<Integer> list)362     static void writeIntList(ByteArrayOutputStream baos, List<Integer> list) {
363         for (int value : list) {
364             writeInt(baos, value);
365         }
366     }
367 
writeInt(ByteArrayOutputStream os, int value)368     static void writeInt(ByteArrayOutputStream os, int value) {
369         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
370         writeByteArray(os, bytes);
371     }
372 
writeLong(ByteArrayOutputStream os, long value)373     static void writeLong(ByteArrayOutputStream os, long value) {
374         byte[] bytes = ByteBuffer.allocate(8).putLong(value).array();
375         writeByteArray(os, bytes);
376     }
377 
setInt(byte[] bytes, int offset, int value)378     static void setInt(byte[] bytes, int offset, int value) {
379         bytes[offset] = (byte) (value >>> 24);
380         bytes[offset + 1] = (byte) (value >>> 16);
381         bytes[offset + 2] = (byte) (value >>> 8);
382         bytes[offset + 3] = (byte) value;
383     }
384 }
385