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