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