• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &gt;= 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