• 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 String zoneTab;
226         private Integer indexOffsetOverride;
227         private Integer dataOffsetOverride;
228         private Integer zoneTabOffsetOverride;
229 
TzDataBuilder()230         public TzDataBuilder() {}
231 
232         /** Sets the header. A valid header is in the form "tzdata2016g". */
setHeaderMagic(String headerMagic)233         public TzDataBuilder setHeaderMagic(String headerMagic) {
234             this.headerMagic = headerMagic;
235             return this;
236         }
237 
setIndexOffsetOverride(int indexOffset)238         public TzDataBuilder setIndexOffsetOverride(int indexOffset) {
239             this.indexOffsetOverride = indexOffset;
240             return this;
241         }
242 
setDataOffsetOverride(int dataOffset)243         public TzDataBuilder setDataOffsetOverride(int dataOffset) {
244             this.dataOffsetOverride = dataOffset;
245             return this;
246         }
247 
setZoneTabOffsetOverride(int zoneTabOffset)248         public TzDataBuilder setZoneTabOffsetOverride(int zoneTabOffset) {
249             this.zoneTabOffsetOverride = zoneTabOffset;
250             return this;
251         }
252 
253         /**
254          * Adds data for a new zone. These must be added in ID string order to generate
255          * a valid file.
256          */
addZicData(String id, byte[] data)257         public TzDataBuilder addZicData(String id, byte[] data) {
258             zicData.add(new ZicDatum(id, data));
259             return this;
260         }
261 
setZoneTab(String zoneTab)262         public TzDataBuilder setZoneTab(String zoneTab) {
263             this.zoneTab = zoneTab;
264             return this;
265         }
266 
initializeToValid()267         public TzDataBuilder initializeToValid() {
268             setHeaderMagic("tzdata9999a");
269             addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
270             setZoneTab("ZoneTab data");
271             return this;
272         }
273 
clearZicData()274         public TzDataBuilder clearZicData() {
275             zicData.clear();
276             return this;
277         }
278 
build()279         public byte[] build() {
280             ByteArrayOutputStream baos = new ByteArrayOutputStream();
281 
282             byte[] headerMagicBytes = headerMagic.getBytes(StandardCharsets.US_ASCII);
283             baos.write(headerMagicBytes, 0, headerMagicBytes.length);
284             baos.write(0);
285 
286             // Write out the offsets for later manipulation.
287             int indexOffsetOffset = baos.size();
288             writeInt(baos, 0);
289             int dataOffsetOffset = baos.size();
290             writeInt(baos, 0);
291             int zoneTabOffsetOffset = baos.size();
292             writeInt(baos, 0);
293 
294             // Construct the data section in advance, so we know the offsets.
295             ByteArrayOutputStream dataBytes = new ByteArrayOutputStream();
296             Map<String, Integer> offsets = new HashMap<>();
297             for (ZicDatum datum : zicData) {
298                 int offset = dataBytes.size();
299                 offsets.put(datum.id, offset);
300                 writeByteArray(dataBytes, datum.data);
301             }
302 
303             int indexOffset = baos.size();
304 
305             // Write the index section.
306             for (ZicDatum zicDatum : zicData) {
307                 // Write the ID.
308                 String id = zicDatum.id;
309                 byte[] idBytes = id.getBytes(StandardCharsets.US_ASCII);
310                 byte[] paddedIdBytes = new byte[40];
311                 System.arraycopy(idBytes, 0, paddedIdBytes, 0, idBytes.length);
312                 writeByteArray(baos, paddedIdBytes);
313                 // Write offset of zic data in the data section.
314                 Integer offset = offsets.get(id);
315                 writeInt(baos, offset);
316                 // Write the length of the zic data.
317                 writeInt(baos, zicDatum.data.length);
318                 // Write a filler value (not used)
319                 writeInt(baos, 0);
320             }
321 
322             // Write the data section.
323             int dataOffset = baos.size();
324             writeByteArray(baos, dataBytes.toByteArray());
325 
326             // Write the zoneTab section.
327             int zoneTabOffset = baos.size();
328             byte[] zoneTabBytes = zoneTab.getBytes(StandardCharsets.US_ASCII);
329             writeByteArray(baos, zoneTabBytes);
330 
331             byte[] bytes = baos.toByteArray();
332             setInt(bytes, indexOffsetOffset,
333                     indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
334             setInt(bytes, dataOffsetOffset,
335                     dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
336             setInt(bytes, zoneTabOffsetOffset,
337                     zoneTabOffsetOverride != null ? zoneTabOffsetOverride : zoneTabOffset);
338             return bytes;
339         }
340 
341         private static class ZicDatum {
342             public final String id;
343             public final byte[] data;
344 
ZicDatum(String id, byte[] data)345             ZicDatum(String id, byte[] data) {
346                 this.id = id;
347                 this.data = data;
348             }
349         }
350     }
351 
writeByteArray(ByteArrayOutputStream baos, byte[] array)352     static void writeByteArray(ByteArrayOutputStream baos, byte[] array) {
353         baos.write(array, 0, array.length);
354     }
355 
writeByteList(ByteArrayOutputStream baos, List<Byte> list)356     static void writeByteList(ByteArrayOutputStream baos, List<Byte> list) {
357         for (byte value : list) {
358             baos.write(value);
359         }
360     }
361 
writeByte(ByteArrayOutputStream baos, int value)362     static void writeByte(ByteArrayOutputStream baos, int value) {
363         baos.write(value);
364     }
365 
writeLongArray(ByteArrayOutputStream baos, long[] array)366     static void writeLongArray(ByteArrayOutputStream baos, long[] array) {
367         for (long value : array) {
368             writeLong(baos, value);
369         }
370     }
371 
writeIntList(ByteArrayOutputStream baos, List<Integer> list)372     static void writeIntList(ByteArrayOutputStream baos, List<Integer> list) {
373         for (int value : list) {
374             writeInt(baos, value);
375         }
376     }
377 
writeInt(ByteArrayOutputStream os, int value)378     static void writeInt(ByteArrayOutputStream os, int value) {
379         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
380         writeByteArray(os, bytes);
381     }
382 
writeLong(ByteArrayOutputStream os, long value)383     static void writeLong(ByteArrayOutputStream os, long value) {
384         byte[] bytes = ByteBuffer.allocate(8).putLong(value).array();
385         writeByteArray(os, bytes);
386     }
387 
setInt(byte[] bytes, int offset, int value)388     static void setInt(byte[] bytes, int offset, int value) {
389         bytes[offset] = (byte) (value >>> 24);
390         bytes[offset + 1] = (byte) (value >>> 16);
391         bytes[offset + 2] = (byte) (value >>> 8);
392         bytes[offset + 3] = (byte) value;
393     }
394 }
395