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