blob: 533aa63aa56fca5218d1791625304a0a51f07574 [file] [log] [blame]
// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "components/services/storage/indexed_db/scopes/varint_coding.h"
#include "testing/gtest/include/gtest/gtest.h"
using blink::IndexedDBKey;
using blink::IndexedDBKeyPath;
namespace content::indexed_db {
namespace {
static IndexedDBKey CreateArrayIDBKey() {
return IndexedDBKey(IndexedDBKey::KeyArray());
}
static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1) {
IndexedDBKey::KeyArray array;
array.emplace_back(key1.Clone());
return IndexedDBKey(std::move(array));
}
static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1,
const IndexedDBKey& key2) {
IndexedDBKey::KeyArray array;
array.emplace_back(key1.Clone());
array.emplace_back(key2.Clone());
return IndexedDBKey(std::move(array));
}
static std::string WrappedEncodeByte(char value) {
std::string buffer;
EncodeByte(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeByte) {
std::string expected;
expected.push_back(0);
unsigned char c;
c = 0;
expected[0] = c;
EXPECT_EQ(expected, WrappedEncodeByte(c));
c = 1;
expected[0] = c;
EXPECT_EQ(expected, WrappedEncodeByte(c));
c = 255;
expected[0] = c;
EXPECT_EQ(expected, WrappedEncodeByte(c));
}
TEST(IndexedDBLevelDBCodingTest, DecodeByte) {
std::vector<unsigned char> test_cases = {0, 1, 255};
for (size_t i = 0; i < test_cases.size(); ++i) {
unsigned char n = test_cases[i];
std::string v;
EncodeByte(n, &v);
unsigned char res;
ASSERT_GT(v.size(), 0u);
std::string_view slice(v);
EXPECT_TRUE(DecodeByte(&slice, &res));
EXPECT_EQ(n, res);
EXPECT_TRUE(slice.empty());
}
{
std::string_view slice;
unsigned char value;
EXPECT_FALSE(DecodeByte(&slice, &value));
}
}
static std::string WrappedEncodeBool(bool value) {
std::string buffer;
EncodeBool(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeBool) {
{
std::string expected;
expected.push_back(1);
EXPECT_EQ(expected, WrappedEncodeBool(true));
}
{
std::string expected;
expected.push_back(0);
EXPECT_EQ(expected, WrappedEncodeBool(false));
}
}
static int CompareKeys(const std::string& a, const std::string& b) {
DCHECK(!a.empty());
DCHECK(!b.empty());
std::string_view slice_a(a);
std::string_view slice_b(b);
bool ok;
int result = CompareEncodedIDBKeys(&slice_a, &slice_b, &ok);
EXPECT_TRUE(ok);
return result;
}
TEST(IndexedDBLevelDBCodingTest, MaxIDBKey) {
std::string max_key = MaxIDBKey();
std::string min_key = MinIDBKey();
std::string array_key;
EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray()), &array_key);
std::string binary_key;
EncodeIDBKey(IndexedDBKey(std::string("\x00\x01\x02")), &binary_key);
std::string string_key;
EncodeIDBKey(IndexedDBKey(u"Hello world"), &string_key);
std::string number_key;
EncodeIDBKey(IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
&number_key);
std::string date_key;
EncodeIDBKey(IndexedDBKey(1000000, blink::mojom::IDBKeyType::Date),
&date_key);
EXPECT_GT(CompareKeys(max_key, min_key), 0);
EXPECT_GT(CompareKeys(max_key, array_key), 0);
EXPECT_GT(CompareKeys(max_key, binary_key), 0);
EXPECT_GT(CompareKeys(max_key, string_key), 0);
EXPECT_GT(CompareKeys(max_key, number_key), 0);
EXPECT_GT(CompareKeys(max_key, date_key), 0);
}
TEST(IndexedDBLevelDBCodingTest, MinIDBKey) {
std::string min_key = MinIDBKey();
std::string max_key = MaxIDBKey();
std::string array_key;
EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray()), &array_key);
std::string binary_key;
EncodeIDBKey(IndexedDBKey(std::string("\x00\x01\x02")), &binary_key);
std::string string_key;
EncodeIDBKey(IndexedDBKey(u"Hello world"), &string_key);
std::string number_key;
EncodeIDBKey(IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
&number_key);
std::string date_key;
EncodeIDBKey(IndexedDBKey(1000000, blink::mojom::IDBKeyType::Date),
&date_key);
EXPECT_LT(CompareKeys(min_key, max_key), 0);
EXPECT_LT(CompareKeys(min_key, array_key), 0);
EXPECT_LT(CompareKeys(min_key, binary_key), 0);
EXPECT_LT(CompareKeys(min_key, string_key), 0);
EXPECT_LT(CompareKeys(min_key, number_key), 0);
EXPECT_LT(CompareKeys(min_key, date_key), 0);
}
static std::string WrappedEncodeInt(int64_t value) {
std::string buffer;
EncodeInt(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeInt) {
EXPECT_EQ(1u, WrappedEncodeInt(0).size());
EXPECT_EQ(1u, WrappedEncodeInt(1).size());
EXPECT_EQ(1u, WrappedEncodeInt(255).size());
EXPECT_EQ(2u, WrappedEncodeInt(256).size());
EXPECT_EQ(4u, WrappedEncodeInt(0xffffffff).size());
#ifdef NDEBUG
EXPECT_EQ(8u, WrappedEncodeInt(-1).size());
#endif
}
TEST(IndexedDBLevelDBCodingTest, DecodeBool) {
{
std::string encoded;
encoded.push_back(1);
std::string_view slice(encoded);
bool value;
EXPECT_TRUE(DecodeBool(&slice, &value));
EXPECT_TRUE(value);
EXPECT_TRUE(slice.empty());
}
{
std::string encoded;
encoded.push_back(0);
std::string_view slice(encoded);
bool value;
EXPECT_TRUE(DecodeBool(&slice, &value));
EXPECT_FALSE(value);
EXPECT_TRUE(slice.empty());
}
{
std::string_view slice;
bool value;
EXPECT_FALSE(DecodeBool(&slice, &value));
}
}
TEST(IndexedDBLevelDBCodingTest, DecodeInt) {
std::vector<int64_t> test_cases = {
0,
1,
255,
256,
65535,
655536,
7711192431755665792ll,
0x7fffffffffffffffll,
#ifdef NDEBUG
-3,
#endif
};
for (size_t i = 0; i < test_cases.size(); ++i) {
int64_t n = test_cases[i];
std::string v = WrappedEncodeInt(n);
ASSERT_GT(v.size(), 0u);
std::string_view slice(v);
int64_t value;
EXPECT_TRUE(DecodeInt(&slice, &value));
EXPECT_EQ(n, value);
EXPECT_TRUE(slice.empty());
// Verify decoding at an offset, to detect unaligned memory access.
v.insert(v.begin(), 1u, static_cast<char>(0));
slice = std::string_view(v).substr(1u);
EXPECT_TRUE(DecodeInt(&slice, &value));
EXPECT_EQ(n, value);
EXPECT_TRUE(slice.empty());
}
{
std::string_view slice;
int64_t value;
EXPECT_FALSE(DecodeInt(&slice, &value));
}
}
static std::string WrappedEncodeString(std::u16string value) {
std::string buffer;
EncodeString(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeString) {
const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
EXPECT_EQ(0u, WrappedEncodeString(u"").size());
EXPECT_EQ(2u, WrappedEncodeString(u"a").size());
EXPECT_EQ(6u, WrappedEncodeString(u"foo").size());
EXPECT_EQ(6u, WrappedEncodeString(std::u16string(test_string_a)).size());
EXPECT_EQ(4u, WrappedEncodeString(std::u16string(test_string_b)).size());
}
TEST(IndexedDBLevelDBCodingTest, DecodeString) {
const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
std::vector<std::u16string> test_cases = {u"", u"a", u"foo", test_string_a,
test_string_b};
for (size_t i = 0; i < test_cases.size(); ++i) {
const std::u16string& test_case = test_cases[i];
std::string v = WrappedEncodeString(test_case);
std::string_view slice;
if (v.size()) {
slice = std::string_view(&*v.begin(), v.size());
}
std::u16string result;
EXPECT_TRUE(DecodeString(&slice, &result));
EXPECT_EQ(test_case, result);
EXPECT_TRUE(slice.empty());
// Verify decoding at an offset, to detect unaligned memory access.
v.insert(v.begin(), 1u, static_cast<char>(0));
slice = std::string_view(v).substr(1u);
EXPECT_TRUE(DecodeString(&slice, &result));
EXPECT_EQ(test_case, result);
EXPECT_TRUE(slice.empty());
}
}
static std::string WrappedEncodeStringWithLength(std::u16string value) {
std::string buffer;
EncodeStringWithLength(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeStringWithLength) {
const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
EXPECT_EQ(1u, WrappedEncodeStringWithLength(u"").size());
EXPECT_EQ(3u, WrappedEncodeStringWithLength(u"a").size());
EXPECT_EQ(
7u, WrappedEncodeStringWithLength(std::u16string(test_string_a)).size());
EXPECT_EQ(
5u, WrappedEncodeStringWithLength(std::u16string(test_string_b)).size());
}
TEST(IndexedDBLevelDBCodingTest, DecodeStringWithLength) {
const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
const int kLongStringLen = 1234;
std::array<char16_t, kLongStringLen + 1> long_string;
for (int i = 0; i < kLongStringLen; ++i)
long_string[i] = i;
long_string[kLongStringLen] = 0;
std::vector<std::u16string> test_cases = {u"",
u"a",
u"foo",
std::u16string(test_string_a),
std::u16string(test_string_b),
std::u16string(long_string.data())};
for (size_t i = 0; i < test_cases.size(); ++i) {
std::u16string s = test_cases[i];
std::string v = WrappedEncodeStringWithLength(s);
ASSERT_GT(v.size(), 0u);
std::string_view slice(v);
std::u16string res;
EXPECT_TRUE(DecodeStringWithLength(&slice, &res));
EXPECT_EQ(s, res);
EXPECT_TRUE(slice.empty());
slice = std::string_view(&*v.begin(), v.size() - 1);
EXPECT_FALSE(DecodeStringWithLength(&slice, &res));
slice = std::string_view(&*v.begin(), static_cast<size_t>(0));
EXPECT_FALSE(DecodeStringWithLength(&slice, &res));
// Verify decoding at an offset, to detect unaligned memory access.
v.insert(v.begin(), 1u, static_cast<char>(0));
slice = std::string_view(v).substr(1u);
EXPECT_TRUE(DecodeStringWithLength(&slice, &res));
EXPECT_EQ(s, res);
EXPECT_TRUE(slice.empty());
}
}
static int CompareStrings(const std::string& p, const std::string& q) {
bool ok;
DCHECK(!p.empty());
DCHECK(!q.empty());
std::string_view slice_p(p);
std::string_view slice_q(q);
int result = CompareEncodedStringsWithLength(&slice_p, &slice_q, &ok);
EXPECT_TRUE(ok);
EXPECT_TRUE(slice_p.empty());
EXPECT_TRUE(slice_q.empty());
return result;
}
TEST(IndexedDBLevelDBCodingTest, CompareEncodedStringsWithLength) {
const char16_t test_string_a[] = {0x1000, 0x1000, '\0'};
const char16_t test_string_b[] = {0x1000, 0x1000, 0x1000, '\0'};
const char16_t test_string_c[] = {0x1000, 0x1000, 0x1001, '\0'};
const char16_t test_string_d[] = {0x1001, 0x1000, 0x1000, '\0'};
const char16_t test_string_e[] = {0xd834, 0xdd1e, '\0'};
const char16_t test_string_f[] = {0xfffd, '\0'};
std::vector<std::u16string> test_cases = {
u"",
u"a",
u"b",
u"baaa",
u"baab",
u"c",
std::u16string(test_string_a),
std::u16string(test_string_b),
std::u16string(test_string_c),
std::u16string(test_string_d),
std::u16string(test_string_e),
std::u16string(test_string_f),
};
for (size_t i = 0; i < test_cases.size() - 1; ++i) {
std::u16string a = test_cases[i];
std::u16string b = test_cases[i + 1];
EXPECT_LT(a.compare(b), 0);
EXPECT_GT(b.compare(a), 0);
EXPECT_EQ(a.compare(a), 0);
EXPECT_EQ(b.compare(b), 0);
std::string encoded_a = WrappedEncodeStringWithLength(a);
EXPECT_TRUE(encoded_a.size());
std::string encoded_b = WrappedEncodeStringWithLength(b);
EXPECT_TRUE(encoded_a.size());
EXPECT_LT(CompareStrings(encoded_a, encoded_b), 0);
EXPECT_GT(CompareStrings(encoded_b, encoded_a), 0);
EXPECT_EQ(CompareStrings(encoded_a, encoded_a), 0);
EXPECT_EQ(CompareStrings(encoded_b, encoded_b), 0);
}
}
static std::string WrappedEncodeBinary(const std::string& value) {
std::string buffer;
EncodeBinary(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeBinary) {
const unsigned char binary_data[] = {0x00, 0x01, 0xfe, 0xff};
EXPECT_EQ(
1u,
WrappedEncodeBinary(std::string(binary_data, binary_data + 0)).size());
EXPECT_EQ(
2u,
WrappedEncodeBinary(std::string(binary_data, binary_data + 1)).size());
EXPECT_EQ(
5u,
WrappedEncodeBinary(std::string(binary_data, binary_data + 4)).size());
}
TEST(IndexedDBLevelDBCodingTest, DecodeBinary) {
const unsigned char binary_data[] = { 0x00, 0x01, 0xfe, 0xff };
std::vector<std::string> test_cases = {
std::string(binary_data, binary_data + 0),
std::string(binary_data, binary_data + 1),
std::string(binary_data, binary_data + 4)};
for (size_t i = 0; i < test_cases.size(); ++i) {
std::string value = test_cases[i];
std::string v = WrappedEncodeBinary(value);
ASSERT_GT(v.size(), 0u);
std::string_view slice(v);
std::string result;
EXPECT_TRUE(DecodeBinary(&slice, &result));
EXPECT_EQ(value, result);
EXPECT_TRUE(slice.empty());
slice = std::string_view(&*v.begin(), v.size() - 1);
EXPECT_FALSE(DecodeBinary(&slice, &result));
slice = std::string_view(&*v.begin(), static_cast<size_t>(0));
EXPECT_FALSE(DecodeBinary(&slice, &result));
// Verify decoding at an offset, to detect unaligned memory access.
v.insert(v.begin(), 1u, static_cast<char>(0));
slice = std::string_view(v).substr(1u);
EXPECT_TRUE(DecodeBinary(&slice, &result));
EXPECT_EQ(value, result);
EXPECT_TRUE(slice.empty());
}
}
static std::string WrappedEncodeDouble(double value) {
std::string buffer;
EncodeDouble(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeDouble) {
EXPECT_EQ(8u, WrappedEncodeDouble(0).size());
EXPECT_EQ(8u, WrappedEncodeDouble(3.14).size());
}
TEST(IndexedDBLevelDBCodingTest, DecodeDouble) {
std::vector<double> test_cases = {3.14, -3.14};
for (size_t i = 0; i < test_cases.size(); ++i) {
double value = test_cases[i];
std::string v = WrappedEncodeDouble(value);
ASSERT_GT(v.size(), 0u);
std::string_view slice(v);
double result;
EXPECT_TRUE(DecodeDouble(&slice, &result));
EXPECT_EQ(value, result);
EXPECT_TRUE(slice.empty());
slice = std::string_view(&*v.begin(), v.size() - 1);
EXPECT_FALSE(DecodeDouble(&slice, &result));
slice = std::string_view(&*v.begin(), static_cast<size_t>(0));
EXPECT_FALSE(DecodeDouble(&slice, &result));
// Verify decoding at an offset, to detect unaligned memory access.
v.insert(v.begin(), 1u, static_cast<char>(0));
slice = std::string_view(v).substr(1u);
EXPECT_TRUE(DecodeDouble(&slice, &result));
EXPECT_EQ(value, result);
EXPECT_TRUE(slice.empty());
}
}
TEST(IndexedDBLevelDBCodingTest, EncodeDecodeIDBKey) {
IndexedDBKey expected_key;
std::string v;
std::string_view slice;
IndexedDBKey::KeyArray array;
array.emplace_back(1234, blink::mojom::IDBKeyType::Number);
array.emplace_back(7890, blink::mojom::IDBKeyType::Date);
array.emplace_back(u"Hello World!");
array.emplace_back(std::string("\x01\x02"));
array.emplace_back(IndexedDBKey::KeyArray());
auto test_cases = std::to_array(
{IndexedDBKey(1234, blink::mojom::IDBKeyType::Number),
IndexedDBKey(7890, blink::mojom::IDBKeyType::Date),
IndexedDBKey(u"Hello World!"), IndexedDBKey(std::string("\x01\x02")),
IndexedDBKey(IndexedDBKey::KeyArray()), IndexedDBKey(std::move(array))});
for (size_t i = 0; i < test_cases.size(); ++i) {
expected_key = test_cases[i].Clone();
v.clear();
EncodeIDBKey(expected_key, &v);
slice = std::string_view(&*v.begin(), v.size());
IndexedDBKey decoded_key = DecodeIDBKey(&slice);
EXPECT_TRUE(decoded_key.IsValid());
EXPECT_TRUE(decoded_key.Equals(expected_key));
EXPECT_TRUE(slice.empty());
slice = std::string_view(&*v.begin(), v.size() - 1);
EXPECT_FALSE(DecodeIDBKey(&slice).IsValid());
slice = std::string_view(&*v.begin(), static_cast<size_t>(0));
EXPECT_FALSE(DecodeIDBKey(&slice).IsValid());
}
}
static std::string WrappedEncodeIDBKeyPath(const IndexedDBKeyPath& value) {
std::string buffer;
EncodeIDBKeyPath(value, &buffer);
return buffer;
}
TEST(IndexedDBLevelDBCodingTest, EncodeDecodeIDBKeyPath) {
std::vector<IndexedDBKeyPath> key_paths;
std::vector<std::string> encoded_paths;
{
key_paths.push_back(IndexedDBKeyPath());
char expected[] = {0, 0, // Header
0 // Type is null
};
encoded_paths.push_back(
std::string(std::begin(expected), std::end(expected)));
}
{
key_paths.push_back(IndexedDBKeyPath(u""));
char expected[] = {0, 0, // Header
1, // Type is string
0 // Length is 0
};
encoded_paths.push_back(
std::string(std::begin(expected), std::end(expected)));
}
{
key_paths.emplace_back(u"foo");
char expected[] = {0, 0, // Header
1, // Type is string
3, 0, 'f', 0, 'o', 0, 'o' // String length 3, UTF-16BE
};
encoded_paths.push_back(
std::string(std::begin(expected), std::end(expected)));
}
{
key_paths.emplace_back(u"foo.bar");
char expected[] = {0, 0, // Header
1, // Type is string
7, 0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0,
'r' // String length 7, UTF-16BE
};
encoded_paths.push_back(
std::string(std::begin(expected), std::end(expected)));
}
{
std::vector<std::u16string> array = {u"", u"foo", u"foo.bar"};
key_paths.push_back(IndexedDBKeyPath(array));
char expected[] = {0, 0, // Header
2, 3, // Type is array, length is 3
0, // Member 1 (String length 0)
3, 0, 'f', 0, 'o', 0, 'o', // Member 2 (String length 3)
7, 0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0,
'r' // Member 3 (String length 7)
};
encoded_paths.push_back(
std::string(std::begin(expected), std::end(expected)));
}
ASSERT_EQ(key_paths.size(), encoded_paths.size());
for (size_t i = 0; i < key_paths.size(); ++i) {
IndexedDBKeyPath key_path = key_paths[i];
std::string encoded = encoded_paths[i];
std::string v = WrappedEncodeIDBKeyPath(key_path);
EXPECT_EQ(encoded, v);
std::string_view slice(encoded);
IndexedDBKeyPath decoded;
EXPECT_TRUE(DecodeIDBKeyPath(&slice, &decoded));
EXPECT_EQ(key_path, decoded);
EXPECT_TRUE(slice.empty());
}
}
TEST(IndexedDBLevelDBCodingTest, EncodeDecodeBlobJournal) {
std::vector<IndexedDBKeyPath> key_paths;
std::vector<std::string> encoded_paths;
std::vector<BlobJournalType> journals;
{ // Empty journal
journals.push_back({});
}
{ // One item
journals.push_back({{4, 7}});
}
{ // kAllBlobsKey
journals.push_back({{5, DatabaseMetaDataKey::kAllBlobsNumber}});
}
{ // A bunch of items
journals.push_back(
{{4, 7}, {5, 6}, {4, 5}, {4, 4}, {1, 12}, {4, 3}, {15, 14}});
}
for (const auto& journal_iter : journals) {
std::string encoding;
EncodeBlobJournal(journal_iter, &encoding);
std::string_view slice(encoding);
BlobJournalType journal_out;
EXPECT_TRUE(DecodeBlobJournal(&slice, &journal_out));
EXPECT_EQ(journal_iter, journal_out);
}
journals.clear();
{ // Illegal database id
journals.push_back({{0, 3}});
}
{ // Illegal blob id
journals.push_back({{4, 0}});
}
for (const auto& journal_iter : journals) {
std::string encoding;
EncodeBlobJournal(journal_iter, &encoding);
std::string_view slice(encoding);
BlobJournalType journal_out;
EXPECT_FALSE(DecodeBlobJournal(&slice, &journal_out));
}
}
TEST(IndexedDBLevelDBCodingTest, DecodeLegacyIDBKeyPath) {
// Legacy encoding of string key paths.
std::vector<IndexedDBKeyPath> key_paths;
std::vector<std::string> encoded_paths;
{
key_paths.push_back(IndexedDBKeyPath(u""));
encoded_paths.push_back(std::string());
}
{
key_paths.emplace_back(u"foo");
char expected[] = {0, 'f', 0, 'o', 0, 'o'};
encoded_paths.push_back(std::string(expected, std::size(expected)));
}
{
key_paths.emplace_back(u"foo.bar");
char expected[] = {0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0, 'r'};
encoded_paths.push_back(std::string(expected, std::size(expected)));
}
ASSERT_EQ(key_paths.size(), encoded_paths.size());
for (size_t i = 0; i < key_paths.size(); ++i) {
IndexedDBKeyPath key_path = key_paths[i];
std::string encoded = encoded_paths[i];
std::string_view slice(encoded);
IndexedDBKeyPath decoded;
EXPECT_TRUE(DecodeIDBKeyPath(&slice, &decoded));
EXPECT_EQ(key_path, decoded);
EXPECT_TRUE(slice.empty());
}
}
TEST(IndexedDBLevelDBCodingTest, ExtractAndCompareIDBKeys) {
auto keys = std::to_array({
IndexedDBKey(-10, blink::mojom::IDBKeyType::Number),
IndexedDBKey(0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
IndexedDBKey(0, blink::mojom::IDBKeyType::Date),
IndexedDBKey(100, blink::mojom::IDBKeyType::Date),
IndexedDBKey(100000, blink::mojom::IDBKeyType::Date),
IndexedDBKey(u""),
IndexedDBKey(u"a"),
IndexedDBKey(u"b"),
IndexedDBKey(u"baaa"),
IndexedDBKey(u"baab"),
IndexedDBKey(u"c"),
IndexedDBKey(std::string()),
IndexedDBKey(std::string("\x01")),
IndexedDBKey(std::string("\x01\x01")),
IndexedDBKey(std::string("\x01\x02")),
IndexedDBKey(std::string("\x02")),
IndexedDBKey(std::string("\x02\x01")),
IndexedDBKey(std::string("\x02\x02")),
IndexedDBKey(std::string("\xff")),
CreateArrayIDBKey(),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Number)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Date)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Date),
IndexedDBKey(0, blink::mojom::IDBKeyType::Date)),
CreateArrayIDBKey(IndexedDBKey(u"")),
CreateArrayIDBKey(IndexedDBKey(u""), IndexedDBKey(u"a")),
CreateArrayIDBKey(CreateArrayIDBKey()),
CreateArrayIDBKey(CreateArrayIDBKey(), CreateArrayIDBKey()),
CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey())),
CreateArrayIDBKey(
CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey()))),
});
for (size_t i = 0; i < keys.size() - 1; ++i) {
const IndexedDBKey& key_a = keys[i];
const IndexedDBKey& key_b = keys[i + 1];
EXPECT_TRUE(key_a.IsLessThan(key_b));
std::string encoded_a;
EncodeIDBKey(key_a, &encoded_a);
EXPECT_TRUE(encoded_a.size());
std::string encoded_b;
EncodeIDBKey(key_b, &encoded_b);
EXPECT_TRUE(encoded_b.size());
std::string extracted_a;
std::string extracted_b;
std::string_view slice;
slice = std::string_view(encoded_a);
EXPECT_TRUE(ExtractEncodedIDBKey(&slice, &extracted_a));
EXPECT_TRUE(slice.empty());
EXPECT_EQ(encoded_a, extracted_a);
slice = std::string_view(encoded_b);
EXPECT_TRUE(ExtractEncodedIDBKey(&slice, &extracted_b));
EXPECT_TRUE(slice.empty());
EXPECT_EQ(encoded_b, extracted_b);
EXPECT_LT(CompareKeys(extracted_a, extracted_b), 0);
EXPECT_GT(CompareKeys(extracted_b, extracted_a), 0);
EXPECT_EQ(CompareKeys(extracted_a, extracted_a), 0);
EXPECT_EQ(CompareKeys(extracted_b, extracted_b), 0);
slice = std::string_view(&*encoded_a.begin(), encoded_a.size() - 1);
EXPECT_FALSE(ExtractEncodedIDBKey(&slice, &extracted_a));
}
}
TEST(IndexedDBLevelDBCodingTest, EncodeAndCompareIDBKeysWithSentinels) {
const char16_t kJunkString[] = {0xdead, 0xbeef, '\0'};
auto keys = std::to_array({
IndexedDBKey(-15, blink::mojom::IDBKeyType::Number),
IndexedDBKey(-10, blink::mojom::IDBKeyType::Number),
IndexedDBKey(0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
IndexedDBKey(42, blink::mojom::IDBKeyType::Number),
IndexedDBKey(0, blink::mojom::IDBKeyType::Date),
IndexedDBKey(100, blink::mojom::IDBKeyType::Date),
IndexedDBKey(100000, blink::mojom::IDBKeyType::Date),
IndexedDBKey(u""),
IndexedDBKey(u"a"),
IndexedDBKey(u"b"),
IndexedDBKey(u"baaa"),
IndexedDBKey(u"baab"),
IndexedDBKey(u"c"),
// Some more adventurous strings.
IndexedDBKey(u"\xA2"),
// Valid UTF16.
IndexedDBKey(u"\x4f60\x597d "),
// Invalid UTF16. The first character is a truncated UTF-16 character.
IndexedDBKey(u"\xd800\x597d"),
IndexedDBKey(std::u16string(kJunkString)),
IndexedDBKey(std::string()),
IndexedDBKey(std::string("\x01")),
IndexedDBKey(std::string("\x01\x01")),
IndexedDBKey(std::string("\x01\x02")),
IndexedDBKey(std::string("\x02")),
IndexedDBKey(std::string("\x02\x01")),
IndexedDBKey(std::string("\x02\x02")),
IndexedDBKey(std::string("\xff")),
CreateArrayIDBKey(),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Number)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Date)),
CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Date),
IndexedDBKey(0, blink::mojom::IDBKeyType::Date)),
CreateArrayIDBKey(IndexedDBKey(u"")),
CreateArrayIDBKey(IndexedDBKey(u""), IndexedDBKey(u"a")),
CreateArrayIDBKey(CreateArrayIDBKey()),
CreateArrayIDBKey(CreateArrayIDBKey(), CreateArrayIDBKey()),
CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey())),
CreateArrayIDBKey(
CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey()))),
});
for (size_t i = 0; i < keys.size(); ++i) {
const IndexedDBKey& key_a = keys[i];
std::string encoded_a = EncodeSortableIDBKey(key_a);
EXPECT_TRUE(encoded_a.size());
ASSERT_TRUE(DecodeSortableIDBKey(encoded_a).IsValid());
EXPECT_TRUE(DecodeSortableIDBKey(encoded_a).Equals(key_a));
if (i == keys.size() - 1) {
break;
}
const IndexedDBKey& key_b = keys[i + 1];
SCOPED_TRACE(testing::Message() << "Comparing keys " << key_a.DebugString()
<< " and " << key_b.DebugString());
EXPECT_TRUE(key_a.IsLessThan(key_b));
std::string encoded_b = EncodeSortableIDBKey(key_b);
EXPECT_TRUE(encoded_b.size());
auto sqlite_compare = [](const std::string& a, const std::string& b) {
return std::memcmp(a.c_str(), b.c_str(),
std::min(a.length(), b.length()));
};
EXPECT_LT(sqlite_compare(encoded_a, encoded_b), 0);
EXPECT_GT(sqlite_compare(encoded_b, encoded_a), 0);
EXPECT_EQ(sqlite_compare(encoded_a, encoded_a), 0);
EXPECT_EQ(sqlite_compare(encoded_b, encoded_b), 0);
}
std::vector<IndexedDBKey> keys_vec;
for (const auto& key : keys) {
keys_vec.emplace_back(key.Clone());
}
// Also test decoding by treating all test cases as one massive array key.
const IndexedDBKey all_keys_key(std::move(keys_vec));
std::string encoded = EncodeSortableIDBKey(all_keys_key);
IndexedDBKey decoded_value = DecodeSortableIDBKey(encoded);
ASSERT_TRUE(decoded_value.IsValid());
EXPECT_TRUE(all_keys_key.Equals(decoded_value))
<< "Original is\n"
<< all_keys_key.DebugString() << "\nwhereas depickled version is\n"
<< decoded_value.DebugString();
}
TEST(IndexedDBLevelDBCodingTest, DecodeSortableWithCorruption) {
std::vector<std::string> cases = {
// Empty string.
{},
// Binary with bad meta-mark.
{"\x40\x02\xff\x00", 4},
// String with bad meta-mark.
{"\x30\x00\x02\xff\xff\x00\x00", 7},
// Array without terminating sentinel.
{"\x50\x20\xff\xff\xff\xff", 6},
// String with no terminating sentinel.
{"\x30\x00\x01\xff\xff", 5},
// Double with insufficient bytes.
{"\x10\x00\x01\xff", 4},
};
for (const auto& test_case : cases) {
EXPECT_FALSE(DecodeSortableIDBKey(test_case).IsValid());
}
}
// Verify that encoded doubles compare in the same order as C++ double
// arithmetic.
TEST(IndexedDBLevelDBCodingTest, EncodeSortableDoubles) {
std::vector<double> values = {
0.0,
-0.0,
1.0,
-1.0,
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::lowest(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::min(),
-std::numeric_limits<double>::min(),
std::numeric_limits<double>::min() * 10,
-std::numeric_limits<double>::min() * 10,
std::numeric_limits<double>::denorm_min(),
-std::numeric_limits<double>::denorm_min(),
std::numeric_limits<double>::denorm_min() * 10,
-std::numeric_limits<double>::denorm_min() * 10,
};
for (double value_a : values) {
for (double value_b : values) {
SCOPED_TRACE(testing::Message()
<< "Comparing " << value_a << " and " << value_b);
std::string encoded_a = EncodeSortableIDBKey(
IndexedDBKey(value_a, blink::mojom::IDBKeyType::Number));
EXPECT_TRUE(encoded_a.size());
std::string encoded_b = EncodeSortableIDBKey(
IndexedDBKey(value_b, blink::mojom::IDBKeyType::Number));
EXPECT_TRUE(encoded_b.size());
EXPECT_EQ(encoded_a.size(), encoded_b.size());
auto sqlite_compare = [](const std::string& a, const std::string& b) {
return std::memcmp(a.c_str(), b.c_str(),
std::min(a.length(), b.length()));
};
if (value_a < value_b) {
EXPECT_LT(sqlite_compare(encoded_a, encoded_b), 0);
} else if (value_a == value_b) {
EXPECT_EQ(sqlite_compare(encoded_a, encoded_b), 0);
} else {
EXPECT_GT(sqlite_compare(encoded_a, encoded_b), 0);
}
}
}
for (double value : values) {
const IndexedDBKey key(value, blink::mojom::IDBKeyType::Number);
std::string encoded = EncodeSortableIDBKey(key);
IndexedDBKey decoded_value = DecodeSortableIDBKey(encoded);
ASSERT_TRUE(decoded_value.IsValid());
EXPECT_TRUE(key.Equals(decoded_value))
<< "Original is\n"
<< key.DebugString() << "\nwhereas depickled version is\n"
<< decoded_value.DebugString();
}
}
TEST(IndexedDBLevelDBCodingTest, ComparisonTest) {
std::vector<std::string> keys = {
SchemaVersionKey::Encode(),
MaxDatabaseIdKey::Encode(),
DatabaseFreeListKey::Encode(0),
DatabaseFreeListKey::EncodeMaxKey(),
DatabaseNameKey::Encode("", u""),
DatabaseNameKey::Encode("", u"a"),
DatabaseNameKey::Encode("a", u"a"),
DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::ORIGIN_NAME),
DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::DATABASE_NAME),
DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_STRING_VERSION),
DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID),
DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_VERSION),
ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::NAME),
ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::KEY_PATH),
ObjectStoreMetaDataKey::Encode(1, 1,
ObjectStoreMetaDataKey::AUTO_INCREMENT),
ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::EVICTABLE),
ObjectStoreMetaDataKey::Encode(1, 1,
ObjectStoreMetaDataKey::LAST_VERSION),
ObjectStoreMetaDataKey::Encode(1, 1,
ObjectStoreMetaDataKey::MAX_INDEX_ID),
ObjectStoreMetaDataKey::Encode(1, 1,
ObjectStoreMetaDataKey::HAS_KEY_PATH),
ObjectStoreMetaDataKey::Encode(
1, 1, ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER),
ObjectStoreMetaDataKey::EncodeMaxKey(1, 1),
ObjectStoreMetaDataKey::EncodeMaxKey(1, 2),
ObjectStoreMetaDataKey::EncodeMaxKey(1),
IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::NAME),
IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::UNIQUE),
IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::KEY_PATH),
IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::MULTI_ENTRY),
IndexMetaDataKey::Encode(1, 1, 31, 0),
IndexMetaDataKey::Encode(1, 1, 31, 1),
IndexMetaDataKey::EncodeMaxKey(1, 1, 31),
IndexMetaDataKey::EncodeMaxKey(1, 1, 32),
IndexMetaDataKey::EncodeMaxKey(1, 1),
IndexMetaDataKey::EncodeMaxKey(1, 2),
ObjectStoreFreeListKey::Encode(1, 1),
ObjectStoreFreeListKey::EncodeMaxKey(1),
IndexFreeListKey::Encode(1, 1, kMinimumIndexId),
IndexFreeListKey::EncodeMaxKey(1, 1),
IndexFreeListKey::Encode(1, 2, kMinimumIndexId),
IndexFreeListKey::EncodeMaxKey(1, 2),
ObjectStoreNamesKey::Encode(1, u""),
ObjectStoreNamesKey::Encode(1, u"a"),
IndexNamesKey::Encode(1, 1, u""),
IndexNamesKey::Encode(1, 1, u"a"),
IndexNamesKey::Encode(1, 2, u"a"),
ObjectStoreDataKey::Encode(1, 1, std::string()),
ObjectStoreDataKey::Encode(1, 1, MinIDBKey()),
ObjectStoreDataKey::Encode(1, 1, MaxIDBKey()),
ExistsEntryKey::Encode(1, 1, std::string()),
ExistsEntryKey::Encode(1, 1, MinIDBKey()),
ExistsEntryKey::Encode(1, 1, MaxIDBKey()),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), std::string(), 0),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 1),
IndexDataKey::Encode(1, 1, 31, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 2, 30, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::EncodeMaxKey(1, 2, std::numeric_limits<int32_t>::max() - 1),
};
for (size_t i = 0; i < keys.size(); ++i) {
EXPECT_EQ(Compare(keys[i], keys[i], false), 0);
for (size_t j = i + 1; j < keys.size(); ++j) {
EXPECT_LT(Compare(keys[i], keys[j], false), 0);
EXPECT_GT(Compare(keys[j], keys[i], false), 0);
}
}
}
TEST(IndexedDBLevelDBCodingTest, IndexDataKeyEncodeDecode) {
std::vector<std::string> keys = {
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, IndexedDBKey(u"user key"),
IndexedDBKey(u"primary key")),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 1),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 0),
IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 1),
IndexDataKey::Encode(1, 1, 31, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::Encode(1, 2, 30, MinIDBKey(), MinIDBKey(), 0),
IndexDataKey::EncodeMaxKey(1, 2, std::numeric_limits<int32_t>::max() - 1),
};
std::vector<IndexDataKey> obj_keys;
for (const std::string& key : keys) {
std::string_view piece(key);
IndexDataKey obj_key;
EXPECT_TRUE(IndexDataKey::Decode(&piece, &obj_key));
obj_keys.push_back(std::move(obj_key));
}
for (size_t i = 0; i < keys.size(); ++i) {
EXPECT_EQ(keys[i], obj_keys[i].Encode()) << "key at " << i;
}
}
TEST(IndexedDBLevelDBCodingTest, EncodeVarIntVSEncodeByteTest) {
std::vector<unsigned char> test_cases = {0, 1, 127};
for (size_t i = 0; i < test_cases.size(); ++i) {
unsigned char n = test_cases[i];
std::string a = WrappedEncodeByte(n);
std::string b;
EncodeVarInt(static_cast<int64_t>(n), &b);
EXPECT_EQ(a.size(), b.size());
EXPECT_EQ(*a.begin(), *b.begin());
}
}
} // namespace
} // namespace content::indexed_db