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.storage.table.packed.read; 18 19 import com.android.storage.block.read.BlockData; 20 import com.android.storage.table.reader.IntValueTable.IntValueEntryMatcher; 21 import com.android.storage.table.reader.LongValueTable.LongValueEntryMatcher; 22 import com.android.storage.util.BitwiseUtils; 23 24 import java.util.Objects; 25 26 /** 27 * A helper class that knows how to read packed tables from {@link BlockData}. Packed tables are a 28 * sorted structure containing fixed-width records made up of a key of N bits and a value of the 29 * remaining bits. Keys do not have to be unique. The combination of ordering by key and fixed-width 30 * records makes packed tables quick to search by key and also compact. 31 * 32 * <p>The information about the entry size / key size / how the value is to be treated is encoded in 33 * the first few bytes of the block data along with the shared table data. 34 * 35 * <p>The implementation is limited in that it can only read entries up to 8 bytes wide. Keys can be 36 * (at most) signed 31-bit values. Entries can only be multiples of a byte. 37 */ 38 public final class PackedTableReader { 39 40 /** The bit in the bit field used to store whether the value is to be treated as signed. */ 41 public static final int BIT_FIELD_SIGNED_VALUE_MASK = 0x01; 42 43 /** The block data to read from . */ 44 private final BlockData mBlockData; 45 46 /** The number of bytes in the BlockData used to hold table / entry metadata. */ 47 private final int mHeaderLengthBytes; 48 49 /** The number of bytes per entry. */ 50 private final int mEntrySizeBytes; 51 52 private final int mEntrySizeBits; 53 54 private final int mKeySizeBits; 55 56 private final int mValueSizeBits; 57 58 private final int mEntryCount; 59 60 /** Domain-specific data that should be common to all entries. */ 61 private final byte[] mSharedData; 62 63 /** 64 * True if the value is to be treated as a signed value, i.e. whether its sign should be 65 * extended when converting it to a Java type. 66 */ 67 private final boolean mSignedValue; 68 69 /** 70 * True if the value can fit in a signed / unsigned int (depending on {@link #mSignedValue} 71 * without risk of overflow. 72 */ 73 private final boolean mIntValueSupported; 74 PackedTableReader(BlockData blockData)75 public PackedTableReader(BlockData blockData) { 76 this(blockData, false); 77 } 78 PackedTableReader(BlockData blockData, boolean useBigSharedData)79 public PackedTableReader(BlockData blockData, boolean useBigSharedData) { 80 mBlockData = Objects.requireNonNull(blockData); 81 82 int offset = 0; 83 84 if (useBigSharedData) { 85 mSharedData = blockData.getByteArray(offset); 86 offset += Integer.BYTES + mSharedData.length; 87 } else { 88 mSharedData = blockData.getTinyByteArray(offset); 89 offset += Byte.BYTES + mSharedData.length; 90 } 91 92 // Boolean properties are extracted from a 32-bit bit field. 93 int bitField = blockData.getUnsignedByte(offset); 94 offset += 1; 95 mSignedValue = (bitField & BIT_FIELD_SIGNED_VALUE_MASK) > 0; 96 97 mEntrySizeBytes = blockData.getUnsignedByte(offset); 98 offset += 1; 99 if (mEntrySizeBytes > 8) { 100 throw new IllegalStateException( 101 "Entries cannot exceed 8 bytes in width, entrySizeBytes=" + mEntrySizeBytes); 102 } 103 mEntrySizeBits = mEntrySizeBytes * 8; 104 105 mKeySizeBits = blockData.getUnsignedByte(offset); 106 offset += 1; 107 108 // There must be at least one key bit and at least one value bit. The key cannot exceed 31 109 // bits in size. 110 int maxKeySizeBits = Math.min(31, mEntrySizeBits - 1); 111 if (mKeySizeBits < 1 || mKeySizeBits > maxKeySizeBits) { 112 throw new IllegalStateException("keySizeBits must be >= 1 and <= " + maxKeySizeBits); 113 } 114 115 // The value bits are whatever entry bits are not key bits. 116 mValueSizeBits = mEntrySizeBits - mKeySizeBits; 117 if (mSignedValue) { 118 mIntValueSupported = mValueSizeBits <= Integer.SIZE; 119 } else { 120 mIntValueSupported = mValueSizeBits <= Integer.SIZE - 1; 121 } 122 123 mHeaderLengthBytes = offset; 124 125 // Calculate the entry count. Note: There can be more than 2^N entries because duplicate 126 // keys are supported. 127 mEntryCount = (blockData.getSize() - mHeaderLengthBytes) / mEntrySizeBytes; 128 } 129 130 /** Returns the number of bytes used to store each entry. */ getEntrySizeBytes()131 public int getEntrySizeBytes() { 132 return mEntrySizeBytes; 133 } 134 135 /** Returns the number of bits devoted to the key. */ getKeySizeBits()136 public int getKeySizeBits() { 137 return mKeySizeBits; 138 } 139 140 /** Returns true if the value is signed. */ isValueSigned()141 public boolean isValueSigned() { 142 return mSignedValue; 143 } 144 145 /** Returns the number of bits devoted to the value. */ getValueSizeBits()146 public int getValueSizeBits() { 147 return mValueSizeBits; 148 } 149 150 /** Returns the table's shared data. */ getSharedData()151 public byte[] getSharedData() { 152 return mSharedData; 153 } 154 155 /** Returns the number of entries in the table. */ getEntryCount()156 public int getEntryCount() { 157 return mEntryCount; 158 } 159 160 /** 161 * Returns an {@link Entry} with the specified key or {@code null} if there isn't one. If 162 * multiple entries have the key, then an arbitrary entry with the key is returned. 163 */ getEntry(int key)164 public Entry getEntry(int key) { 165 BitwiseUtils.checkUnsignedValueInRange(mKeySizeBits, key); 166 return findLongValueEntry((k, v) -> key - k); 167 } 168 169 /** Returns the {@link Entry} with the specified index. */ getEntryByIndex(int i)170 public Entry getEntryByIndex(int i) { 171 long entryBytes = getEntryBytesForIndex(i); 172 return new Entry(i, entryBytes); 173 } 174 175 /** 176 * Finds an entry in the table using the supplied matcher, return {@code null} if none match. If 177 * multiple entries in the table match then an arbitrary matching entry is returned. 178 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 179 * of overflow with an int. 180 */ findIntValueEntry(IntValueEntryMatcher entryMatcher)181 public Entry findIntValueEntry(IntValueEntryMatcher entryMatcher) { 182 checkIntValueSupported(); 183 return findLongValueEntry(convertToLongValueEntryMatcher(entryMatcher)); 184 } 185 186 /** 187 * Finds an entry in the table using the supplied matcher, return {@code null} if none match. If 188 * multiple entries in the table match then an arbitrary matching entry is returned. 189 */ findLongValueEntry(LongValueEntryMatcher entryMatcher)190 public Entry findLongValueEntry(LongValueEntryMatcher entryMatcher) { 191 int lower = 0; 192 int upper = mEntryCount - 1; 193 return binarySearch(lower, upper, entryMatcher); 194 } 195 binarySearch(int lower, int upper, LongValueEntryMatcher entryMatcher)196 private Entry binarySearch(int lower, int upper, LongValueEntryMatcher entryMatcher) { 197 while (lower <= upper) { 198 int mid = (lower + upper) / 2; 199 200 long entryBytes = getEntryBytesForIndex(mid); 201 int entryKey = getKeyFromEntryBytes(entryBytes); 202 long entryValue = getLongValueFromEntryBytes(entryBytes); 203 int compareResult = entryMatcher.compare(entryKey, entryValue); 204 if (compareResult > 0) { 205 lower = mid + 1; 206 } else if (compareResult < 0) { 207 upper = mid - 1; 208 } else { 209 return new Entry(mid, entryBytes); 210 } 211 } 212 return null; 213 } 214 getEntryBytesForIndex(int index)215 private long getEntryBytesForIndex(int index) { 216 if (index < 0 || index >= mEntryCount) { 217 throw new IndexOutOfBoundsException(); 218 } 219 int byteOffset = mHeaderLengthBytes + (index * mEntrySizeBytes); 220 return mBlockData.getValueAsLong(mEntrySizeBytes, byteOffset, mSignedValue); 221 } 222 getKeyFromEntryBytes(long entryBytes)223 private int getKeyFromEntryBytes(long entryBytes) { 224 return getKeyFromEntryBytes(mEntrySizeBits, mKeySizeBits, entryBytes); 225 } 226 227 /** 228 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 229 * of overflow with an int. 230 */ getIntValueFromEntryBytes(long entryBytes)231 private int getIntValueFromEntryBytes(long entryBytes) { 232 checkIntValueSupported(); 233 long value = getLongValueFromEntryBytes(entryBytes); 234 return (int) value; 235 } 236 getLongValueFromEntryBytes(long entryBytes)237 private long getLongValueFromEntryBytes(long entryBytes) { 238 long value = BitwiseUtils.extractLowBits(mValueSizeBits, entryBytes); 239 if (mSignedValue) { 240 value = BitwiseUtils.signExtendToLong(mValueSizeBits, value); 241 } 242 return value; 243 } 244 checkIntValueSupported()245 private void checkIntValueSupported() { 246 if (!mIntValueSupported) { 247 throw new IllegalStateException("value size is too big to support int," 248 + " mSignedValue=" + mSignedValue + ", mValueSizeBits=" + mValueSizeBits); 249 } 250 } 251 252 /** 253 * An entry from a packed table. 254 */ 255 public final class Entry { 256 257 final int mIndex; 258 259 /** The entry bytes held in a long. */ 260 private final long mEntryBytes; 261 262 /** The key, lazily initialized. Use {@link #getKey()} to access the key. */ 263 private int mKey = -1; 264 Entry(int index, long entryBytes)265 Entry(int index, long entryBytes) { 266 mIndex = index; 267 mEntryBytes = entryBytes; 268 } 269 270 /** Returns the next entry in the table, {@code null} if there isn't one. */ getNext()271 public Entry getNext() { 272 if (mIndex >= mEntryCount - 1) { 273 return null; 274 } 275 int nextIndex = mIndex + 1; 276 return getEntryByIndex(nextIndex); 277 } 278 279 /** Returns the previous entry in the table, {@code null} if there isn't one. */ getPrevious()280 public Entry getPrevious() { 281 if (mIndex == 0) { 282 return null; 283 } 284 int previousIndex = mIndex - 1; 285 return getEntryByIndex(previousIndex); 286 } 287 288 /** 289 * Finds an entry using the supplied matcher via a binary search. Returns {@code null} if no 290 * entry matches. Uses this entry as a starting point. 291 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 292 * of overflow with an int. 293 */ findNearbyEntry(IntValueEntryMatcher entryMatcher)294 public Entry findNearbyEntry(IntValueEntryMatcher entryMatcher) { 295 checkIntValueSupported(); 296 return findNearbyEntry(convertToLongValueEntryMatcher(entryMatcher)); 297 } 298 299 /** 300 * Finds an entry using the supplied matcher via a binary search. Returns {@code null} if no 301 * entry matches. Uses this entry as a starting point. 302 */ findNearbyEntry(LongValueEntryMatcher entryMatcher)303 public Entry findNearbyEntry(LongValueEntryMatcher entryMatcher) { 304 int key = getKey(); 305 int compare = entryMatcher.compare(key, getLongValue()); 306 if (compare == 0) { 307 return this; 308 } 309 310 int lower, upper; 311 if (compare < 0) { 312 lower = 0; 313 upper = mIndex; 314 } else { 315 lower = mIndex; 316 upper = mEntryCount - 1; 317 } 318 return binarySearch(lower, upper, entryMatcher); 319 } 320 321 /** Returns the key for this entry. */ getKey()322 public int getKey() { 323 if (mKey == -1) { 324 mKey = getKeyFromEntryBytes(mEntryBytes); 325 } 326 return mKey; 327 } 328 329 /** Returns the index for this entry. */ getIndex()330 public int getIndex() { 331 return mIndex; 332 } 333 334 /** 335 * Returns the entry's value as an int. 336 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 337 * of overflow with an int. 338 */ getIntValue()339 public int getIntValue() { 340 return getIntValueFromEntryBytes(mEntryBytes); 341 } 342 343 /** 344 * Returns the entry's value as a long. 345 */ getLongValue()346 public long getLongValue() { 347 return getLongValueFromEntryBytes(mEntryBytes); 348 } 349 350 @Override equals(Object o)351 public boolean equals(Object o) { 352 if (this == o) { 353 return true; 354 } 355 if (o == null || getClass() != o.getClass()) { 356 return false; 357 } 358 Entry entry = (Entry) o; 359 return mIndex == entry.mIndex 360 && mEntryBytes == entry.mEntryBytes; 361 } 362 363 @Override hashCode()364 public int hashCode() { 365 return Objects.hash(mIndex); 366 } 367 } 368 getKeyFromEntryBytes(int entrySizeBits, int keySizeBits, long entryBytes)369 private static int getKeyFromEntryBytes(int entrySizeBits, int keySizeBits, long entryBytes) { 370 long mask = BitwiseUtils.getMidBitsMask(entrySizeBits, keySizeBits); 371 372 // Extract the key bits and shift them right. 373 long keyBits = mask & entryBytes; 374 int valueSizeBits = entrySizeBits - keySizeBits; 375 return (int) (keyBits >>> valueSizeBits); 376 } 377 convertToLongValueEntryMatcher( IntValueEntryMatcher entryMatcher)378 private static LongValueEntryMatcher convertToLongValueEntryMatcher( 379 IntValueEntryMatcher entryMatcher) { 380 return (key, value) -> entryMatcher.compare(key, (int) value); 381 } 382 } 383