| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/indexed_db/indexed_db_leveldb_coding.h" |
| |
| #include <array> |
| #include <iterator> |
| #include <limits> |
| #include <list> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/bits.h" |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/span.h" |
| #include "base/notreached.h" |
| #include "base/numerics/byte_conversions.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/indexed_db/scopes/leveldb_scopes_coding.h" |
| #include "components/services/storage/indexed_db/scopes/varint_coding.h" |
| |
| // See leveldb_coding_scheme.md for detailed documentation of the coding |
| // scheme implemented here. |
| |
| using blink::IndexedDBKey; |
| using blink::IndexedDBKeyPath; |
| |
| namespace content::indexed_db { |
| |
| namespace { |
| |
| // As most of the IndexedDBKeys and encoded values are short, we |
| // initialize some std::vectors with a default inline buffer size to reduce |
| // the memory re-allocations when the std::vectors are appended. |
| const size_t kDefaultInlineBufferSize = 32; |
| |
| constexpr unsigned char kIndexedDBKeyNullTypeByte = 0; |
| constexpr unsigned char kIndexedDBKeyStringTypeByte = 1; |
| constexpr unsigned char kIndexedDBKeyDateTypeByte = 2; |
| constexpr unsigned char kIndexedDBKeyNumberTypeByte = 3; |
| constexpr unsigned char kIndexedDBKeyArrayTypeByte = 4; |
| constexpr unsigned char kIndexedDBKeyMinKeyTypeByte = 5; |
| constexpr unsigned char kIndexedDBKeyBinaryTypeByte = 6; |
| |
| constexpr unsigned char kSentinel = 0x0; |
| constexpr size_t kSentinelLength = sizeof(kSentinel); |
| // These values are used with sentinel-based encoding. The relative order is |
| // important as it matches the standard algorithm to compare two keys: |
| // https://w3c.github.io/IndexedDB/#compare-two-keys |
| // Gaps are left between values in case we need to insert new types. |
| constexpr unsigned char kOrderedNumberTypeByte = 0x10; |
| constexpr unsigned char kOrderedDateTypeByte = 0x20; |
| constexpr unsigned char kOrderedStringTypeByte = 0x30; |
| constexpr unsigned char kOrderedBinaryTypeByte = 0x40; |
| constexpr unsigned char kOrderedArrayTypeByte = 0x50; |
| |
| constexpr unsigned char kIndexedDBKeyPathTypeCodedByte1 = 0; |
| constexpr unsigned char kIndexedDBKeyPathTypeCodedByte2 = 0; |
| |
| constexpr unsigned char kIndexedDBKeyPathNullTypeByte = 0; |
| constexpr unsigned char kIndexedDBKeyPathStringTypeByte = 1; |
| constexpr unsigned char kIndexedDBKeyPathArrayTypeByte = 2; |
| |
| constexpr unsigned char kObjectStoreDataIndexId = 1; |
| constexpr unsigned char kExistsEntryIndexId = 2; |
| constexpr unsigned char kBlobEntryIndexId = 3; |
| |
| constexpr unsigned char kSchemaVersionTypeByte = 0; |
| constexpr unsigned char kMaxDatabaseIdTypeByte = 1; |
| constexpr unsigned char kDataVersionTypeByte = 2; |
| constexpr unsigned char kRecoveryBlobJournalTypeByte = 3; |
| constexpr unsigned char kActiveBlobJournalTypeByte = 4; |
| constexpr unsigned char kEarliestSweepTimeTypeByte = 5; |
| constexpr unsigned char kEarliestCompactionTimeTypeByte = 6; |
| constexpr unsigned char kMaxSimpleGlobalMetaDataTypeByte = |
| 7; // Insert before this and increment. |
| constexpr unsigned char kScopesPrefixByte = 50; |
| constexpr unsigned char kDatabaseFreeListTypeByte = 100; |
| constexpr unsigned char kDatabaseNameTypeByte = 201; |
| |
| constexpr unsigned char kObjectStoreMetaDataTypeByte = 50; |
| constexpr unsigned char kIndexMetaDataTypeByte = 100; |
| constexpr unsigned char kObjectStoreFreeListTypeByte = 150; |
| constexpr unsigned char kIndexFreeListTypeByte = 151; |
| constexpr unsigned char kObjectStoreNamesTypeByte = 200; |
| constexpr unsigned char kIndexNamesKeyTypeByte = 201; |
| |
| constexpr unsigned char kObjectMetaDataTypeMaximum = 255; |
| constexpr unsigned char kIndexMetaDataTypeMaximum = 255; |
| |
| constexpr unsigned char kTwoByteEncodingIndicator = 0x80; |
| constexpr unsigned char kThreeByteEncodingIndicator = 0xff; |
| |
| // Appends encoded `source` to the end of `target` using a variable-length |
| // encoding that maintains relative comparison order. When `source` is null, a |
| // sentinel is encoded. |
| void EncodeSortableVarChar16(std::optional<char16_t> source, |
| std::string* target) { |
| // The sentinel value is a null byte. |
| if (!source.has_value()) { |
| target->push_back(kSentinel); |
| return; |
| } |
| |
| // All char16_t that fit in 7 bits will be encoded in a single byte (where the |
| // first bit is 0), with a caveat. An actual null byte cannot be encoded as a |
| // null byte because it would conflict with the sentinel, so we add 1 to all |
| // of these, and 0x7f will fall into the next bucket. |
| if (*source <= (0x80 - 2)) { |
| target->push_back(*source + 1); |
| return; |
| } |
| |
| // If the character can fit into 14 bits, encode in two bytes, with the first |
| // two bits 1 and 0 so that it sorts higher than the previous bucket |
| // encodings, which always start with 0. |
| if (*source < (0xffff >> 2)) { |
| unsigned char high = ((*source >> 8) & 0xff) | kTwoByteEncodingIndicator; |
| unsigned char low = *source & 0xff; |
| target->push_back(high); |
| target->push_back(low); |
| return; |
| } |
| |
| // Otherwise we'll need three bytes. The first two bits are 1 and 1 so that it |
| // sorts higher than the two-byte encoding. The following 6 bits are wasted |
| // (all 1, but not used). |
| unsigned char high = (*source >> 8) & 0xff; |
| unsigned char low = *source & 0xff; |
| target->push_back(kThreeByteEncodingIndicator); |
| target->push_back(high); |
| target->push_back(low); |
| } |
| |
| // Decodes the first few bytes (up to 3) from `from`, which were encoded from a |
| // char16_t using EncodeSortableVarChar16(). The value is stored in `target`, |
| // which will be nullopt for the sentinel value. Returns true on success. |
| bool DecodeSortableVarChar16(std::string_view* from, |
| std::optional<char16_t>* target) { |
| if (from->empty()) { |
| return false; |
| } |
| |
| unsigned char first = from->front(); |
| if (first == kSentinel) { |
| from->remove_prefix(1); |
| target->reset(); |
| return true; |
| } |
| |
| if ((first & 0x80) == 0) { |
| from->remove_prefix(1); |
| *target = (first & 0x7f) - 1; |
| return true; |
| } |
| |
| if (from->size() < 2) { |
| return false; |
| } |
| |
| unsigned char second = from->at(1); |
| if ((first & 0b11000000) == kTwoByteEncodingIndicator) { |
| from->remove_prefix(2); |
| *target = char16_t{second} | ((char16_t{first} & 0b00111111) << 8); |
| return true; |
| } |
| |
| if (from->size() < 3) { |
| return false; |
| } |
| |
| unsigned char third = from->at(2); |
| if (first != kThreeByteEncodingIndicator) { |
| return false; |
| } |
| from->remove_prefix(3); |
| *target = char16_t{third} | (char16_t{second} << 8); |
| return true; |
| } |
| |
| IndexedDBKey InvalidKey() { |
| return IndexedDBKey{blink::mojom::IDBKeyType::Invalid}; |
| } |
| |
| inline void EncodeIntSafely(int64_t value, int64_t max, std::string* into) { |
| DCHECK_LE(value, max); |
| return EncodeInt(value, into); |
| } |
| |
| void EncodeStringWithSentinel(const std::u16string& value, std::string* into) { |
| size_t length = value.length(); |
| // This is a guesstimate. |
| into->reserve(into->size() + length * sizeof(char16_t) + kSentinelLength); |
| |
| for (char16_t c : value) { |
| EncodeSortableVarChar16(c, into); |
| } |
| EncodeSortableVarChar16(std::nullopt, into); |
| } |
| |
| // Reads and consumes the first bytes of `encoded` and outputs decoded string to |
| // `output`. Returns true on success. |
| bool DecodeStringWithSentinel(std::string_view& encoded, |
| std::u16string* output) { |
| if (encoded.empty()) { |
| return false; |
| } |
| |
| while (true) { |
| std::optional<char16_t> decoded_char; |
| if (!DecodeSortableVarChar16(&encoded, &decoded_char)) { |
| return false; |
| } |
| if (!decoded_char.has_value()) { |
| // Sentinel value. |
| return true; |
| } |
| output->push_back(*decoded_char); |
| } |
| } |
| |
| // Constants used for EncodeBinaryWithSentinel(). |
| // The amount of data written in between markers/sentinels. |
| constexpr size_t kChunkSize = 8; |
| // Like a "sentinel", but indicates that there is more data to be read. This is |
| // more than the chunk size because all values less than or equal to the chunk |
| // size are reserved for sentinels. |
| constexpr unsigned char kMarkerByte = kChunkSize + 1; |
| constexpr unsigned char kEmptyBinarySentinel = 0; |
| constexpr size_t kChunkSizeWithMarker = kChunkSize + sizeof(kMarkerByte); |
| // Used to stuff the last chunk after running out of payload bytes. This needs |
| // to be zero so that a shorter string that's a prefix of a longer string sorts |
| // before the longer string. |
| constexpr char kPaddingByte = 0x0; |
| |
| // Encodes the binary data in `value` and appends it to `into`. The |
| // encoding maintains sorting order. The bytes are copied without |
| // modification, but before every 8 bytes a marker byte (0x08) is |
| // inserted. When there is no more data to append, padding bytes are added |
| // to fill the 8 byte chunk, and a sentinel is inserted, which is between |
| // 0 and 0x07. The value of the sentinel indicates how many bytes in the last |
| // chunk are payload (non-padding).This encoding will increase the size of the |
| // encoded data by 1 byte for every 8 bytes, as well as an additional byte for |
| // the sentinel and up to 7 padding bytes, so for large data the size increase |
| // is ~12.5%. This is preferred over a variable length encoding because, unlike |
| // string keys, we assume a fairly even distribution of frequencies for |
| // bytes between 0 and 0xff, and therefore a variable length encoding will |
| // waste space as often (or more) than it saves space. |
| void EncodeBinaryWithSentinel(const std::string& value, std::string* into) { |
| if (value.empty()) { |
| into->push_back(kEmptyBinarySentinel); |
| return; |
| } |
| |
| size_t length = value.length(); |
| int num_chunks = length / kChunkSize + !!(length % kChunkSize); |
| into->reserve(into->size() + kChunkSizeWithMarker * num_chunks + |
| kSentinelLength); |
| |
| for (size_t i = 0; i < value.length(); i += kChunkSize) { |
| into->push_back(kMarkerByte); |
| for (size_t j = 0; j < kChunkSize; ++j) { |
| size_t idx = i + j; |
| into->push_back(idx < value.length() ? value[idx] : kPaddingByte); |
| } |
| } |
| |
| // Sentinel. |
| int non_padding = length % kChunkSize; |
| into->push_back(non_padding == 0 ? kChunkSize : non_padding); |
| } |
| |
| // Reads and consumes the first bytes of `encoded` and outputs decoded binary as |
| // string. Any non-null return value, including the empty string, indicates |
| // success. A nullopt return value indicates failure. |
| std::optional<std::string> DecodeBinaryWithSentinel(std::string_view& encoded) { |
| if (!encoded.empty() && encoded.front() == kEmptyBinarySentinel) { |
| encoded.remove_prefix(1); |
| return std::string(); |
| } |
| |
| std::string output; |
| |
| while (!encoded.empty()) { |
| const unsigned char marker_or_sentinel = encoded.front(); |
| if (marker_or_sentinel == kMarkerByte) { |
| if (encoded.size() < kChunkSizeWithMarker) { |
| return std::nullopt; |
| } |
| encoded.remove_prefix(1); |
| |
| for (size_t i = 0; i < kChunkSize; ++i) { |
| output.push_back(encoded.at(i)); |
| } |
| encoded.remove_prefix(kChunkSize); |
| } else if (marker_or_sentinel >= 1 && marker_or_sentinel <= kChunkSize) { |
| if (marker_or_sentinel > static_cast<int64_t>(output.size())) { |
| return std::nullopt; |
| } |
| const int num_padding_bytes = kChunkSize - marker_or_sentinel; |
| for (int i = 0; i < num_padding_bytes; ++i) { |
| if (output.back() != kPaddingByte) { |
| return std::nullopt; |
| } |
| output.pop_back(); |
| } |
| |
| encoded.remove_prefix(1); |
| return output; |
| } else { |
| return std::nullopt; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| void EncodeSortableDouble(double value, std::string* into) { |
| CHECK(!std::isnan(value)); |
| |
| uint64_t double_bits = 0; |
| base::byte_span_from_ref(double_bits) |
| .copy_from_nonoverlapping( |
| base::byte_span_from_ref(base::allow_nonunique_obj, value)); |
| |
| // When interpreted as plain bits, negative doubles will sort in reverse, so |
| // invert the bits. For positive doubles we only have to invert the sign bit |
| // so they sort higher than the negatives. The one exception to this is -0, |
| // which should be normalized to positive zero. This aligns with this spec |
| // proposal: https://github.com/w3c/IndexedDB/pull/386 |
| // TODO(estade): revisit if this spec PR is not accepted. |
| uint64_t modified_bits = 0; |
| if (std::signbit(value) && value != -0.0) { |
| modified_bits = double_bits ^ std::numeric_limits<uint64_t>::max(); |
| } else { |
| static constexpr uint64_t kSignBit = base::bits::LeftmostBit<uint64_t>(); |
| modified_bits = kSignBit | double_bits; |
| } |
| |
| std::array<uint8_t, 8u> chars; |
| base::span(chars).copy_from_nonoverlapping( |
| base::U64ToBigEndian(modified_bits)); |
| into->insert(into->end(), chars.begin(), chars.end()); |
| } |
| |
| // Reads and consumes the first 8 bytes of `encoded` and outputs decoded double |
| // as `output`. Returns true on success. |
| bool DecodeSortableDouble(std::string_view& data, double* output) { |
| constexpr size_t kLengthInBytes = sizeof(double); |
| if (data.size() < kLengthInBytes) { |
| return false; |
| } |
| |
| uint64_t host_bits = |
| base::U64FromBigEndian(base::as_byte_span(data).first<kLengthInBytes>()); |
| data = data.substr(kLengthInBytes); |
| |
| static constexpr uint64_t kSignBit = base::bits::LeftmostBit<uint64_t>(); |
| if (host_bits & kSignBit) { |
| host_bits = host_bits ^ kSignBit; |
| } else { |
| host_bits = host_bits ^ std::numeric_limits<uint64_t>::max(); |
| } |
| |
| base::byte_span_from_ref(base::allow_nonunique_obj, *output) |
| .copy_from_nonoverlapping(base::byte_span_from_ref(host_bits)); |
| return true; |
| } |
| |
| // Decodes bytes of type `value_type` starting at `data`. Returns an invalid key |
| // on failure. |
| IndexedDBKey DecodeSortableKeyNonArray(char value_type, |
| std::string_view& data) { |
| switch (value_type) { |
| case kOrderedBinaryTypeByte: { |
| std::optional<std::string> binary = DecodeBinaryWithSentinel(data); |
| if (binary.has_value()) { |
| return IndexedDBKey(*std::move(binary)); |
| } |
| return InvalidKey(); |
| } |
| |
| case kOrderedStringTypeByte: { |
| std::u16string string_bytes; |
| if (DecodeStringWithSentinel(data, &string_bytes)) { |
| return IndexedDBKey(std::move(string_bytes)); |
| } |
| return InvalidKey(); |
| } |
| |
| case kOrderedDateTypeByte: { |
| double date; |
| if (DecodeSortableDouble(data, &date)) { |
| return IndexedDBKey(date, blink::mojom::IDBKeyType::Date); |
| } |
| return InvalidKey(); |
| } |
| |
| case kOrderedNumberTypeByte: { |
| double number; |
| if (DecodeSortableDouble(data, &number)) { |
| return IndexedDBKey(number, blink::mojom::IDBKeyType::Number); |
| } |
| return InvalidKey(); |
| } |
| |
| case kOrderedArrayTypeByte: |
| case kSentinel: |
| default: |
| return InvalidKey(); |
| } |
| } |
| |
| } // namespace |
| |
| std::string MaxIDBKey() { |
| std::string ret; |
| EncodeByte(kIndexedDBKeyNullTypeByte, &ret); |
| return ret; |
| } |
| |
| std::string MinIDBKey() { |
| std::string ret; |
| EncodeByte(kIndexedDBKeyMinKeyTypeByte, &ret); |
| return ret; |
| } |
| |
| void EncodeByte(unsigned char value, std::string* into) { |
| into->push_back(value); |
| } |
| |
| void EncodeBool(bool value, std::string* into) { |
| into->push_back(value ? 1 : 0); |
| } |
| |
| void EncodeInt(int64_t value, std::string* into) { |
| #ifndef NDEBUG |
| // Exercised by unit tests in debug only. |
| DCHECK_GE(value, 0); |
| #endif |
| uint64_t n = static_cast<uint64_t>(value); |
| |
| do { |
| unsigned char c = n; |
| into->push_back(c); |
| n >>= 8; |
| } while (n); |
| } |
| |
| void EncodeString(const std::u16string& value, std::string* into) { |
| if (value.empty()) |
| return; |
| |
| // Backing store is UTF-16BE, convert from host endianness. |
| into->reserve(into->size() + value.length() * 2); |
| for (char16_t c : value) { |
| into->push_back(static_cast<char>(c >> 8)); |
| into->push_back(static_cast<char>(c)); |
| } |
| } |
| |
| void EncodeBinary(const std::string& value, std::string* into) { |
| EncodeVarInt(value.length(), into); |
| into->append(value); |
| DCHECK_GE(into->size(), value.size()); |
| } |
| |
| void EncodeBinary(base::span<const uint8_t> value, std::string* into) { |
| EncodeVarInt(value.size(), into); |
| into->append(base::as_string_view(value)); |
| DCHECK_GE(into->size(), value.size()); |
| } |
| |
| void EncodeStringWithLength(const std::u16string& value, std::string* into) { |
| EncodeVarInt(value.length(), into); |
| EncodeString(value, into); |
| } |
| |
| void EncodeDouble(double value, std::string* into) { |
| // This always has host endianness. |
| into->append(base::as_string_view( |
| base::byte_span_from_ref(base::allow_nonunique_obj, value))); |
| } |
| |
| // Return value is true iff successful. |
| [[nodiscard]] bool EncodeIDBKeyRecursively(const IndexedDBKey& value, |
| std::string* into, |
| size_t recursion_level) { |
| // The recursion level is enforced in the renderer (in V8). If this check |
| // fails, it suggests a compromised renderer. |
| if (recursion_level > IndexedDBKey::kMaximumDepth) { |
| return false; |
| } |
| |
| size_t previous_size = into->size(); |
| switch (value.type()) { |
| case blink::mojom::IDBKeyType::Array: { |
| EncodeByte(kIndexedDBKeyArrayTypeByte, into); |
| size_t length = value.array().size(); |
| EncodeVarInt(length, into); |
| for (size_t i = 0; i < length; ++i) { |
| if (!EncodeIDBKeyRecursively(value.array()[i], into, |
| 1 + recursion_level)) { |
| return false; |
| } |
| } |
| DCHECK_GT(into->size(), previous_size); |
| return true; |
| } |
| case blink::mojom::IDBKeyType::Binary: |
| EncodeByte(kIndexedDBKeyBinaryTypeByte, into); |
| EncodeBinary(value.binary(), into); |
| DCHECK_GT(into->size(), previous_size); |
| return true; |
| case blink::mojom::IDBKeyType::String: |
| EncodeByte(kIndexedDBKeyStringTypeByte, into); |
| EncodeStringWithLength(value.string(), into); |
| DCHECK_GT(into->size(), previous_size); |
| return true; |
| case blink::mojom::IDBKeyType::Date: |
| EncodeByte(kIndexedDBKeyDateTypeByte, into); |
| EncodeDouble(value.date(), into); |
| DCHECK_EQ(9u, static_cast<size_t>(into->size() - previous_size)); |
| return true; |
| case blink::mojom::IDBKeyType::Number: |
| EncodeByte(kIndexedDBKeyNumberTypeByte, into); |
| EncodeDouble(value.number(), into); |
| DCHECK_EQ(9u, static_cast<size_t>(into->size() - previous_size)); |
| return true; |
| case blink::mojom::IDBKeyType::None: |
| case blink::mojom::IDBKeyType::Invalid: |
| case blink::mojom::IDBKeyType::Min: |
| default: |
| return false; |
| } |
| } |
| |
| // This function must be a thin wrapper around `MaybeEncodeIDBKey` to ensure |
| // comprehensive test coverage. |
| void EncodeIDBKey(const IndexedDBKey& value, std::string* into) { |
| CHECK(MaybeEncodeIDBKey(value, into)); |
| } |
| |
| bool MaybeEncodeIDBKey(const IndexedDBKey& value, std::string* into) { |
| return EncodeIDBKeyRecursively(value, into, 0); |
| } |
| |
| std::string EncodeSortableIDBKey(const IndexedDBKey& value) { |
| CHECK(value.IsValid()); |
| std::string into; |
| |
| std::list<const IndexedDBKey*> keys; |
| keys.push_back(&value); |
| |
| while (!keys.empty()) { |
| const IndexedDBKey* key = keys.back(); |
| keys.pop_back(); |
| |
| if (!key) { |
| // This value pushed by an Array case (see below). |
| EncodeByte(kSentinel, &into); |
| continue; |
| } |
| |
| switch (key->type()) { |
| case blink::mojom::IDBKeyType::Array: { |
| EncodeByte(kOrderedArrayTypeByte, &into); |
| |
| // Used to indicate that a sentinel should be inserted later. |
| keys.push_back(nullptr); |
| for (const IndexedDBKey& subkey : base::Reversed(key->array())) { |
| keys.push_back(&subkey); |
| } |
| |
| continue; |
| } |
| case blink::mojom::IDBKeyType::Binary: |
| EncodeByte(kOrderedBinaryTypeByte, &into); |
| EncodeBinaryWithSentinel(key->binary(), &into); |
| continue; |
| case blink::mojom::IDBKeyType::String: |
| EncodeByte(kOrderedStringTypeByte, &into); |
| EncodeStringWithSentinel(key->string(), &into); |
| continue; |
| case blink::mojom::IDBKeyType::Date: |
| EncodeByte(kOrderedDateTypeByte, &into); |
| EncodeSortableDouble(key->date(), &into); |
| continue; |
| case blink::mojom::IDBKeyType::Number: |
| EncodeByte(kOrderedNumberTypeByte, &into); |
| EncodeSortableDouble(key->number(), &into); |
| continue; |
| case blink::mojom::IDBKeyType::None: |
| case blink::mojom::IDBKeyType::Invalid: |
| case blink::mojom::IDBKeyType::Min: |
| NOTREACHED(); |
| } |
| } |
| |
| return into; |
| } |
| |
| #define COMPILE_ASSERT_MATCHING_VALUES(a, b) \ |
| static_assert( \ |
| static_cast<unsigned char>(a) == static_cast<unsigned char>(b), \ |
| "Blink enum and coding byte must match.") |
| |
| COMPILE_ASSERT_MATCHING_VALUES(blink::mojom::IDBKeyPathType::Null, |
| kIndexedDBKeyPathNullTypeByte); |
| COMPILE_ASSERT_MATCHING_VALUES(blink::mojom::IDBKeyPathType::String, |
| kIndexedDBKeyPathStringTypeByte); |
| COMPILE_ASSERT_MATCHING_VALUES(blink::mojom::IDBKeyPathType::Array, |
| kIndexedDBKeyPathArrayTypeByte); |
| #undef COMPILE_ASSERT_MATCHING_VALUES |
| |
| void EncodeIDBKeyPath(const IndexedDBKeyPath& value, std::string* into) { |
| // May be typed, or may be a raw string. An invalid leading |
| // byte is used to identify typed coding. New records are |
| // always written as typed. |
| EncodeByte(kIndexedDBKeyPathTypeCodedByte1, into); |
| EncodeByte(kIndexedDBKeyPathTypeCodedByte2, into); |
| EncodeByte(static_cast<char>(value.type()), into); |
| switch (value.type()) { |
| case blink::mojom::IDBKeyPathType::Null: |
| break; |
| case blink::mojom::IDBKeyPathType::String: { |
| EncodeStringWithLength(value.string(), into); |
| break; |
| } |
| case blink::mojom::IDBKeyPathType::Array: { |
| const std::vector<std::u16string>& array = value.array(); |
| size_t count = array.size(); |
| EncodeVarInt(count, into); |
| for (size_t i = 0; i < count; ++i) { |
| EncodeStringWithLength(array[i], into); |
| } |
| break; |
| } |
| } |
| } |
| |
| void EncodeBlobJournal(const BlobJournalType& journal, std::string* into) { |
| for (const auto& iter : journal) { |
| EncodeVarInt(iter.first, into); |
| EncodeVarInt(iter.second, into); |
| } |
| } |
| |
| bool DecodeByte(std::string_view* slice, unsigned char* value) { |
| if (slice->empty()) |
| return false; |
| |
| *value = (*slice)[0]; |
| slice->remove_prefix(1); |
| return true; |
| } |
| |
| bool DecodeBool(std::string_view* slice, bool* value) { |
| if (slice->empty()) |
| return false; |
| |
| *value = !!(*slice)[0]; |
| slice->remove_prefix(1); |
| return true; |
| } |
| |
| bool DecodeInt(std::string_view* slice, int64_t* value) { |
| if (slice->empty()) |
| return false; |
| |
| std::string_view::const_iterator it = slice->begin(); |
| int shift = 0; |
| int64_t ret = 0; |
| while (it != slice->end()) { |
| unsigned char c = *it++; |
| ret |= static_cast<int64_t>(c) << shift; |
| shift += 8; |
| } |
| *value = ret; |
| slice->remove_prefix(it - slice->begin()); |
| return true; |
| } |
| |
| bool DecodeString(std::string_view* slice, std::u16string* value) { |
| if (slice->empty()) { |
| value->clear(); |
| return true; |
| } |
| |
| // Backing store is UTF-16BE, convert to host endianness. |
| DCHECK(!(slice->size() % sizeof(char16_t))); |
| size_t length = slice->size() / sizeof(char16_t); |
| std::u16string decoded; |
| decoded.reserve(length); |
| for (size_t i = 0; i < length; ++i) { |
| uint8_t hi = static_cast<uint8_t>((*slice)[2 * i]); |
| uint8_t lo = static_cast<uint8_t>((*slice)[2 * i + 1]); |
| decoded.push_back((char16_t{hi} << 8) | char16_t{lo}); |
| } |
| |
| *value = decoded; |
| slice->remove_prefix(length * sizeof(char16_t)); |
| return true; |
| } |
| |
| bool DecodeStringWithLength(std::string_view* slice, std::u16string* value) { |
| if (slice->empty()) |
| return false; |
| |
| int64_t length = 0; |
| size_t bytes; |
| if (!DecodeVarInt(slice, &length) || |
| !base::CheckMul(length, sizeof(char16_t)).AssignIfValid(&bytes)) { |
| return false; |
| } |
| if (slice->size() < bytes) |
| return false; |
| |
| std::string_view subpiece = slice->substr(0, bytes); |
| slice->remove_prefix(bytes); |
| if (!DecodeString(&subpiece, value)) |
| return false; |
| |
| return true; |
| } |
| |
| bool DecodeBinary(std::string_view* slice, std::string* value) { |
| if (slice->empty()) |
| return false; |
| |
| int64_t length = 0; |
| size_t size; |
| if (!DecodeVarInt(slice, &length) || |
| !base::MakeCheckedNum(length).AssignIfValid(&size)) { |
| return false; |
| } |
| |
| if (slice->size() < size) { |
| return false; |
| } |
| |
| value->assign(slice->data(), size); |
| slice->remove_prefix(size); |
| return true; |
| } |
| |
| bool DecodeBinary(std::string_view* slice, base::span<const uint8_t>* value) { |
| if (slice->empty()) |
| return false; |
| |
| int64_t length = 0; |
| size_t size; |
| if (!DecodeVarInt(slice, &length) || |
| !base::MakeCheckedNum(length).AssignIfValid(&size)) { |
| return false; |
| } |
| |
| if (slice->size() < size) |
| return false; |
| |
| *value = base::as_byte_span(*slice).first(size); |
| slice->remove_prefix(size); |
| return true; |
| } |
| |
| IndexedDBKey DecodeIDBKeyRecursive(std::string_view* slice, size_t recursion) { |
| if (slice->empty()) |
| return InvalidKey(); |
| |
| if (recursion > IndexedDBKey::kMaximumDepth) |
| return InvalidKey(); |
| |
| unsigned char type = (*slice)[0]; |
| slice->remove_prefix(1); |
| |
| switch (type) { |
| case kIndexedDBKeyNullTypeByte: |
| return InvalidKey(); |
| |
| case kIndexedDBKeyArrayTypeByte: { |
| int64_t length = 0; |
| if (!DecodeVarInt(slice, &length) || length < 0) |
| return {}; |
| IndexedDBKey::KeyArray array; |
| while (length--) { |
| if (IndexedDBKey key = DecodeIDBKeyRecursive(slice, recursion + 1); |
| key.IsValid()) { |
| array.push_back(std::move(key)); |
| } else { |
| return InvalidKey(); |
| } |
| } |
| return IndexedDBKey(std::move(array)); |
| } |
| case kIndexedDBKeyBinaryTypeByte: { |
| std::string binary; |
| if (DecodeBinary(slice, &binary)) { |
| return IndexedDBKey(std::move(binary)); |
| } |
| return InvalidKey(); |
| } |
| case kIndexedDBKeyStringTypeByte: { |
| std::u16string s; |
| if (DecodeStringWithLength(slice, &s)) { |
| return IndexedDBKey(std::move(s)); |
| } |
| return InvalidKey(); |
| } |
| case kIndexedDBKeyDateTypeByte: { |
| double d; |
| if (DecodeDouble(slice, &d)) { |
| return IndexedDBKey(d, blink::mojom::IDBKeyType::Date); |
| } |
| return InvalidKey(); |
| } |
| case kIndexedDBKeyNumberTypeByte: { |
| double d; |
| if (DecodeDouble(slice, &d)) { |
| return IndexedDBKey(d, blink::mojom::IDBKeyType::Number); |
| } |
| return InvalidKey(); |
| } |
| case kIndexedDBKeyMinKeyTypeByte: { |
| return InvalidKey(); |
| } |
| } |
| |
| return InvalidKey(); |
| } |
| |
| IndexedDBKey DecodeIDBKey(std::string_view* slice) { |
| return DecodeIDBKeyRecursive(slice, 0); |
| } |
| |
| IndexedDBKey DecodeSortableIDBKey(std::string_view serialized) { |
| if (serialized.empty()) { |
| return InvalidKey(); |
| } |
| |
| std::string_view data = serialized; |
| IndexedDBKey value; |
| IndexedDBKey* into = &value; |
| std::list<std::vector<IndexedDBKey>> key_arrays; |
| while (!data.empty()) { |
| char value_type = data.front(); |
| data = data.substr(1); |
| switch (value_type) { |
| case kOrderedArrayTypeByte: |
| key_arrays.emplace_back(); |
| continue; |
| |
| case kOrderedBinaryTypeByte: |
| case kOrderedStringTypeByte: |
| case kOrderedDateTypeByte: |
| case kOrderedNumberTypeByte: |
| if (!key_arrays.empty()) { |
| key_arrays.back().emplace_back(); |
| into = &key_arrays.back().back(); |
| } else if (into != &value) { |
| return InvalidKey(); |
| } |
| *into = DecodeSortableKeyNonArray(value_type, data); |
| if (!into->IsValid()) { |
| return InvalidKey(); |
| } |
| continue; |
| |
| case kSentinel: { |
| if (key_arrays.empty()) { |
| return InvalidKey(); |
| } |
| IndexedDBKey keys(std::move(key_arrays.back())); |
| key_arrays.pop_back(); |
| if (key_arrays.empty()) { |
| value = std::move(keys); |
| break; |
| } |
| |
| key_arrays.back().emplace_back(std::move(keys)); |
| continue; |
| } |
| |
| default: |
| return InvalidKey(); |
| } |
| } |
| if (!data.empty()) { |
| return InvalidKey(); |
| } |
| return value; |
| } |
| |
| bool DecodeDouble(std::string_view* slice, double* value) { |
| constexpr size_t size = sizeof(*value); |
| if (slice->size() < size) { |
| return false; |
| } |
| |
| base::byte_span_from_ref(base::allow_nonunique_obj, *value) |
| .copy_from(base::as_byte_span(*slice).first<size>()); |
| slice->remove_prefix(size); |
| return true; |
| } |
| |
| bool DecodeIDBKeyPath(std::string_view* slice, IndexedDBKeyPath* value) { |
| // May be typed, or may be a raw string. An invalid leading |
| // byte sequence is used to identify typed coding. New records are |
| // always written as typed. |
| if (slice->size() < 3 || (*slice)[0] != kIndexedDBKeyPathTypeCodedByte1 || |
| (*slice)[1] != kIndexedDBKeyPathTypeCodedByte2) { |
| std::u16string s; |
| if (!DecodeString(slice, &s)) |
| return false; |
| *value = IndexedDBKeyPath(s); |
| return true; |
| } |
| |
| slice->remove_prefix(2); |
| DCHECK(!slice->empty()); |
| blink::mojom::IDBKeyPathType type = |
| static_cast<blink::mojom::IDBKeyPathType>((*slice)[0]); |
| slice->remove_prefix(1); |
| |
| switch (type) { |
| case blink::mojom::IDBKeyPathType::Null: |
| if (!slice->empty()) { |
| return false; |
| } |
| *value = IndexedDBKeyPath(); |
| return true; |
| case blink::mojom::IDBKeyPathType::String: { |
| std::u16string string; |
| if (!DecodeStringWithLength(slice, &string) || !slice->empty()) { |
| return false; |
| } |
| *value = IndexedDBKeyPath(string); |
| return true; |
| } |
| case blink::mojom::IDBKeyPathType::Array: { |
| std::vector<std::u16string> array; |
| int64_t count; |
| if (!DecodeVarInt(slice, &count) || count < 0) |
| return false; |
| while (count--) { |
| std::u16string string; |
| if (!DecodeStringWithLength(slice, &string)) |
| return false; |
| array.push_back(string); |
| } |
| if (!slice->empty()) { |
| return false; |
| } |
| *value = IndexedDBKeyPath(array); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DecodeBlobJournal(std::string_view* slice, BlobJournalType* journal) { |
| BlobJournalType output; |
| while (!slice->empty()) { |
| int64_t database_id = -1; |
| int64_t blob_number = -1; |
| if (!DecodeVarInt(slice, &database_id)) |
| return false; |
| if (!KeyPrefix::IsValidDatabaseId(database_id)) |
| return false; |
| if (!DecodeVarInt(slice, &blob_number)) |
| return false; |
| if (!DatabaseMetaDataKey::IsValidBlobNumber(blob_number) && |
| (blob_number != DatabaseMetaDataKey::kAllBlobsNumber)) { |
| return false; |
| } |
| output.push_back({database_id, blob_number}); |
| } |
| journal->swap(output); |
| return true; |
| } |
| |
| bool ConsumeEncodedIDBKey(std::string_view* slice) { |
| unsigned char type = (*slice)[0]; |
| slice->remove_prefix(1); |
| |
| switch (type) { |
| case kIndexedDBKeyNullTypeByte: |
| case kIndexedDBKeyMinKeyTypeByte: |
| return true; |
| case kIndexedDBKeyArrayTypeByte: { |
| int64_t length; |
| if (!DecodeVarInt(slice, &length) || length < 0) |
| return false; |
| while (length--) { |
| if (!ConsumeEncodedIDBKey(slice)) |
| return false; |
| } |
| return true; |
| } |
| case kIndexedDBKeyBinaryTypeByte: { |
| int64_t length = 0; |
| if (!DecodeVarInt(slice, &length) || length < 0) |
| return false; |
| if (slice->size() < static_cast<size_t>(length)) |
| return false; |
| slice->remove_prefix(length); |
| return true; |
| } |
| case kIndexedDBKeyStringTypeByte: { |
| int64_t length = 0; |
| if (!DecodeVarInt(slice, &length) || length < 0) |
| return false; |
| if (slice->size() < static_cast<size_t>(length) * sizeof(char16_t)) |
| return false; |
| slice->remove_prefix(length * sizeof(char16_t)); |
| return true; |
| } |
| case kIndexedDBKeyDateTypeByte: |
| case kIndexedDBKeyNumberTypeByte: |
| if (slice->size() < sizeof(double)) |
| return false; |
| slice->remove_prefix(sizeof(double)); |
| return true; |
| } |
| NOTREACHED(); |
| } |
| |
| bool ExtractEncodedIDBKey(std::string_view* slice, std::string* result) { |
| const char* start = slice->data(); |
| if (!ConsumeEncodedIDBKey(slice)) |
| return false; |
| |
| if (result) |
| result->assign(start, slice->data()); |
| return true; |
| } |
| |
| static blink::mojom::IDBKeyType KeyTypeByteToKeyType(unsigned char type) { |
| switch (type) { |
| case kIndexedDBKeyNullTypeByte: |
| return blink::mojom::IDBKeyType::Invalid; |
| case kIndexedDBKeyArrayTypeByte: |
| return blink::mojom::IDBKeyType::Array; |
| case kIndexedDBKeyBinaryTypeByte: |
| return blink::mojom::IDBKeyType::Binary; |
| case kIndexedDBKeyStringTypeByte: |
| return blink::mojom::IDBKeyType::String; |
| case kIndexedDBKeyDateTypeByte: |
| return blink::mojom::IDBKeyType::Date; |
| case kIndexedDBKeyNumberTypeByte: |
| return blink::mojom::IDBKeyType::Number; |
| case kIndexedDBKeyMinKeyTypeByte: |
| return blink::mojom::IDBKeyType::Min; |
| } |
| |
| DUMP_WILL_BE_NOTREACHED() << "Got invalid type " << type; |
| return blink::mojom::IDBKeyType::Invalid; |
| } |
| |
| int CompareEncodedStringsWithLength(std::string_view* slice1, |
| std::string_view* slice2, |
| bool* ok) { |
| int64_t len1, len2; |
| if (!DecodeVarInt(slice1, &len1) || !DecodeVarInt(slice2, &len2)) { |
| *ok = false; |
| return 0; |
| } |
| |
| size_t size1, size2; |
| if (!base::CheckMul(len1, sizeof(char16_t)).AssignIfValid(&size1) || |
| !base::CheckMul(len2, sizeof(char16_t)).AssignIfValid(&size2)) { |
| *ok = false; |
| return 0; |
| } |
| |
| if (slice1->size() < size1 || slice2->size() < size2) { |
| *ok = false; |
| return 0; |
| } |
| |
| // Extract the string data, and advance the passed slices. |
| std::string_view string1(slice1->data(), size1); |
| std::string_view string2(slice2->data(), size2); |
| slice1->remove_prefix(size1); |
| slice2->remove_prefix(size2); |
| |
| *ok = true; |
| // Strings are UTF-16BE encoded, so a simple memcmp is sufficient. |
| return string1.compare(string2); |
| } |
| |
| int CompareEncodedBinary(std::string_view* slice1, |
| std::string_view* slice2, |
| bool* ok) { |
| int64_t len1, len2; |
| if (!DecodeVarInt(slice1, &len1) || !DecodeVarInt(slice2, &len2)) { |
| *ok = false; |
| return 0; |
| } |
| |
| size_t size1, size2; |
| if (!base::MakeCheckedNum(len1).AssignIfValid(&size1) || |
| !base::MakeCheckedNum(len2).AssignIfValid(&size2)) { |
| *ok = false; |
| return 0; |
| } |
| |
| if (slice1->size() < size1 || slice2->size() < size2) { |
| *ok = false; |
| return 0; |
| } |
| |
| // Extract the binary data, and advance the passed slices. |
| std::string_view binary1(slice1->data(), size1); |
| std::string_view binary2(slice2->data(), size2); |
| slice1->remove_prefix(size1); |
| slice2->remove_prefix(size2); |
| |
| *ok = true; |
| // This is the same as a memcmp() |
| return binary1.compare(binary2); |
| } |
| |
| static int CompareInts(int64_t a, int64_t b) { |
| #ifndef NDEBUG |
| // Exercised by unit tests in debug only. |
| DCHECK_GE(a, 0); |
| DCHECK_GE(b, 0); |
| #endif |
| int64_t diff = a - b; |
| if (diff < 0) |
| return -1; |
| if (diff > 0) |
| return 1; |
| return 0; |
| } |
| |
| static inline int CompareSizes(size_t a, size_t b) { |
| if (a > b) |
| return 1; |
| if (b > a) |
| return -1; |
| return 0; |
| } |
| |
| static int CompareTypes(blink::mojom::IDBKeyType a, |
| blink::mojom::IDBKeyType b) { |
| return static_cast<int32_t>(b) - static_cast<int32_t>(a); |
| } |
| |
| int CompareEncodedIDBKeys(std::string_view* slice_a, |
| std::string_view* slice_b, |
| bool* ok) { |
| DCHECK(!slice_a->empty()); |
| DCHECK(!slice_b->empty()); |
| *ok = true; |
| unsigned char type_a = (*slice_a)[0]; |
| unsigned char type_b = (*slice_b)[0]; |
| slice_a->remove_prefix(1); |
| slice_b->remove_prefix(1); |
| |
| if (int x = CompareTypes(KeyTypeByteToKeyType(type_a), |
| KeyTypeByteToKeyType(type_b))) |
| return x; |
| |
| switch (type_a) { |
| case kIndexedDBKeyNullTypeByte: |
| case kIndexedDBKeyMinKeyTypeByte: |
| // Null type or max type; no payload to compare. |
| return 0; |
| case kIndexedDBKeyArrayTypeByte: { |
| int64_t length_a, length_b; |
| if (!DecodeVarInt(slice_a, &length_a) || |
| !DecodeVarInt(slice_b, &length_b)) { |
| *ok = false; |
| return 0; |
| } |
| for (int64_t i = 0; i < length_a && i < length_b; ++i) { |
| int result = CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| if (!*ok || result) |
| return result; |
| } |
| return length_a - length_b; |
| } |
| case kIndexedDBKeyBinaryTypeByte: |
| return CompareEncodedBinary(slice_a, slice_b, ok); |
| case kIndexedDBKeyStringTypeByte: |
| return CompareEncodedStringsWithLength(slice_a, slice_b, ok); |
| case kIndexedDBKeyDateTypeByte: |
| case kIndexedDBKeyNumberTypeByte: { |
| double d, e; |
| if (!DecodeDouble(slice_a, &d) || !DecodeDouble(slice_b, &e)) { |
| *ok = false; |
| return 0; |
| } |
| if (d < e) |
| return -1; |
| if (d > e) |
| return 1; |
| return 0; |
| } |
| } |
| |
| NOTREACHED(); |
| } |
| |
| namespace { |
| |
| template <typename KeyType> |
| int Compare(std::string_view a, |
| std::string_view b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| KeyType key_a; |
| KeyType key_b; |
| |
| std::string_view slice_a(a); |
| if (!KeyType::Decode(&slice_a, &key_a)) { |
| *ok = false; |
| return 0; |
| } |
| std::string_view slice_b(b); |
| if (!KeyType::Decode(&slice_b, &key_b)) { |
| *ok = false; |
| return 0; |
| } |
| |
| *ok = true; |
| return key_a.Compare(key_b); |
| } |
| |
| template <typename KeyType> |
| int CompareSuffix(std::string_view* a, |
| std::string_view* b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| NOTREACHED(); |
| } |
| |
| template <> |
| int CompareSuffix<ExistsEntryKey>(std::string_view* slice_a, |
| std::string_view* slice_b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| DCHECK(!slice_a->empty()); |
| DCHECK(!slice_b->empty()); |
| return CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| } |
| |
| template <> |
| int CompareSuffix<ObjectStoreDataKey>(std::string_view* slice_a, |
| std::string_view* slice_b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| return CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| } |
| |
| template <> |
| int CompareSuffix<BlobEntryKey>(std::string_view* slice_a, |
| std::string_view* slice_b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| return CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| } |
| |
| template <> |
| int CompareSuffix<IndexDataKey>(std::string_view* slice_a, |
| std::string_view* slice_b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| // index key |
| int result = CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| if (!*ok || result) |
| return result; |
| if (only_compare_index_keys) |
| return 0; |
| |
| // sequence number [optional] |
| int64_t sequence_number_a = -1; |
| int64_t sequence_number_b = -1; |
| if (!slice_a->empty() && !DecodeVarInt(slice_a, &sequence_number_a)) |
| return 0; |
| if (!slice_b->empty() && !DecodeVarInt(slice_b, &sequence_number_b)) |
| return 0; |
| |
| if (slice_a->empty() || slice_b->empty()) |
| return CompareSizes(slice_a->size(), slice_b->size()); |
| |
| // primary key [optional] |
| result = CompareEncodedIDBKeys(slice_a, slice_b, ok); |
| if (!*ok || result) |
| return result; |
| |
| return CompareInts(sequence_number_a, sequence_number_b); |
| } |
| |
| int Compare(std::string_view a, |
| std::string_view b, |
| bool only_compare_index_keys, |
| bool* ok) { |
| std::string_view slice_a(a); |
| std::string_view slice_b(b); |
| KeyPrefix prefix_a; |
| KeyPrefix prefix_b; |
| bool ok_a = KeyPrefix::Decode(&slice_a, &prefix_a); |
| bool ok_b = KeyPrefix::Decode(&slice_b, &prefix_b); |
| if (!ok_a || !ok_b) { |
| *ok = false; |
| return 0; |
| } |
| |
| *ok = true; |
| if (int x = prefix_a.Compare(prefix_b)) |
| return x; |
| |
| switch (prefix_a.type()) { |
| case KeyPrefix::GLOBAL_METADATA: { |
| DCHECK(!slice_a.empty()); |
| DCHECK(!slice_b.empty()); |
| |
| unsigned char type_byte_a; |
| if (!DecodeByte(&slice_a, &type_byte_a)) { |
| *ok = false; |
| return 0; |
| } |
| |
| unsigned char type_byte_b; |
| if (!DecodeByte(&slice_b, &type_byte_b)) { |
| *ok = false; |
| return 0; |
| } |
| |
| if (int x = type_byte_a - type_byte_b) |
| return x; |
| if (type_byte_a < kMaxSimpleGlobalMetaDataTypeByte) |
| return 0; |
| |
| if (type_byte_a == kScopesPrefixByte) |
| return slice_a.compare(slice_b); |
| |
| // Compare<> is used (which re-decodes the prefix) rather than an |
| // specialized CompareSuffix<> because metadata is relatively uncommon |
| // in the database. |
| |
| if (type_byte_a == kDatabaseFreeListTypeByte) { |
| // TODO(jsbell): No need to pass only_compare_index_keys through here. |
| return Compare<DatabaseFreeListKey>(a, b, only_compare_index_keys, ok); |
| } |
| if (type_byte_a == kDatabaseNameTypeByte) { |
| return Compare<DatabaseNameKey>(a, b, /*only_compare_index_keys*/ false, |
| ok); |
| } |
| break; |
| } |
| |
| case KeyPrefix::DATABASE_METADATA: { |
| DCHECK(!slice_a.empty()); |
| DCHECK(!slice_b.empty()); |
| |
| unsigned char type_byte_a; |
| if (!DecodeByte(&slice_a, &type_byte_a)) { |
| *ok = false; |
| return 0; |
| } |
| |
| unsigned char type_byte_b; |
| if (!DecodeByte(&slice_b, &type_byte_b)) { |
| *ok = false; |
| return 0; |
| } |
| |
| if (int x = type_byte_a - type_byte_b) |
| return x; |
| if (type_byte_a < DatabaseMetaDataKey::MAX_SIMPLE_METADATA_TYPE) |
| return 0; |
| |
| // Compare<> is used (which re-decodes the prefix) rather than an |
| // specialized CompareSuffix<> because metadata is relatively uncommon |
| // in the database. |
| |
| if (type_byte_a == kObjectStoreMetaDataTypeByte) { |
| // TODO(jsbell): No need to pass only_compare_index_keys through here. |
| return Compare<ObjectStoreMetaDataKey>(a, b, only_compare_index_keys, |
| ok); |
| } |
| if (type_byte_a == kIndexMetaDataTypeByte) { |
| return Compare<IndexMetaDataKey>(a, b, |
| /*only_compare_index_keys*/ false, ok); |
| } |
| if (type_byte_a == kObjectStoreFreeListTypeByte) { |
| return Compare<ObjectStoreFreeListKey>(a, b, only_compare_index_keys, |
| ok); |
| } |
| if (type_byte_a == kIndexFreeListTypeByte) { |
| return Compare<IndexFreeListKey>(a, b, |
| /*only_compare_index_keys*/ false, ok); |
| } |
| if (type_byte_a == kObjectStoreNamesTypeByte) { |
| // TODO(jsbell): No need to pass only_compare_index_keys through here. |
| return Compare<ObjectStoreNamesKey>(a, b, only_compare_index_keys, ok); |
| } |
| if (type_byte_a == kIndexNamesKeyTypeByte) { |
| return Compare<IndexNamesKey>(a, b, /*only_compare_index_keys*/ false, |
| ok); |
| } |
| break; |
| } |
| |
| case KeyPrefix::OBJECT_STORE_DATA: { |
| // Provide a stable ordering for invalid data. |
| if (slice_a.empty() || slice_b.empty()) |
| return CompareSizes(slice_a.size(), slice_b.size()); |
| |
| return CompareSuffix<ObjectStoreDataKey>( |
| &slice_a, &slice_b, /*only_compare_index_keys*/ false, ok); |
| } |
| |
| case KeyPrefix::EXISTS_ENTRY: { |
| // Provide a stable ordering for invalid data. |
| if (slice_a.empty() || slice_b.empty()) |
| return CompareSizes(slice_a.size(), slice_b.size()); |
| |
| return CompareSuffix<ExistsEntryKey>( |
| &slice_a, &slice_b, /*only_compare_index_keys*/ false, ok); |
| } |
| |
| case KeyPrefix::BLOB_ENTRY: { |
| // Provide a stable ordering for invalid data. |
| if (slice_a.empty() || slice_b.empty()) |
| return CompareSizes(slice_a.size(), slice_b.size()); |
| |
| return CompareSuffix<BlobEntryKey>(&slice_a, &slice_b, |
| /*only_compare_index_keys*/ false, ok); |
| } |
| |
| case KeyPrefix::INDEX_DATA: { |
| // Provide a stable ordering for invalid data. |
| if (slice_a.empty() || slice_b.empty()) |
| return CompareSizes(slice_a.size(), slice_b.size()); |
| |
| return CompareSuffix<IndexDataKey>(&slice_a, &slice_b, |
| only_compare_index_keys, ok); |
| } |
| |
| case KeyPrefix::INVALID_TYPE: |
| break; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| int Compare(std::string_view a, |
| std::string_view b, |
| bool only_compare_index_keys) { |
| bool ok; |
| int result = Compare(a, b, only_compare_index_keys, &ok); |
| // TODO(dmurph): Report this somehow. https://crbug.com/913121 |
| DCHECK(ok); |
| if (!ok) |
| return 0; |
| return result; |
| } |
| |
| int CompareKeys(std::string_view a, std::string_view b) { |
| return Compare(a, b, /*index_keys=*/false); |
| } |
| |
| int CompareIndexKeys(std::string_view a, std::string_view b) { |
| return Compare(a, b, /*index_keys=*/true); |
| } |
| |
| std::string IndexedDBKeyToDebugString(std::string_view key) { |
| std::string_view key_with_prefix_preserved = key; |
| KeyPrefix prefix; |
| std::stringstream result; |
| if (!KeyPrefix::Decode(&key, &prefix)) { |
| result << "<Error decoding key prefix>"; |
| return result.str(); |
| } |
| result << prefix.DebugString() << ", "; |
| |
| switch (prefix.type()) { |
| case KeyPrefix::GLOBAL_METADATA: { |
| unsigned char type_byte; |
| if (!DecodeByte(&key, &type_byte)) { |
| result << "No_Type_Byte"; |
| break; |
| } |
| switch (type_byte) { |
| case kSchemaVersionTypeByte: |
| result << "kSchemaVersionTypeByte"; |
| break; |
| case kMaxDatabaseIdTypeByte: |
| result << "kMaxDatabaseIdTypeByte"; |
| break; |
| case kDataVersionTypeByte: |
| result << "kDataVersionTypeByte"; |
| break; |
| case kRecoveryBlobJournalTypeByte: |
| result << "kRecoveryBlobJournalTypeByte"; |
| break; |
| case kActiveBlobJournalTypeByte: |
| result << "kActiveBlobJournalTypeByte"; |
| break; |
| case kEarliestSweepTimeTypeByte: |
| result << "kEarliestSweepTimeTypeByte"; |
| break; |
| case kEarliestCompactionTimeTypeByte: |
| result << "kEarliestCompactionTimeTypeByte"; |
| break; |
| case kScopesPrefixByte: |
| result << "Scopes key: " |
| << leveldb_scopes::KeyToDebugString(base::as_byte_span(key)); |
| break; |
| case kDatabaseFreeListTypeByte: { |
| DatabaseFreeListKey db_free_list_key; |
| if (!DatabaseFreeListKey::Decode(&key_with_prefix_preserved, |
| &db_free_list_key)) { |
| result << "kDatabaseFreeListTypeByte, Invalid_Key"; |
| break; |
| } |
| result << db_free_list_key.DebugString(); |
| break; |
| } |
| case kDatabaseNameTypeByte: { |
| DatabaseNameKey db_name_key; |
| if (!DatabaseNameKey::Decode(&key_with_prefix_preserved, |
| &db_name_key)) { |
| result << "kDatabaseNameTypeByte, Invalid_Key"; |
| break; |
| } |
| result << db_name_key.DebugString(); |
| break; |
| } |
| default: |
| result << "Invalid_metadata_type"; |
| break; |
| } |
| break; |
| } |
| |
| case KeyPrefix::DATABASE_METADATA: { |
| unsigned char type_byte; |
| if (!DecodeByte(&key, &type_byte)) { |
| result << "No_Type_Byte"; |
| break; |
| } |
| switch (type_byte) { |
| case DatabaseMetaDataKey::ORIGIN_NAME: |
| result << "ORIGIN_NAME"; |
| break; |
| case DatabaseMetaDataKey::DATABASE_NAME: |
| result << "DATABASE_NAME"; |
| break; |
| case DatabaseMetaDataKey::USER_STRING_VERSION: |
| result << "USER_STRING_VERSION"; |
| break; |
| case DatabaseMetaDataKey::MAX_OBJECT_STORE_ID: |
| result << "MAX_OBJECT_STORE_ID"; |
| break; |
| case DatabaseMetaDataKey::USER_VERSION: |
| result << "USER_VERSION"; |
| break; |
| case DatabaseMetaDataKey::BLOB_KEY_GENERATOR_CURRENT_NUMBER: |
| result << "BLOB_KEY_GENERATOR_CURRENT_NUMBER"; |
| break; |
| case kObjectStoreMetaDataTypeByte: { |
| ObjectStoreMetaDataKey sub_key; |
| if (!ObjectStoreMetaDataKey::Decode(&key_with_prefix_preserved, |
| &sub_key)) { |
| result << "Invalid_ObjectStoreMetaDataKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| case kIndexMetaDataTypeByte: { |
| IndexMetaDataKey sub_key; |
| if (!IndexMetaDataKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_IndexMetaDataKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| case kObjectStoreFreeListTypeByte: { |
| ObjectStoreFreeListKey sub_key; |
| if (!ObjectStoreFreeListKey::Decode(&key_with_prefix_preserved, |
| &sub_key)) { |
| result << "Invalid_ObjectStoreFreeListKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| case kIndexFreeListTypeByte: { |
| IndexFreeListKey sub_key; |
| if (!IndexFreeListKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_IndexFreeListKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| case kObjectStoreNamesTypeByte: { |
| ObjectStoreNamesKey sub_key; |
| if (!ObjectStoreNamesKey::Decode(&key_with_prefix_preserved, |
| &sub_key)) { |
| result << "Invalid_ObjectStoreNamesKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| case kIndexNamesKeyTypeByte: { |
| IndexNamesKey sub_key; |
| if (!IndexNamesKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_IndexNamesKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| } |
| break; |
| } |
| case KeyPrefix::OBJECT_STORE_DATA: { |
| ObjectStoreDataKey sub_key; |
| if (!ObjectStoreDataKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_ObjectStoreDataKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| |
| case KeyPrefix::EXISTS_ENTRY: { |
| ExistsEntryKey sub_key; |
| if (!ExistsEntryKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_ExistsEntryKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| |
| case KeyPrefix::BLOB_ENTRY: { |
| BlobEntryKey sub_key; |
| if (!BlobEntryKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_BlobEntryKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| |
| case KeyPrefix::INDEX_DATA: { |
| IndexDataKey sub_key; |
| if (!IndexDataKey::Decode(&key_with_prefix_preserved, &sub_key)) { |
| result << "Invalid_IndexDataKey"; |
| break; |
| } |
| result << sub_key.DebugString(); |
| break; |
| } |
| |
| case KeyPrefix::INVALID_TYPE: |
| result << "InvalidKeyType"; |
| break; |
| } |
| result << "]"; |
| return result.str(); |
| } |
| |
| KeyPrefix::KeyPrefix() |
| : database_id_(INVALID_TYPE), |
| object_store_id_(INVALID_TYPE), |
| index_id_(INVALID_TYPE) {} |
| |
| KeyPrefix::KeyPrefix(int64_t database_id) |
| : database_id_(database_id), object_store_id_(0), index_id_(0) { |
| DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); |
| } |
| |
| KeyPrefix::KeyPrefix(int64_t database_id, int64_t object_store_id) |
| : database_id_(database_id), |
| object_store_id_(object_store_id), |
| index_id_(0) { |
| DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); |
| DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); |
| } |
| |
| KeyPrefix::KeyPrefix(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) |
| : database_id_(database_id), |
| object_store_id_(object_store_id), |
| index_id_(index_id) { |
| DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); |
| DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); |
| DCHECK(KeyPrefix::IsValidIndexId(index_id)); |
| } |
| |
| KeyPrefix::KeyPrefix(enum Type type, |
| int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) |
| : database_id_(database_id), |
| object_store_id_(object_store_id), |
| index_id_(index_id) { |
| DCHECK_EQ(type, INVALID_TYPE); |
| DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); |
| DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); |
| } |
| |
| KeyPrefix KeyPrefix::CreateWithSpecialIndex(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); |
| DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); |
| DCHECK(index_id); |
| return KeyPrefix(INVALID_TYPE, database_id, object_store_id, index_id); |
| } |
| |
| bool KeyPrefix::IsValidDatabaseId(int64_t database_id) { |
| return (database_id > 0) && (database_id < KeyPrefix::kMaxDatabaseId); |
| } |
| |
| bool KeyPrefix::IsValidObjectStoreId(int64_t object_store_id) { |
| return (object_store_id > 0) && |
| (object_store_id < KeyPrefix::kMaxObjectStoreId); |
| } |
| |
| bool KeyPrefix::IsValidIndexId(int64_t index_id) { |
| return (index_id >= kMinimumIndexId) && (index_id < KeyPrefix::kMaxIndexId); |
| } |
| |
| bool KeyPrefix::Decode(std::string_view* slice, KeyPrefix* result) { |
| unsigned char first_byte; |
| if (!DecodeByte(slice, &first_byte)) |
| return false; |
| |
| size_t database_id_bytes = ((first_byte >> 5) & 0x7) + 1; |
| size_t object_store_id_bytes = ((first_byte >> 2) & 0x7) + 1; |
| size_t index_id_bytes = (first_byte & 0x3) + 1; |
| |
| if (database_id_bytes + object_store_id_bytes + index_id_bytes > |
| slice->size()) |
| return false; |
| |
| { |
| std::string_view tmp = slice->substr(0, database_id_bytes); |
| if (!DecodeInt(&tmp, &result->database_id_)) |
| return false; |
| } |
| slice->remove_prefix(database_id_bytes); |
| { |
| std::string_view tmp = slice->substr(0, object_store_id_bytes); |
| if (!DecodeInt(&tmp, &result->object_store_id_)) |
| return false; |
| } |
| slice->remove_prefix(object_store_id_bytes); |
| { |
| std::string_view tmp = slice->substr(0, index_id_bytes); |
| if (!DecodeInt(&tmp, &result->index_id_)) |
| return false; |
| } |
| slice->remove_prefix(index_id_bytes); |
| return true; |
| } |
| |
| std::string KeyPrefix::EncodeEmpty() { |
| const std::string result(4, 0); |
| DCHECK_EQ(EncodeInternal(0, 0, 0), std::string(4, 0)); |
| return result; |
| } |
| |
| std::string KeyPrefix::Encode() const { |
| DCHECK_NE(database_id_, kInvalidId); |
| DCHECK_NE(object_store_id_, kInvalidId); |
| DCHECK_NE(index_id_, kInvalidId); |
| return EncodeInternal(database_id_, object_store_id_, index_id_); |
| } |
| |
| std::string KeyPrefix::EncodeInternal(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| std::string database_id_string; |
| std::string object_store_id_string; |
| std::string index_id_string; |
| |
| EncodeIntSafely(database_id, kMaxDatabaseId, &database_id_string); |
| EncodeIntSafely(object_store_id, kMaxObjectStoreId, &object_store_id_string); |
| EncodeIntSafely(index_id, kMaxIndexId, &index_id_string); |
| |
| DCHECK_LE(database_id_string.size(), kMaxDatabaseIdSizeBytes); |
| DCHECK_LE(object_store_id_string.size(), kMaxObjectStoreIdSizeBytes); |
| DCHECK_LE(index_id_string.size(), kMaxIndexIdSizeBytes); |
| |
| unsigned char first_byte = |
| (database_id_string.size() - 1) |
| << (kMaxObjectStoreIdSizeBits + kMaxIndexIdSizeBits) | |
| (object_store_id_string.size() - 1) << kMaxIndexIdSizeBits | |
| (index_id_string.size() - 1); |
| static_assert(kMaxDatabaseIdSizeBits + kMaxObjectStoreIdSizeBits + |
| kMaxIndexIdSizeBits == |
| sizeof(first_byte) * 8, |
| "cannot encode ids"); |
| std::string ret; |
| ret.reserve(kDefaultInlineBufferSize); |
| ret.push_back(first_byte); |
| ret.append(database_id_string); |
| ret.append(object_store_id_string); |
| ret.append(index_id_string); |
| |
| DCHECK_LE(ret.size(), kDefaultInlineBufferSize); |
| return ret; |
| } |
| |
| int KeyPrefix::Compare(const KeyPrefix& other) const { |
| DCHECK_NE(database_id_, kInvalidId); |
| DCHECK_NE(object_store_id_, kInvalidId); |
| DCHECK_NE(index_id_, kInvalidId); |
| |
| if (database_id_ != other.database_id_) |
| return CompareInts(database_id_, other.database_id_); |
| if (object_store_id_ != other.object_store_id_) |
| return CompareInts(object_store_id_, other.object_store_id_); |
| if (index_id_ != other.index_id_) |
| return CompareInts(index_id_, other.index_id_); |
| return 0; |
| } |
| |
| std::string KeyPrefix::DebugString() { |
| std::stringstream result; |
| result << "{"; |
| switch (type()) { |
| case GLOBAL_METADATA: |
| result << "GLOBAL_META"; |
| break; |
| case DATABASE_METADATA: |
| result << "DB_META, db: " << database_id_; |
| break; |
| case OBJECT_STORE_DATA: |
| result << "OS_DATA, db: " << database_id_ << ", os: " << object_store_id_; |
| break; |
| case EXISTS_ENTRY: |
| result << "EXISTS_ENTRY, db: " << database_id_ |
| << ", os: " << object_store_id_; |
| break; |
| case BLOB_ENTRY: |
| result << "BLOB_ENTRY, db: " << database_id_ |
| << ", os: " << object_store_id_; |
| break; |
| case INDEX_DATA: |
| result << "INDEX_DATA, db: " << database_id_ |
| << ", os: " << object_store_id_ << ", idx: " << index_id_; |
| break; |
| case INVALID_TYPE: |
| result << "INVALID_TYPE"; |
| break; |
| } |
| result << "}"; |
| return result.str(); |
| } |
| |
| KeyPrefix::Type KeyPrefix::type() const { |
| DCHECK_NE(database_id_, kInvalidId); |
| DCHECK_NE(object_store_id_, kInvalidId); |
| DCHECK_NE(index_id_, kInvalidId); |
| |
| if (!database_id_) |
| return GLOBAL_METADATA; |
| if (!object_store_id_) |
| return DATABASE_METADATA; |
| if (index_id_ == kObjectStoreDataIndexId) |
| return OBJECT_STORE_DATA; |
| if (index_id_ == kExistsEntryIndexId) |
| return EXISTS_ENTRY; |
| if (index_id_ == kBlobEntryIndexId) |
| return BLOB_ENTRY; |
| if (index_id_ >= kMinimumIndexId) |
| return INDEX_DATA; |
| |
| NOTREACHED(); |
| } |
| |
| std::string SchemaVersionKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kSchemaVersionTypeByte); |
| return ret; |
| } |
| |
| std::string MaxDatabaseIdKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kMaxDatabaseIdTypeByte); |
| return ret; |
| } |
| |
| std::string DataVersionKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kDataVersionTypeByte); |
| return ret; |
| } |
| |
| std::string RecoveryBlobJournalKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kRecoveryBlobJournalTypeByte); |
| return ret; |
| } |
| |
| std::string ActiveBlobJournalKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kActiveBlobJournalTypeByte); |
| return ret; |
| } |
| |
| std::string EarliestSweepKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kEarliestSweepTimeTypeByte); |
| return ret; |
| } |
| |
| std::string EarliestCompactionKey::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kEarliestCompactionTimeTypeByte); |
| return ret; |
| } |
| |
| std::vector<uint8_t> ScopesPrefix::Encode() { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kScopesPrefixByte); |
| return std::vector<uint8_t>(ret.begin(), ret.end()); |
| } |
| |
| DatabaseFreeListKey::DatabaseFreeListKey() : database_id_(-1) {} |
| |
| bool DatabaseFreeListKey::Decode(std::string_view* slice, |
| DatabaseFreeListKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(!prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kDatabaseFreeListTypeByte); |
| if (!DecodeVarInt(slice, &result->database_id_)) |
| return false; |
| return true; |
| } |
| |
| std::string DatabaseFreeListKey::Encode(int64_t database_id) { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kDatabaseFreeListTypeByte); |
| EncodeVarInt(database_id, &ret); |
| return ret; |
| } |
| |
| std::string DatabaseFreeListKey::EncodeMaxKey() { |
| return Encode(std::numeric_limits<int64_t>::max()); |
| } |
| |
| int64_t DatabaseFreeListKey::DatabaseId() const { |
| DCHECK_GE(database_id_, 0); |
| return database_id_; |
| } |
| |
| int DatabaseFreeListKey::Compare(const DatabaseFreeListKey& other) const { |
| DCHECK_GE(database_id_, 0); |
| return CompareInts(database_id_, other.database_id_); |
| } |
| |
| std::string DatabaseFreeListKey::DebugString() const { |
| std::stringstream result; |
| result << "DatabaseFreeListKey{db: " << database_id_ << "}"; |
| return result.str(); |
| } |
| |
| bool DatabaseNameKey::Decode(std::string_view* slice, DatabaseNameKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(!prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kDatabaseNameTypeByte); |
| if (!DecodeStringWithLength(slice, &result->origin_)) |
| return false; |
| if (!DecodeStringWithLength(slice, &result->database_name_)) |
| return false; |
| return true; |
| } |
| |
| std::string DatabaseNameKey::Encode(const std::string& origin_identifier, |
| const std::u16string& database_name) { |
| std::string ret = KeyPrefix::EncodeEmpty(); |
| ret.push_back(kDatabaseNameTypeByte); |
| EncodeStringWithLength(base::ASCIIToUTF16(origin_identifier), &ret); |
| EncodeStringWithLength(database_name, &ret); |
| return ret; |
| } |
| |
| std::string DatabaseNameKey::EncodeMinKeyForOrigin( |
| const std::string& origin_identifier) { |
| return Encode(origin_identifier, std::u16string()); |
| } |
| |
| std::string DatabaseNameKey::EncodeStopKeyForOrigin( |
| const std::string& origin_identifier) { |
| // just after origin in collation order |
| return EncodeMinKeyForOrigin(origin_identifier + '\x01'); |
| } |
| |
| int DatabaseNameKey::Compare(const DatabaseNameKey& other) { |
| if (int x = origin_.compare(other.origin_)) |
| return x; |
| return database_name_.compare(other.database_name_); |
| } |
| |
| std::string DatabaseNameKey::DebugString() const { |
| std::stringstream result; |
| result << "DatabaseNameKey{origin: " << origin_ |
| << ", database_name: " << database_name_ << "}"; |
| return result.str(); |
| } |
| |
| bool DatabaseMetaDataKey::IsValidBlobNumber(int64_t blob_number) { |
| return blob_number >= kBlobNumberGeneratorInitialNumber; |
| } |
| |
| const int64_t KeyPrefix::kInvalidId = -1; |
| const int64_t DatabaseMetaDataKey::kAllBlobsNumber = 1; |
| const int64_t DatabaseMetaDataKey::kBlobNumberGeneratorInitialNumber = 2; |
| const int64_t DatabaseMetaDataKey::kInvalidBlobNumber = -1; |
| |
| std::string DatabaseMetaDataKey::Encode(int64_t database_id, |
| MetaDataType meta_data_type) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(meta_data_type); |
| return ret; |
| } |
| |
| const int64_t ObjectStoreMetaDataKey::kKeyGeneratorInitialNumber = 1; |
| |
| ObjectStoreMetaDataKey::ObjectStoreMetaDataKey() |
| : object_store_id_(-1), meta_data_type_(0xFF) {} |
| |
| bool ObjectStoreMetaDataKey::Decode(std::string_view* slice, |
| ObjectStoreMetaDataKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kObjectStoreMetaDataTypeByte); |
| if (!DecodeVarInt(slice, &result->object_store_id_)) |
| return false; |
| DCHECK(result->object_store_id_); |
| if (!DecodeByte(slice, &result->meta_data_type_)) |
| return false; |
| return true; |
| } |
| |
| std::string ObjectStoreMetaDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| unsigned char meta_data_type) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kObjectStoreMetaDataTypeByte); |
| EncodeVarInt(object_store_id, &ret); |
| ret.push_back(meta_data_type); |
| return ret; |
| } |
| |
| std::string ObjectStoreMetaDataKey::EncodeMaxKey(int64_t database_id) { |
| return Encode(database_id, std::numeric_limits<int64_t>::max(), |
| kObjectMetaDataTypeMaximum); |
| } |
| |
| std::string ObjectStoreMetaDataKey::EncodeMaxKey(int64_t database_id, |
| int64_t object_store_id) { |
| return Encode(database_id, object_store_id, kObjectMetaDataTypeMaximum); |
| } |
| |
| int64_t ObjectStoreMetaDataKey::ObjectStoreId() const { |
| DCHECK_GE(object_store_id_, 0); |
| return object_store_id_; |
| } |
| unsigned char ObjectStoreMetaDataKey::MetaDataType() const { |
| return meta_data_type_; |
| } |
| |
| int ObjectStoreMetaDataKey::Compare(const ObjectStoreMetaDataKey& other) { |
| DCHECK_GE(object_store_id_, 0); |
| if (int x = CompareInts(object_store_id_, other.object_store_id_)) |
| return x; |
| return meta_data_type_ - other.meta_data_type_; |
| } |
| |
| std::string ObjectStoreMetaDataKey::DebugString() const { |
| std::stringstream result; |
| result << "ObjectStoreMetaDataKey{os: " << object_store_id_; |
| switch (meta_data_type_) { |
| case NAME: |
| result << ", NAME"; |
| break; |
| case KEY_PATH: |
| result << ", KEY_PATH"; |
| break; |
| case AUTO_INCREMENT: |
| result << ", AUTO_INCREMENT"; |
| break; |
| case EVICTABLE: |
| result << ", EVICTABLE"; |
| break; |
| case LAST_VERSION: |
| result << ", LAST_VERSION"; |
| break; |
| case MAX_INDEX_ID: |
| result << ", MAX_INDEX_ID"; |
| break; |
| case HAS_KEY_PATH: |
| result << ", HAS_KEY_PATH"; |
| break; |
| case KEY_GENERATOR_CURRENT_NUMBER: |
| result << ", KEY_GENERATOR_CURRENT_NUMBER"; |
| break; |
| default: |
| result << ", INVALID_TYPE"; |
| } |
| result << "}"; |
| return result.str(); |
| } |
| |
| IndexMetaDataKey::IndexMetaDataKey() |
| : object_store_id_(-1), index_id_(-1), meta_data_type_(0) {} |
| |
| bool IndexMetaDataKey::Decode(std::string_view* slice, |
| IndexMetaDataKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kIndexMetaDataTypeByte); |
| if (!DecodeVarInt(slice, &result->object_store_id_)) |
| return false; |
| if (!DecodeVarInt(slice, &result->index_id_)) |
| return false; |
| if (!DecodeByte(slice, &result->meta_data_type_)) |
| return false; |
| return true; |
| } |
| |
| std::string IndexMetaDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id, |
| unsigned char meta_data_type) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kIndexMetaDataTypeByte); |
| EncodeVarInt(object_store_id, &ret); |
| EncodeVarInt(index_id, &ret); |
| EncodeByte(meta_data_type, &ret); |
| return ret; |
| } |
| |
| std::string IndexMetaDataKey::EncodeMaxKey(int64_t database_id, |
| int64_t object_store_id) { |
| return Encode(database_id, object_store_id, |
| std::numeric_limits<int64_t>::max(), kIndexMetaDataTypeMaximum); |
| } |
| |
| std::string IndexMetaDataKey::EncodeMaxKey(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| return Encode(database_id, object_store_id, index_id, |
| kIndexMetaDataTypeMaximum); |
| } |
| |
| int IndexMetaDataKey::Compare(const IndexMetaDataKey& other) { |
| DCHECK_GE(object_store_id_, 0); |
| DCHECK_GE(index_id_, 0); |
| |
| if (int x = CompareInts(object_store_id_, other.object_store_id_)) |
| return x; |
| if (int x = CompareInts(index_id_, other.index_id_)) |
| return x; |
| return meta_data_type_ - other.meta_data_type_; |
| } |
| |
| std::string IndexMetaDataKey::DebugString() const { |
| std::stringstream result; |
| result << "IndexMetaDataKey{os: " << object_store_id_ |
| << ", idx: " << index_id_; |
| switch (meta_data_type_) { |
| case NAME: |
| result << ", NAME"; |
| break; |
| case UNIQUE: |
| result << ", UNIQUE"; |
| break; |
| case KEY_PATH: |
| result << ", KEY_PATH"; |
| break; |
| case MULTI_ENTRY: |
| result << ", MULTI_ENTRY"; |
| break; |
| default: |
| result << ", INVALID_TYPE"; |
| } |
| result << "}"; |
| return result.str(); |
| } |
| |
| int64_t IndexMetaDataKey::IndexId() const { |
| DCHECK_GE(index_id_, 0); |
| return index_id_; |
| } |
| |
| ObjectStoreFreeListKey::ObjectStoreFreeListKey() : object_store_id_(-1) {} |
| |
| bool ObjectStoreFreeListKey::Decode(std::string_view* slice, |
| ObjectStoreFreeListKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kObjectStoreFreeListTypeByte); |
| if (!DecodeVarInt(slice, &result->object_store_id_)) |
| return false; |
| return true; |
| } |
| |
| std::string ObjectStoreFreeListKey::Encode(int64_t database_id, |
| int64_t object_store_id) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kObjectStoreFreeListTypeByte); |
| EncodeVarInt(object_store_id, &ret); |
| return ret; |
| } |
| |
| std::string ObjectStoreFreeListKey::EncodeMaxKey(int64_t database_id) { |
| return Encode(database_id, std::numeric_limits<int64_t>::max()); |
| } |
| |
| int64_t ObjectStoreFreeListKey::ObjectStoreId() const { |
| DCHECK_GE(object_store_id_, 0); |
| return object_store_id_; |
| } |
| |
| int ObjectStoreFreeListKey::Compare(const ObjectStoreFreeListKey& other) { |
| // TODO(jsbell): It may seem strange that we're not comparing database id's, |
| // but that comparison will have been made earlier. |
| // We should probably make this more clear, though... |
| DCHECK_GE(object_store_id_, 0); |
| return CompareInts(object_store_id_, other.object_store_id_); |
| } |
| |
| std::string ObjectStoreFreeListKey::DebugString() const { |
| std::stringstream result; |
| result << "ObjectStoreFreeListKey{os: " << object_store_id_ << "}"; |
| return result.str(); |
| } |
| |
| IndexFreeListKey::IndexFreeListKey() : object_store_id_(-1), index_id_(-1) {} |
| |
| bool IndexFreeListKey::Decode(std::string_view* slice, |
| IndexFreeListKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kIndexFreeListTypeByte); |
| if (!DecodeVarInt(slice, &result->object_store_id_)) |
| return false; |
| if (!DecodeVarInt(slice, &result->index_id_)) |
| return false; |
| return true; |
| } |
| |
| std::string IndexFreeListKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kIndexFreeListTypeByte); |
| EncodeVarInt(object_store_id, &ret); |
| EncodeVarInt(index_id, &ret); |
| return ret; |
| } |
| |
| std::string IndexFreeListKey::EncodeMaxKey(int64_t database_id, |
| int64_t object_store_id) { |
| return Encode(database_id, object_store_id, |
| std::numeric_limits<int64_t>::max()); |
| } |
| |
| int IndexFreeListKey::Compare(const IndexFreeListKey& other) { |
| DCHECK_GE(object_store_id_, 0); |
| DCHECK_GE(index_id_, 0); |
| if (int x = CompareInts(object_store_id_, other.object_store_id_)) |
| return x; |
| return CompareInts(index_id_, other.index_id_); |
| } |
| |
| std::string IndexFreeListKey::DebugString() const { |
| std::stringstream result; |
| result << "IndexFreeListKey{os: " << object_store_id_ |
| << ", idx: " << index_id_ << "}"; |
| return result.str(); |
| } |
| |
| int64_t IndexFreeListKey::ObjectStoreId() const { |
| DCHECK_GE(object_store_id_, 0); |
| return object_store_id_; |
| } |
| |
| int64_t IndexFreeListKey::IndexId() const { |
| DCHECK_GE(index_id_, 0); |
| return index_id_; |
| } |
| |
| bool ObjectStoreNamesKey::Decode(std::string_view* slice, |
| ObjectStoreNamesKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kObjectStoreNamesTypeByte); |
| if (!DecodeStringWithLength(slice, &result->object_store_name_)) |
| return false; |
| return true; |
| } |
| |
| std::string ObjectStoreNamesKey::Encode( |
| int64_t database_id, |
| const std::u16string& object_store_name) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kObjectStoreNamesTypeByte); |
| EncodeStringWithLength(object_store_name, &ret); |
| return ret; |
| } |
| |
| int ObjectStoreNamesKey::Compare(const ObjectStoreNamesKey& other) { |
| return object_store_name_.compare(other.object_store_name_); |
| } |
| |
| std::string ObjectStoreNamesKey::DebugString() const { |
| std::stringstream result; |
| result << "ObjectStoreNamesKey{object_store_name: " << object_store_name_ |
| << "}"; |
| return result.str(); |
| } |
| |
| IndexNamesKey::IndexNamesKey() : object_store_id_(-1) {} |
| |
| // TODO(jsbell): We never use this to look up index ids, because a mapping |
| // is kept at a higher level. |
| bool IndexNamesKey::Decode(std::string_view* slice, IndexNamesKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(!prefix.object_store_id_); |
| DCHECK(!prefix.index_id_); |
| unsigned char type_byte = 0; |
| if (!DecodeByte(slice, &type_byte)) |
| return false; |
| DCHECK_EQ(type_byte, kIndexNamesKeyTypeByte); |
| if (!DecodeVarInt(slice, &result->object_store_id_)) |
| return false; |
| if (!DecodeStringWithLength(slice, &result->index_name_)) |
| return false; |
| return true; |
| } |
| |
| std::string IndexNamesKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const std::u16string& index_name) { |
| KeyPrefix prefix(database_id); |
| std::string ret = prefix.Encode(); |
| ret.push_back(kIndexNamesKeyTypeByte); |
| EncodeVarInt(object_store_id, &ret); |
| EncodeStringWithLength(index_name, &ret); |
| return ret; |
| } |
| |
| int IndexNamesKey::Compare(const IndexNamesKey& other) { |
| DCHECK_GE(object_store_id_, 0); |
| if (int x = CompareInts(object_store_id_, other.object_store_id_)) |
| return x; |
| return index_name_.compare(other.index_name_); |
| } |
| |
| std::string IndexNamesKey::DebugString() const { |
| std::stringstream result; |
| result << "IndexNamesKey{os: " << object_store_id_ |
| << ", index_name: " << index_name_ << "}"; |
| return result.str(); |
| } |
| |
| ObjectStoreDataKey::ObjectStoreDataKey() {} |
| ObjectStoreDataKey::~ObjectStoreDataKey() {} |
| |
| bool ObjectStoreDataKey::Decode(std::string_view* slice, |
| ObjectStoreDataKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(prefix.object_store_id_); |
| DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber); |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_)) |
| return false; |
| return true; |
| } |
| |
| std::string ObjectStoreDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const std::string& encoded_user_key) { |
| KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( |
| database_id, object_store_id, kSpecialIndexNumber)); |
| std::string ret = prefix.Encode(); |
| ret.append(encoded_user_key); |
| |
| return ret; |
| } |
| |
| std::string ObjectStoreDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const IndexedDBKey& user_key) { |
| std::string encoded_key; |
| EncodeIDBKey(user_key, &encoded_key); |
| return Encode(database_id, object_store_id, encoded_key); |
| } |
| |
| std::string ObjectStoreDataKey::DebugString() const { |
| return base::StrCat( |
| {"ObjectStoreDataKey{user_key: ", DecodeUserKey().DebugString(), "}"}); |
| } |
| |
| IndexedDBKey ObjectStoreDataKey::DecodeUserKey() const { |
| std::string_view slice(encoded_user_key_); |
| return DecodeIDBKey(&slice); |
| } |
| |
| const int64_t ObjectStoreDataKey::kSpecialIndexNumber = kObjectStoreDataIndexId; |
| |
| ExistsEntryKey::ExistsEntryKey() {} |
| ExistsEntryKey::~ExistsEntryKey() {} |
| |
| bool ExistsEntryKey::Decode(std::string_view* slice, ExistsEntryKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(prefix.object_store_id_); |
| DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber); |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_)) |
| return false; |
| return true; |
| } |
| |
| std::string ExistsEntryKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const std::string& encoded_key) { |
| KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( |
| database_id, object_store_id, kSpecialIndexNumber)); |
| std::string ret = prefix.Encode(); |
| ret.append(encoded_key); |
| return ret; |
| } |
| |
| std::string ExistsEntryKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const IndexedDBKey& user_key) { |
| std::string encoded_key; |
| EncodeIDBKey(user_key, &encoded_key); |
| return Encode(database_id, object_store_id, encoded_key); |
| } |
| |
| std::string ExistsEntryKey::DebugString() const { |
| return base::StrCat( |
| {"ExistsEntryKey{user_key: ", DecodeUserKey().DebugString(), "}"}); |
| } |
| |
| IndexedDBKey ExistsEntryKey::DecodeUserKey() const { |
| std::string_view slice(encoded_user_key_); |
| return DecodeIDBKey(&slice); |
| } |
| |
| const int64_t ExistsEntryKey::kSpecialIndexNumber = kExistsEntryIndexId; |
| |
| bool BlobEntryKey::Decode(std::string_view* slice, BlobEntryKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(prefix.object_store_id_); |
| DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber); |
| |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_)) |
| return false; |
| result->database_id_ = prefix.database_id_; |
| result->object_store_id_ = prefix.object_store_id_; |
| |
| return true; |
| } |
| |
| bool BlobEntryKey::FromObjectStoreDataKey(std::string_view* slice, |
| BlobEntryKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| DCHECK(prefix.database_id_); |
| DCHECK(prefix.object_store_id_); |
| DCHECK_EQ(prefix.index_id_, ObjectStoreDataKey::kSpecialIndexNumber); |
| |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_)) |
| return false; |
| result->database_id_ = prefix.database_id_; |
| result->object_store_id_ = prefix.object_store_id_; |
| return true; |
| } |
| |
| std::string BlobEntryKey::ReencodeToObjectStoreDataKey( |
| std::string_view* slice) { |
| // TODO(ericu): We could be more efficient here, since the suffix is the same. |
| BlobEntryKey key; |
| if (!Decode(slice, &key)) |
| return std::string(); |
| |
| return ObjectStoreDataKey::Encode(key.database_id_, key.object_store_id_, |
| key.encoded_user_key_); |
| } |
| |
| std::string BlobEntryKey::EncodeMinKeyForObjectStore(int64_t database_id, |
| int64_t object_store_id) { |
| // Our implied encoded_user_key_ here is empty, the lowest possible key. |
| return Encode(database_id, object_store_id, std::string()); |
| } |
| |
| std::string BlobEntryKey::EncodeStopKeyForObjectStore(int64_t database_id, |
| int64_t object_store_id) { |
| DCHECK(KeyPrefix::ValidIds(database_id, object_store_id)); |
| KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( |
| database_id, object_store_id, kSpecialIndexNumber + 1)); |
| return prefix.Encode(); |
| } |
| |
| std::string BlobEntryKey::Encode() const { |
| DCHECK(!encoded_user_key_.empty()); |
| return Encode(database_id_, object_store_id_, encoded_user_key_); |
| } |
| |
| std::string BlobEntryKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const IndexedDBKey& user_key) { |
| std::string encoded_key; |
| EncodeIDBKey(user_key, &encoded_key); |
| return Encode(database_id, object_store_id, encoded_key); |
| } |
| |
| std::string BlobEntryKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| const std::string& encoded_user_key) { |
| DCHECK(KeyPrefix::ValidIds(database_id, object_store_id)); |
| KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( |
| database_id, object_store_id, kSpecialIndexNumber)); |
| return prefix.Encode() + encoded_user_key; |
| } |
| |
| std::string BlobEntryKey::DebugString() const { |
| std::stringstream result; |
| result << "BlobEntryKey{db: " << database_id_ << "os: " << object_store_id_ |
| << ", user_key: "; |
| std::string_view slice(encoded_user_key_); |
| if (blink::IndexedDBKey key = DecodeIDBKey(&slice); key.IsValid()) { |
| result << key.DebugString(); |
| } else { |
| result << "Invalid"; |
| } |
| result << "}"; |
| return result.str(); |
| } |
| |
| const int64_t BlobEntryKey::kSpecialIndexNumber = kBlobEntryIndexId; |
| |
| IndexDataKey::IndexDataKey() |
| : database_id_(-1), |
| object_store_id_(-1), |
| index_id_(-1), |
| sequence_number_(-1) {} |
| |
| IndexDataKey::IndexDataKey(IndexDataKey&& other) = default; |
| |
| IndexDataKey::~IndexDataKey() {} |
| |
| bool IndexDataKey::Decode(std::string_view* slice, IndexDataKey* result) { |
| KeyPrefix prefix; |
| if (!KeyPrefix::Decode(slice, &prefix)) |
| return false; |
| if (prefix.database_id_ <= 0) |
| return false; |
| if (prefix.object_store_id_ <= 0) |
| return false; |
| if (prefix.index_id_ < kMinimumIndexId) |
| return false; |
| result->database_id_ = prefix.database_id_; |
| result->object_store_id_ = prefix.object_store_id_; |
| result->index_id_ = prefix.index_id_; |
| result->sequence_number_ = -1; |
| result->encoded_primary_key_ = MinIDBKey(); |
| |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_)) |
| return false; |
| |
| // [optional] sequence number |
| if (slice->empty()) |
| return true; |
| if (!DecodeVarInt(slice, &result->sequence_number_)) |
| return false; |
| |
| // [optional] primary key |
| if (slice->empty()) |
| return true; |
| if (!ExtractEncodedIDBKey(slice, &result->encoded_primary_key_)) |
| return false; |
| return true; |
| } |
| |
| std::string IndexDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id, |
| std::string_view encoded_user_key, |
| std::string_view encoded_primary_key, |
| int64_t sequence_number) { |
| KeyPrefix prefix(database_id, object_store_id, index_id); |
| std::string ret = prefix.Encode(); |
| ret.append(encoded_user_key); |
| EncodeVarInt(sequence_number, &ret); |
| ret.append(encoded_primary_key); |
| return ret; |
| } |
| |
| std::string IndexDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id, |
| const IndexedDBKey& user_key) { |
| std::string encoded_key; |
| EncodeIDBKey(user_key, &encoded_key); |
| return Encode(database_id, object_store_id, index_id, encoded_key, |
| MinIDBKey(), 0); |
| } |
| |
| std::string IndexDataKey::Encode(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id, |
| const IndexedDBKey& user_key, |
| const IndexedDBKey& user_primary_key) { |
| std::string encoded_key; |
| EncodeIDBKey(user_key, &encoded_key); |
| std::string encoded_primary_key; |
| EncodeIDBKey(user_primary_key, &encoded_primary_key); |
| return Encode(database_id, object_store_id, index_id, encoded_key, |
| encoded_primary_key, 0); |
| } |
| |
| std::string IndexDataKey::EncodeMinKey(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| return Encode(database_id, object_store_id, index_id, MinIDBKey(), |
| MinIDBKey(), 0); |
| } |
| |
| std::string IndexDataKey::EncodeMaxKey(int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id) { |
| return Encode(database_id, object_store_id, index_id, MaxIDBKey(), |
| MaxIDBKey(), std::numeric_limits<int64_t>::max()); |
| } |
| |
| std::string IndexDataKey::Encode() const { |
| return Encode(database_id_, object_store_id_, index_id_, encoded_user_key_, |
| encoded_primary_key_, sequence_number_); |
| } |
| |
| std::string IndexDataKey::DebugString() const { |
| blink::IndexedDBKey user = DecodeUserKey(); |
| blink::IndexedDBKey primary = DecodePrimaryKey(); |
| std::stringstream result; |
| result << "IndexDataKey{db: " << database_id_ << ", os: " << object_store_id_ |
| << ", idx: " << index_id_ << ", sequence_number: " << sequence_number_ |
| << ", user_key: " << user.DebugString() |
| << ", primary_key: " << primary.DebugString() << "}"; |
| return result.str(); |
| } |
| |
| int64_t IndexDataKey::DatabaseId() const { |
| DCHECK_GE(database_id_, 0); |
| return database_id_; |
| } |
| |
| int64_t IndexDataKey::ObjectStoreId() const { |
| DCHECK_GE(object_store_id_, 0); |
| return object_store_id_; |
| } |
| |
| int64_t IndexDataKey::IndexId() const { |
| DCHECK_GE(index_id_, 0); |
| return index_id_; |
| } |
| |
| IndexedDBKey IndexDataKey::DecodeUserKey() const { |
| std::string_view slice(encoded_user_key_); |
| return DecodeIDBKey(&slice); |
| } |
| |
| IndexedDBKey IndexDataKey::DecodePrimaryKey() const { |
| std::string_view slice(encoded_primary_key_); |
| return DecodeIDBKey(&slice); |
| } |
| |
| } // namespace content::indexed_db |