• 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. Currently it only creates minimal TZif
35      * version 1 data necessary to be compatible with Android's ZoneInfo class.
36      */
37     public static class ZicDataBuilder {
38 
39         private int magic = 0x545a6966; // Default, valid magic.
40         private Integer transitionCountOverride; // Used to override the correct transition count.
41         private int[] transitionTimes; // Time of each transition, one per transition.
42         private byte[] transitionTypes; // Type of each transition, one per transition.
43         private Integer typesCountOverride; // Used to override the correct type count.
44         private int[] isDsts; // Whether a type uses DST, one per type.
45         private int[] offsetsSeconds; // The UTC offset, one per type.
46 
ZicDataBuilder()47         public ZicDataBuilder() {}
48 
setMagic(int magic)49         public ZicDataBuilder setMagic(int magic) {
50             this.magic = magic;
51             return this;
52         }
53 
setTypeCountOverride(int typesCountOverride)54         public ZicDataBuilder setTypeCountOverride(int typesCountOverride) {
55             this.typesCountOverride = typesCountOverride;
56             return this;
57         }
58 
setTransitionCountOverride(int transitionCountOverride)59         public ZicDataBuilder setTransitionCountOverride(int transitionCountOverride) {
60             this.transitionCountOverride = transitionCountOverride;
61             return this;
62         }
63 
64         /**
65          * See {@link #setTransitions(int[][])} and {@link #setTypes(int[][])}.
66          */
setTransitionsAndTypes( int[][] transitionPairs, int[][] typePairs)67         public ZicDataBuilder setTransitionsAndTypes(
68                 int[][] transitionPairs, int[][] typePairs) {
69             setTransitions(transitionPairs);
70             setTypes(typePairs);
71             return this;
72         }
73         /**
74          * Sets transition information using an array of pairs of ints. e.g.
75          *
76          * new int[][] {
77          *   { transitionTimeSeconds1, typeIndex1 },
78          *   { transitionTimeSeconds2, typeIndex1 },
79          * }
80          */
setTransitions(int[][] transitionPairs)81         public ZicDataBuilder setTransitions(int[][] transitionPairs) {
82             int[] transitions = new int[transitionPairs.length];
83             byte[] types = new byte[transitionPairs.length];
84             for (int i = 0; i < transitionPairs.length; i++) {
85                 transitions[i] = transitionPairs[i][0];
86                 types[i] = (byte) transitionPairs[i][1];
87             }
88             this.transitionTimes = transitions;
89             this.transitionTypes = types;
90             return this;
91         }
92 
93         /**
94          * Sets transition information using an array of pairs of ints. e.g.
95          *
96          * new int[][] {
97          *   { typeIsDst1, offsetSeconds1 },
98          *   { typeIsDst2, offsetSeconds2 },
99          * }
100          */
setTypes(int[][] typePairs)101         public ZicDataBuilder setTypes(int[][] typePairs) {
102             int[] isDsts = new int[typePairs.length];
103             int[] offsetSeconds = new int[typePairs.length];
104             for (int i = 0; i < typePairs.length; i++) {
105                 offsetSeconds[i] = typePairs[i][0];
106                 isDsts[i] = typePairs[i][1];
107             }
108             this.isDsts = isDsts;
109             this.offsetsSeconds = offsetSeconds;
110             return this;
111         }
112 
113         /** Initializes to a minimum viable ZoneInfo data. */
initializeToValid()114         public ZicDataBuilder initializeToValid() {
115             setTransitions(new int[0][0]);
116             setTypes(new int[][] {
117                     { 3600, 0}
118             });
119             return this;
120         }
121 
build()122         public byte[] build() {
123             ByteArrayOutputStream baos = new ByteArrayOutputStream();
124 
125             // Magic number.
126             writeInt(baos, magic);
127 
128             // Some useless stuff in the header.
129             for (int i = 0; i < 28; ++i) {
130                 baos.write(i);
131             }
132 
133             // Transition time count
134             int transitionsCount = transitionCountOverride != null
135                     ? transitionCountOverride : transitionTimes.length;
136             writeInt(baos, transitionsCount);
137 
138             // Transition type count.
139             int typesCount = typesCountOverride != null
140                     ? typesCountOverride : offsetsSeconds.length;
141             writeInt(baos, typesCount);
142             // Useless stuff.
143             writeInt(baos, 0xdeadbeef);
144 
145             // Transition time array, as ints.
146             writeIntArray(baos, transitionTimes);
147 
148             // Transition type array.
149             writeByteArray(baos, transitionTypes);
150 
151             // Offset / DST
152             for (int i = 0; i < offsetsSeconds.length; i++) {
153                 writeInt(baos, offsetsSeconds[i]);
154                 writeByte(baos, isDsts[i]);
155                 // Useless stuff.
156                 writeByte(baos, i);
157             }
158             return baos.toByteArray();
159         }
160     }
161 
162     /**
163      * Constructs valid and invalid tzdata files for tests. See also ZoneCompactor class in
164      * system/timezone/zone_compactor which is the real thing.
165      */
166     public static class TzDataBuilder {
167 
168         private String headerMagic;
169         // A list is used in preference to a Map to allow simulation of badly ordered / duplicate
170         // IDs.
171         private List<ZicDatum> zicData = new ArrayList<>();
172         private String zoneTab;
173         private Integer indexOffsetOverride;
174         private Integer dataOffsetOverride;
175         private Integer zoneTabOffsetOverride;
176 
TzDataBuilder()177         public TzDataBuilder() {}
178 
179         /** Sets the header. A valid header is in the form "tzdata2016g". */
setHeaderMagic(String headerMagic)180         public TzDataBuilder setHeaderMagic(String headerMagic) {
181             this.headerMagic = headerMagic;
182             return this;
183         }
184 
setIndexOffsetOverride(int indexOffset)185         public TzDataBuilder setIndexOffsetOverride(int indexOffset) {
186             this.indexOffsetOverride = indexOffset;
187             return this;
188         }
189 
setDataOffsetOverride(int dataOffset)190         public TzDataBuilder setDataOffsetOverride(int dataOffset) {
191             this.dataOffsetOverride = dataOffset;
192             return this;
193         }
194 
setZoneTabOffsetOverride(int zoneTabOffset)195         public TzDataBuilder setZoneTabOffsetOverride(int zoneTabOffset) {
196             this.zoneTabOffsetOverride = zoneTabOffset;
197             return this;
198         }
199 
200         /**
201          * Adds data for a new zone. These must be added in ID string order to generate
202          * a valid file.
203          */
addZicData(String id, byte[] data)204         public TzDataBuilder addZicData(String id, byte[] data) {
205             zicData.add(new ZicDatum(id, data));
206             return this;
207         }
208 
setZoneTab(String zoneTab)209         public TzDataBuilder setZoneTab(String zoneTab) {
210             this.zoneTab = zoneTab;
211             return this;
212         }
213 
initializeToValid()214         public TzDataBuilder initializeToValid() {
215             setHeaderMagic("tzdata9999a");
216             addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
217             setZoneTab("ZoneTab data");
218             return this;
219         }
220 
clearZicData()221         public TzDataBuilder clearZicData() {
222             zicData.clear();
223             return this;
224         }
225 
build()226         public byte[] build() {
227             ByteArrayOutputStream baos = new ByteArrayOutputStream();
228 
229             byte[] headerMagicBytes = headerMagic.getBytes(StandardCharsets.US_ASCII);
230             baos.write(headerMagicBytes, 0, headerMagicBytes.length);
231             baos.write(0);
232 
233             // Write out the offsets for later manipulation.
234             int indexOffsetOffset = baos.size();
235             writeInt(baos, 0);
236             int dataOffsetOffset = baos.size();
237             writeInt(baos, 0);
238             int zoneTabOffsetOffset = baos.size();
239             writeInt(baos, 0);
240 
241             // Construct the data section in advance, so we know the offsets.
242             ByteArrayOutputStream dataBytes = new ByteArrayOutputStream();
243             Map<String, Integer> offsets = new HashMap<>();
244             for (ZicDatum datum : zicData) {
245                 int offset = dataBytes.size();
246                 offsets.put(datum.id, offset);
247                 writeByteArray(dataBytes, datum.data);
248             }
249 
250             int indexOffset = baos.size();
251 
252             // Write the index section.
253             for (ZicDatum zicDatum : zicData) {
254                 // Write the ID.
255                 String id = zicDatum.id;
256                 byte[] idBytes = id.getBytes(StandardCharsets.US_ASCII);
257                 byte[] paddedIdBytes = new byte[40];
258                 System.arraycopy(idBytes, 0, paddedIdBytes, 0, idBytes.length);
259                 writeByteArray(baos, paddedIdBytes);
260                 // Write offset of zic data in the data section.
261                 Integer offset = offsets.get(id);
262                 writeInt(baos, offset);
263                 // Write the length of the zic data.
264                 writeInt(baos, zicDatum.data.length);
265                 // Write a filler value (not used)
266                 writeInt(baos, 0);
267             }
268 
269             // Write the data section.
270             int dataOffset = baos.size();
271             writeByteArray(baos, dataBytes.toByteArray());
272 
273             // Write the zoneTab section.
274             int zoneTabOffset = baos.size();
275             byte[] zoneTabBytes = zoneTab.getBytes(StandardCharsets.US_ASCII);
276             writeByteArray(baos, zoneTabBytes);
277 
278             byte[] bytes = baos.toByteArray();
279             setInt(bytes, indexOffsetOffset,
280                     indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
281             setInt(bytes, dataOffsetOffset,
282                     dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
283             setInt(bytes, zoneTabOffsetOffset,
284                     zoneTabOffsetOverride != null ? zoneTabOffsetOverride : zoneTabOffset);
285             return bytes;
286         }
287 
288         private static class ZicDatum {
289             public final String id;
290             public final byte[] data;
291 
ZicDatum(String id, byte[] data)292             ZicDatum(String id, byte[] data) {
293                 this.id = id;
294                 this.data = data;
295             }
296         }
297     }
298 
writeByteArray(ByteArrayOutputStream baos, byte[] array)299     static void writeByteArray(ByteArrayOutputStream baos, byte[] array) {
300         baos.write(array, 0, array.length);
301     }
302 
writeByte(ByteArrayOutputStream baos, int value)303     static void writeByte(ByteArrayOutputStream baos, int value) {
304         baos.write(value);
305     }
306 
writeIntArray(ByteArrayOutputStream baos, int[] array)307     static void writeIntArray(ByteArrayOutputStream baos, int[] array) {
308         for (int value : array) {
309             writeInt(baos, value);
310         }
311     }
312 
writeInt(ByteArrayOutputStream os, int value)313     static void writeInt(ByteArrayOutputStream os, int value) {
314         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
315         writeByteArray(os, bytes);
316     }
317 
setInt(byte[] bytes, int offset, int value)318     static void setInt(byte[] bytes, int offset, int value) {
319         bytes[offset] = (byte) (value >>> 24);
320         bytes[offset + 1] = (byte) (value >>> 16);
321         bytes[offset + 2] = (byte) (value >>> 8);
322         bytes[offset + 3] = (byte) value;
323     }
324 }
325