1 /* 2 * Copyright (C) 2020 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 17 package com.android.timezone.location.storage.tzs2range; 18 19 import static com.android.timezone.location.storage.s2.S2Support.FACE_BIT_COUNT; 20 import static com.android.timezone.location.storage.s2.S2Support.MAX_FACE_ID; 21 import static com.android.timezone.location.storage.s2.S2Support.MAX_S2_LEVEL; 22 23 import com.android.timezone.location.storage.s2.S2Support; 24 import com.android.timezone.location.storage.util.BitwiseUtils; 25 import com.android.timezone.location.storage.util.Conditions; 26 27 import java.util.Objects; 28 29 /** 30 * Holds information about the format of a tz s2 data file, which is a type of block file (see 31 * {@link com.android.timezone.location.storage.block.read.BlockFileReader}). 32 * Some information is hardcode and some is parameterized using information read from the file's 33 * header block. This class contains useful methods for validation, interpretation and storage of 34 * data in a file with the specified format. 35 */ 36 public final class TzS2RangeFileFormat { 37 38 /** The block type of the tz s2 data file header (held in block 0). */ 39 public static final int BLOCK_TYPE_HEADER = 1; 40 41 /** 42 * The block type used for padding between the header and suffix tables that allows for future 43 * expansion. See {@link #getSuffixTableBlockIdOffset()}. 44 */ 45 public static final int BLOCK_TYPE_PADDING = 20; 46 47 /** The block type of a populated suffix table. */ 48 public static final int BLOCK_TYPE_SUFFIX_TABLE = 10; 49 50 /** The expected magic value of a tz s2 data file. */ 51 public static final char MAGIC = 0xAFCF; 52 53 /** The format version of the tz s2 data file, read and written. */ 54 public static final int VERSION = 1; 55 56 private final int mDataS2Level; 57 58 private final int mPrefixBitCount; 59 60 private final int mMaxPrefixValue; 61 62 private final int mSuffixTableBlockIdOffset; 63 64 private final int mSuffixBitCount; 65 66 private final int mMaxSuffixValue; 67 68 private final int mTableEntryByteCount; 69 70 private final int mTableEntryBitCount; 71 72 private final int mTableEntryRangeLengthBitCount; 73 74 private final int mTableEntryMaxRangeLengthValue; 75 76 /** The number of bits in each suffix table entry used to store the TZ ID Set ID. */ 77 private final int mTableEntryTzIdSetIdBitCount; 78 79 private final int mTableEntryMaxTzIdSetIdValue; 80 81 /** 82 * The number of bits in a cell ID of the data storage level that have fixed values, i.e. the 83 * final "1" followed by all zeros. 84 */ 85 private final int mUnusedCellIdBitCount; 86 87 /** 88 * Creates a new file format. This constructor validates the values against various hard-coded 89 * constraints and will throw an {@link IllegalArgumentException} if they are not satisfied. 90 */ TzS2RangeFileFormat(int s2Level, int prefixBitCount, int suffixBitCount, int suffixTableBlockIdOffset, int tableEntryBitCount, int entryRangeLengthBitCount)91 public TzS2RangeFileFormat(int s2Level, int prefixBitCount, int suffixBitCount, 92 int suffixTableBlockIdOffset, int tableEntryBitCount, int entryRangeLengthBitCount) { 93 94 Conditions.checkArgInRange("s2Level", s2Level, 0, MAX_S2_LEVEL); 95 96 // prefixBitCount must include at least the face bits and one more, it makes the logic 97 // for mMaxPrefixValue easier below. We also assume that prefix and suffix will be 31-bits 98 // as that makes sure they can be represented, unsigned in a Java int. A prefix / suffix 99 // of 31-bits (each) should be enough for anyone(TM). 31-bits = ~15 S2 levels. 100 // Anything more than level 18 for time zone data (i.e. prefix PLUS suffix) will be very 101 // detailed so it's unlikely this constraint will be a problem. 102 Conditions.checkArgInRange("prefixBitCount", prefixBitCount, 4, Integer.SIZE - 1); 103 104 // The suffix table uses fixed length records that are broken into a key and a value. The 105 // implementation requires the key is an unsigned int value, i.e. 31-bits, and the value can 106 // be held in an int value. 107 108 // The key of a suffix table entry is used to store the suffix for the cell ID at the start 109 // of a range of S2 cells (the prefix for that cell ID is implicit and the same for every 110 // entry in the table, see prefixBitCount). 111 int tableEntryKeyBitCount = suffixBitCount; 112 Conditions.checkArgInRange( 113 "tableEntryKeyBitCount", tableEntryKeyBitCount, 1, Integer.SIZE - 1); 114 115 // The value of a suffix table entry is used to hold both the range length and the TZ ID set 116 // ID. See methods below that extract these components from an int. Hence, we check they 117 // will fit. 118 int tableEntryValueBitCount = tableEntryBitCount - tableEntryKeyBitCount; 119 Conditions.checkArgInRange( 120 "tableEntryValueBitCount", tableEntryValueBitCount, 1, Integer.SIZE); 121 122 if (S2Support.storageBitCountForLevel(s2Level) != prefixBitCount + suffixBitCount) { 123 // s2Level implies cellIds have a certain number of "storage bits", the prefix and 124 // suffix must consume all the bits. 125 throw new IllegalArgumentException("prefixBitCount=" + prefixBitCount 126 + " + suffixBitCount=" + suffixBitCount + " must be correct for the s2Level (" 127 + S2Support.storageBitCountForLevel(s2Level) + ")"); 128 } 129 if (suffixTableBlockIdOffset < 1) { 130 // The format includes a header block, so there will always be an adjustment for at 131 // least that one block. 132 throw new IllegalArgumentException( 133 "suffixTableBlockIdOffset=" + suffixTableBlockIdOffset + " must be >= 1"); 134 } 135 if (tableEntryBitCount < 0 || tableEntryBitCount % Byte.SIZE != 0 136 || tableEntryBitCount > Long.SIZE) { 137 // The classes used to read suffix tables only support entries that are a multiples of 138 // a byte. They also restrict to up a maximum of 8-bytes per table entry. 139 throw new IllegalArgumentException( 140 "suffixTableEntryBitCount=" + tableEntryBitCount 141 + " must be >= 0, be divisible by 8, and be no more than 64 bits"); 142 } 143 144 // The range length bit count must be positive. Also, the suffix table entry has to fit 145 // the start value (suffixBitCount), the range length (min 2 bit but typically more) and 146 // the TZ ID Set ID (min 2 bit, but typically more). For simplicity below we ensure we 147 // can hold the maximum range length value in an unsigned Java int, so 31-bits. 148 Conditions.checkArgInRange("entryRangeLengthBitCount", entryRangeLengthBitCount, 149 2, Math.min(tableEntryBitCount - 2, Integer.SIZE - 1)); 150 151 // Set all the fields. The fields are either set directly from parameters or derived from 152 // the values given. 153 154 mDataS2Level = s2Level; 155 mPrefixBitCount = prefixBitCount; 156 157 // Prefix value: contains the face ID plus one or more bits for the index. 158 int cellIdIndexBitCount = prefixBitCount - FACE_BIT_COUNT; 159 mMaxPrefixValue = (int) 160 ((((long) MAX_FACE_ID) << cellIdIndexBitCount) 161 | BitwiseUtils.maxUnsignedValue(cellIdIndexBitCount)); 162 163 mSuffixBitCount = suffixBitCount; 164 mMaxSuffixValue = (int) BitwiseUtils.maxUnsignedValue(suffixBitCount); 165 166 // prefixBitCount + suffixBitCount are all the "useful" bits. The remaining bits in a 64-bit 167 // cell ID are the trailing "1" (which we don't need to store) and the rest are zeros. 168 mUnusedCellIdBitCount = Long.SIZE - (prefixBitCount + suffixBitCount); 169 170 mTableEntryBitCount = tableEntryBitCount; 171 mTableEntryByteCount = tableEntryBitCount / 8; 172 173 mTableEntryRangeLengthBitCount = entryRangeLengthBitCount; 174 mTableEntryMaxRangeLengthValue = 175 (int) BitwiseUtils.maxUnsignedValue(entryRangeLengthBitCount); 176 177 // Everything in a suffix table entry that isn't the suffix or range length is the TZ ID Set 178 // ID so we can calculate it from the information given. 179 mTableEntryTzIdSetIdBitCount = 180 tableEntryBitCount - (suffixBitCount + entryRangeLengthBitCount); 181 mTableEntryMaxTzIdSetIdValue = 182 (int) BitwiseUtils.maxUnsignedValue(mTableEntryTzIdSetIdBitCount); 183 184 mSuffixTableBlockIdOffset = suffixTableBlockIdOffset; 185 } 186 187 /** Returns the S2 level of all geo data stored in the file. */ getS2Level()188 public int getS2Level() { 189 return mDataS2Level; 190 } 191 192 /** 193 * Returns the number of prefix bits from an S2 cell ID used to identify the block containing 194 * ranges. 195 */ getPrefixBitCount()196 public int getPrefixBitCount() { 197 return mPrefixBitCount; 198 } 199 200 /** 201 * Returns the maximum valid value that {@link #getPrefixBitCount()} can represent. Note: This 202 * is not just the number of bits: the prefix contains the face ID which can only be 0 - 5 203 * (inclusive). 204 */ getMaxPrefixValue()205 public int getMaxPrefixValue() { 206 return mMaxPrefixValue; 207 } 208 209 /** 210 * Returns the number of "useful" bits of an S2 cell ID in the data after 211 * {@link #getPrefixBitCount()}, i.e. not including the trailing "1". 212 * Dependent on the {@link #getS2Level()}, which dictates the number of storage bits in every 213 * cell ID in a file, and {@link #getPrefixBitCount()}. 214 */ getSuffixBitCount()215 public int getSuffixBitCount() { 216 return mSuffixBitCount; 217 } 218 219 /** 220 * Returns the maximum value that {@link #getSuffixBitCount()} can represent. 221 */ getMaxSuffixValue()222 public int getMaxSuffixValue() { 223 return mMaxSuffixValue; 224 } 225 226 /** 227 * Returns the number of bits in each suffix table entry. i.e. 228 * {@link #getTableEntryByteCount()} * 8 229 */ getTableEntryBitCount()230 public int getTableEntryBitCount() { 231 return mTableEntryBitCount; 232 } 233 234 /** Returns the number of bytes in each suffix table entry. */ getTableEntryByteCount()235 public int getTableEntryByteCount() { 236 return mTableEntryByteCount; 237 } 238 239 /** Return the number of bits in each suffix table entry used to store the length of a range. */ getTableEntryRangeLengthBitCount()240 public int getTableEntryRangeLengthBitCount() { 241 return mTableEntryRangeLengthBitCount; 242 } 243 244 /** Returns the maximum value that {@link #getTableEntryRangeLengthBitCount()} can represent. */ getTableEntryMaxRangeLengthValue()245 public int getTableEntryMaxRangeLengthValue() { 246 return mTableEntryMaxRangeLengthValue; 247 } 248 249 /** Returns the maximum value that can be used as a TZ IDS set ID. */ getMaxTzIdSetIdValue()250 public int getMaxTzIdSetIdValue() { 251 return mTableEntryMaxTzIdSetIdValue; 252 } 253 254 /** 255 * Returns thee offset to apply to the prefix value to compute the block ID holding the data for 256 * that prefix. Always >= 1 to account for the header block. 257 */ getSuffixTableBlockIdOffset()258 public int getSuffixTableBlockIdOffset() { 259 return mSuffixTableBlockIdOffset; 260 } 261 262 /** Extracts the prefix bits from a cell ID and returns them as an unsigned int. */ extractPrefixValueFromCellId(long cellId)263 public int extractPrefixValueFromCellId(long cellId) { 264 checkS2Level("cellId", cellId); 265 return (int) (cellId >>> (mSuffixBitCount + mUnusedCellIdBitCount)); 266 } 267 268 /** Extracts the suffix bits from a cell ID and returns them as an unsigned int. */ extractSuffixValueFromCellId(long cellId)269 public int extractSuffixValueFromCellId(long cellId) { 270 checkS2Level("cellId", cellId); 271 return (int) (cellId >>> (mUnusedCellIdBitCount)) & mMaxSuffixValue; 272 } 273 274 /** Extracts the range length from a table entry value. */ extractRangeLengthFromTableEntryValue(int value)275 public int extractRangeLengthFromTableEntryValue(int value) { 276 return extractRangeLength(value, 277 mTableEntryRangeLengthBitCount + mTableEntryTzIdSetIdBitCount, 278 mTableEntryRangeLengthBitCount); 279 } 280 extractRangeLength(int value, int suffixRecordBitCount, int rangeLengthBitCount)281 private static int extractRangeLength(int value, int suffixRecordBitCount, 282 int rangeLengthBitCount) { 283 long mask = BitwiseUtils.getMidBitsMask(suffixRecordBitCount, rangeLengthBitCount); 284 return (int) ((value & mask) >> (suffixRecordBitCount - rangeLengthBitCount)); 285 } 286 287 /** Extracts the TZ IDs Set ID from a table entry value. */ extractTzIdSetIdFromTableEntryValue(int value)288 public int extractTzIdSetIdFromTableEntryValue(int value) { 289 return extractTzIdSetId(value, mTableEntryTzIdSetIdBitCount); 290 } 291 extractTzIdSetId(int value, int bitCount)292 private static int extractTzIdSetId(int value, int bitCount) { 293 long mask = BitwiseUtils.getLowBitsMask(bitCount); 294 return (int) (value & mask); 295 } 296 297 /** Creates a table entry value from a range length and a TZ IDs set ID. */ createSuffixTableValue(int rangeLength, int tzIdSetId)298 public long createSuffixTableValue(int rangeLength, int tzIdSetId) { 299 Conditions.checkArgInRange( 300 "rangeLength", rangeLength, 0, getTableEntryMaxRangeLengthValue()); 301 Conditions.checkArgInRange("tzIdSetId", tzIdSetId, 0, getMaxTzIdSetIdValue()); 302 long value = ((long) rangeLength) << mTableEntryTzIdSetIdBitCount; 303 value |= tzIdSetId; 304 return value; 305 } 306 307 /** Creates a cell ID from a prefix and a suffix component. */ createCellId(int prefixValue, int suffixValue)308 public long createCellId(int prefixValue, int suffixValue) { 309 Conditions.checkArgInRange("prefixValue", prefixValue, 0, mMaxPrefixValue); 310 Conditions.checkArgInRange("suffixValue", suffixValue, 0, mMaxSuffixValue); 311 long cellId = prefixValue; 312 cellId <<= mSuffixBitCount; 313 cellId |= suffixValue; 314 cellId <<= 1; 315 cellId |= 1; 316 cellId <<= mUnusedCellIdBitCount - 1; 317 318 checkS2Level("cellId", cellId); 319 return cellId; 320 } 321 322 /** Extracts the face ID bits from a prefix value. */ extractFaceIdFromPrefix(int prefixValue)323 public int extractFaceIdFromPrefix(int prefixValue) { 324 return prefixValue >>> (mPrefixBitCount - FACE_BIT_COUNT); 325 } 326 327 /** 328 * Calculates the number of cell IDs in the given range. Throws {@link IllegalArgumentException} 329 * if {@code rangeStartCellId} is "higher" than {@code rangeEndCellId} or the range length would 330 * be outside of the int range. 331 * 332 * @param rangeStartCellId the start of the range (inclusive) 333 * @param rangeEndCellId the end of the range (exclusive) 334 */ calculateRangeLength(long rangeStartCellId, long rangeEndCellId)335 public int calculateRangeLength(long rangeStartCellId, long rangeEndCellId) { 336 checkS2Level("rangeStartCellId", rangeStartCellId); 337 checkS2Level("rangeEndCellId", rangeEndCellId); 338 339 // Convert to numeric values that can just be subtracted without worrying about sign 340 // issues. 341 long rangeEndCellIdNumeric = rangeEndCellId >>> mUnusedCellIdBitCount; 342 long rangeStartCellIdNumeric = rangeStartCellId >>> mUnusedCellIdBitCount; 343 if (rangeStartCellIdNumeric >= rangeEndCellIdNumeric) { 344 throw new IllegalArgumentException( 345 "rangeStartCellId=" + cellIdToString(rangeStartCellId) 346 + " is >= rangeEndCellId=" + cellIdToString(rangeEndCellId)); 347 } 348 long differenceNumeric = rangeEndCellIdNumeric - rangeStartCellIdNumeric; 349 if (differenceNumeric < 0 || differenceNumeric > Integer.MAX_VALUE) { 350 throw new IllegalArgumentException("rangeLength=" + differenceNumeric 351 + " is outside of expected range"); 352 } 353 return (int) differenceNumeric; 354 } 355 356 /** Formats a cellId in terms of prefix and suffix values. */ cellIdToString(long cellId)357 public String cellIdToString(long cellId) { 358 int prefix = extractPrefixValueFromCellId(cellId); 359 int suffix = extractSuffixValueFromCellId(cellId); 360 String prefixBitsString = BitwiseUtils.toUnsignedString(mPrefixBitCount, prefix); 361 String suffixBitsString = BitwiseUtils.toUnsignedString(mSuffixBitCount, suffix); 362 return "cellId{prefix=" + prefix + " (" + prefixBitsString + ")" 363 + ", suffix=" + suffix + " (" + suffixBitsString + ")" 364 + "}"; 365 } 366 367 @Override toString()368 public String toString() { 369 return "TzS2RangeFileFormat{" 370 + "mDataS2Level=" + mDataS2Level 371 + ", mPrefixBitCount=" + mPrefixBitCount 372 + ", mMaxPrefixValue=" + mMaxPrefixValue 373 + ", mSuffixBitCount=" + mSuffixBitCount 374 + ", mMaxSuffixValue=" + mMaxSuffixValue 375 + ", mTableEntryBitCount=" + mTableEntryBitCount 376 + ", mTableEntryRangeLengthBitCount=" + mTableEntryRangeLengthBitCount 377 + ", mTableEntryMaxRangeLengthValue=" + mTableEntryMaxRangeLengthValue 378 + ", mTableEntryTzIdSetIdBitCount=" + mTableEntryTzIdSetIdBitCount 379 + ", mTableEntryMaxTzIdSetIdValue=" + mTableEntryMaxTzIdSetIdValue 380 + ", mSuffixTableBlockIdOffset=" + mSuffixTableBlockIdOffset 381 + ", mUnusedCellIdBitCount=" + mUnusedCellIdBitCount 382 + '}'; 383 } 384 385 @Override equals(Object o)386 public boolean equals(Object o) { 387 if (this == o) { 388 return true; 389 } 390 if (o == null || getClass() != o.getClass()) { 391 return false; 392 } 393 TzS2RangeFileFormat that = (TzS2RangeFileFormat) o; 394 return mDataS2Level == that.mDataS2Level 395 && mPrefixBitCount == that.mPrefixBitCount 396 && mMaxPrefixValue == that.mMaxPrefixValue 397 && mSuffixBitCount == that.mSuffixBitCount 398 && mMaxSuffixValue == that.mMaxSuffixValue 399 && mTableEntryBitCount == that.mTableEntryBitCount 400 && mTableEntryRangeLengthBitCount == that.mTableEntryRangeLengthBitCount 401 && mTableEntryMaxRangeLengthValue == that.mTableEntryMaxRangeLengthValue 402 && mTableEntryTzIdSetIdBitCount == that.mTableEntryTzIdSetIdBitCount 403 && mTableEntryMaxTzIdSetIdValue == that.mTableEntryMaxTzIdSetIdValue 404 && mSuffixTableBlockIdOffset == that.mSuffixTableBlockIdOffset 405 && mUnusedCellIdBitCount == that.mUnusedCellIdBitCount; 406 } 407 408 @Override hashCode()409 public int hashCode() { 410 return Objects.hash(mDataS2Level, mPrefixBitCount, mMaxPrefixValue, mSuffixBitCount, 411 mMaxSuffixValue, mTableEntryBitCount, mTableEntryRangeLengthBitCount, 412 mTableEntryMaxRangeLengthValue, mTableEntryTzIdSetIdBitCount, 413 mTableEntryMaxTzIdSetIdValue, mSuffixTableBlockIdOffset, mUnusedCellIdBitCount); 414 } 415 checkS2Level(String name, long cellId)416 private void checkS2Level(String name, long cellId) { 417 if (S2Support.getS2Level(cellId) != mDataS2Level) { 418 throw new IllegalArgumentException( 419 name + "=" + S2Support.cellIdToString(cellId) + " is at the wrong level"); 420 } 421 } 422 } 423