// 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.

#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_
#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "content/common/content_export.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"

namespace content::indexed_db {

// 0 - Initial version.
// 1 - Adds UserIntVersion to DatabaseMetaData.
// 2 - Adds DataVersion to to global metadata.
// 3 - Adds metadata needed for blob support.
// 4 - Adds size & last_modified to 'file' blob_info encodings.
// 5 - One time verification that blob files exist on disk.
inline constexpr int64_t kLatestKnownSchemaVersion = 5;
// Migration from version 2 to 3 occurred in 2014, and migration to version 4
// began in early 2020, so we currently continue to support schema that are as
// old as 2014.
inline constexpr int64_t kEarliestSupportedSchemaVersion = 3;

inline constexpr unsigned char kMinimumIndexId = 30;

CONTENT_EXPORT std::string MaxIDBKey();
CONTENT_EXPORT std::string MinIDBKey();

// DatabaseId, BlobNumber
using BlobJournalEntryType = std::pair<int64_t, int64_t>;
using BlobJournalType = std::vector<BlobJournalEntryType>;

CONTENT_EXPORT void EncodeByte(unsigned char value, std::string* into);
CONTENT_EXPORT void EncodeBool(bool value, std::string* into);

// Unlike EncodeVarInt, this is a 'dumb' implementation of a variable int
// encoder. It writes, little-endian', until there are no more '1' bits in the
// number. The Decoder must know how to calculate the size of the encoded int,
// typically by having this reside at the end of the value or key.
CONTENT_EXPORT void EncodeInt(int64_t value, std::string* into);
CONTENT_EXPORT void EncodeString(const std::u16string& value,
                                 std::string* into);
CONTENT_EXPORT void EncodeStringWithLength(const std::u16string& value,
                                           std::string* into);
CONTENT_EXPORT void EncodeBinary(const std::string& value, std::string* into);
CONTENT_EXPORT void EncodeBinary(base::span<const uint8_t> value,
                                 std::string* into);
CONTENT_EXPORT void EncodeDouble(double value, std::string* into);
// This version will CHECK if encoding fails. This is generally preferred to
// handling an error.
CONTENT_EXPORT void EncodeIDBKey(const blink::IndexedDBKey& value,
                                 std::string* into);
// This version will return `true` on success. It exists so tests can run
// without crashing.
[[nodiscard]] CONTENT_EXPORT bool MaybeEncodeIDBKey(
    const blink::IndexedDBKey& value,
    std::string* into);
// This function creates a byte stream that can be directly compared to other
// byte streams on a byte-by-byte basis and retain semantic ordering. This
// enables the value to be stored as a SQLite blob without a specialized
// collation operation. Unlike `EncodeIDBKey`, which makes use of length bytes,
// this operation re-encodes variable-length values in a way that supports
// sentinels.
CONTENT_EXPORT std::string EncodeSortableIDBKey(
    const blink::IndexedDBKey& value);
CONTENT_EXPORT void EncodeIDBKeyPath(const blink::IndexedDBKeyPath& value,
                                     std::string* into);
CONTENT_EXPORT void EncodeBlobJournal(const BlobJournalType& journal,
                                      std::string* into);

[[nodiscard]] CONTENT_EXPORT bool DecodeByte(std::string_view* slice,
                                             unsigned char* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeBool(std::string_view* slice,
                                             bool* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeInt(std::string_view* slice,
                                            int64_t* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeString(std::string_view* slice,
                                               std::u16string* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeStringWithLength(
    std::string_view* slice,
    std::u16string* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeBinary(std::string_view* slice,
                                               std::string* value);
// The returned span is only valid as long as the date behind |slice| is
// still valid.
[[nodiscard]] CONTENT_EXPORT bool DecodeBinary(
    std::string_view* slice,
    base::span<const uint8_t>* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeDouble(std::string_view* slice,
                                               double* value);
// Will return an invalid key if deserialization fails.
[[nodiscard]] CONTENT_EXPORT blink::IndexedDBKey DecodeIDBKey(
    std::string_view* slice);
// Will return an invalid key if deserialization fails.
[[nodiscard]] CONTENT_EXPORT blink::IndexedDBKey DecodeSortableIDBKey(
    std::string_view serialized);
[[nodiscard]] CONTENT_EXPORT bool DecodeIDBKeyPath(
    std::string_view* slice,
    blink::IndexedDBKeyPath* value);
[[nodiscard]] CONTENT_EXPORT bool DecodeBlobJournal(std::string_view* slice,
                                                    BlobJournalType* journal);

CONTENT_EXPORT int CompareEncodedStringsWithLength(std::string_view* slice1,
                                                   std::string_view* slice2,
                                                   bool* ok);

[[nodiscard]] CONTENT_EXPORT bool ExtractEncodedIDBKey(std::string_view* slice,
                                                       std::string* result);

CONTENT_EXPORT int CompareEncodedIDBKeys(std::string_view* slice1,
                                         std::string_view* slice2,
                                         bool* ok);

CONTENT_EXPORT int Compare(std::string_view a,
                           std::string_view b,
                           bool index_keys);

CONTENT_EXPORT int CompareKeys(std::string_view a, std::string_view b);

CONTENT_EXPORT int CompareIndexKeys(std::string_view a, std::string_view b);

// Logging support.
std::string IndexedDBKeyToDebugString(std::string_view key);

// TODO(dmurph): Modify all decoding methods to return something more sensible,
// as it is not obvious that they modify the input slice to remove the decoded
// bit. https://crbug.com/922225
class KeyPrefix {
 public:
  // These are serialized to disk; any new items must be appended, and none can
  // be deleted.
  enum Type {
    GLOBAL_METADATA = 0,
    DATABASE_METADATA = 1,
    OBJECT_STORE_DATA = 2,
    EXISTS_ENTRY = 3,
    INDEX_DATA = 4,
    INVALID_TYPE = 5,
    BLOB_ENTRY = 6
  };

  static const size_t kMaxDatabaseIdSizeBits = 3;
  static const size_t kMaxObjectStoreIdSizeBits = 3;
  static const size_t kMaxIndexIdSizeBits = 2;

  static const size_t kMaxDatabaseIdSizeBytes = 1ULL
                                                << kMaxDatabaseIdSizeBits;  // 8
  static const size_t kMaxObjectStoreIdSizeBytes =
      1ULL << kMaxObjectStoreIdSizeBits;                                   // 8
  static const size_t kMaxIndexIdSizeBytes = 1ULL << kMaxIndexIdSizeBits;  // 4

  static const size_t kMaxDatabaseIdBits =
      kMaxDatabaseIdSizeBytes * 8 - 1;  // 63
  static const size_t kMaxObjectStoreIdBits =
      kMaxObjectStoreIdSizeBytes * 8 - 1;                              // 63
  static const size_t kMaxIndexIdBits = kMaxIndexIdSizeBytes * 8 - 1;  // 31

  static const int64_t kMaxDatabaseId =
      (1ULL << kMaxDatabaseIdBits) - 1;  // max signed int64_t
  static const int64_t kMaxObjectStoreId =
      (1ULL << kMaxObjectStoreIdBits) - 1;  // max signed int64_t
  static const int64_t kMaxIndexId =
      (1ULL << kMaxIndexIdBits) - 1;  // max signed int32_t

  CONTENT_EXPORT static const int64_t kInvalidId;

  KeyPrefix();
  explicit KeyPrefix(int64_t database_id);
  KeyPrefix(int64_t database_id, int64_t object_store_id);
  KeyPrefix(int64_t database_id, int64_t object_store_id, int64_t index_id);
  static KeyPrefix CreateWithSpecialIndex(int64_t database_id,
                                          int64_t object_store_id,
                                          int64_t index_id);

  static bool Decode(std::string_view* slice, KeyPrefix* result);
  std::string Encode() const;
  CONTENT_EXPORT static std::string EncodeEmpty();
  int Compare(const KeyPrefix& other) const;

  CONTENT_EXPORT static bool IsValidDatabaseId(int64_t database_id);
  static bool IsValidObjectStoreId(int64_t index_id);
  static bool IsValidIndexId(int64_t index_id);
  static bool ValidIds(int64_t database_id,
                       int64_t object_store_id,
                       int64_t index_id) {
    return IsValidDatabaseId(database_id) &&
           IsValidObjectStoreId(object_store_id) && IsValidIndexId(index_id);
  }
  static bool ValidIds(int64_t database_id, int64_t object_store_id) {
    return IsValidDatabaseId(database_id) &&
           IsValidObjectStoreId(object_store_id);
  }

  std::string DebugString();

  // Returns false if `this` cannot have been created by encoding a valid key by
  // any means. This can only evaluate to true due to disk corruption. Note that
  // this is not baked into `Decode()` simply because these extra checks can
  // have a performance impact, which would anyway be redundant with checks made
  // by many of the callers of `Decode()`.
  bool CanBeValid() const;

  // The KeyPrefix for any record stored in the DB should always evaluate to
  // some `type`, but ones that are just used for range bounds and therefore not
  // stored (such as that for `BlobEntryKey::EncodeStopKeyForOrigin()`) may not
  // have a valid type.
  std::optional<Type> MaybeType() const;

  int64_t database_id_;
  int64_t object_store_id_;
  int64_t index_id_;

 private:
  FRIEND_TEST_ALL_PREFIXES(IndexedDBLevelDBCodingTest, Empty);

  CONTENT_EXPORT static std::string EncodeInternal(int64_t database_id,
                                                   int64_t object_store_id,
                                                   int64_t index_id);
};

class SchemaVersionKey {
 public:
  CONTENT_EXPORT static std::string Encode();
};

class MaxDatabaseIdKey {
 public:
  CONTENT_EXPORT static std::string Encode();
};

class DataVersionKey {
 public:
  CONTENT_EXPORT static std::string Encode();
};

class RecoveryBlobJournalKey {
 public:
  static std::string Encode();
};

class ActiveBlobJournalKey {
 public:
  static std::string Encode();
};

class EarliestSweepKey {
 public:
  static std::string Encode();
};

class EarliestCompactionKey {
 public:
  static std::string Encode();
};

class ScopesPrefix {
 public:
  CONTENT_EXPORT static std::vector<uint8_t> Encode();
};

class DatabaseFreeListKey {
 public:
  DatabaseFreeListKey();
  static bool Decode(std::string_view* slice, DatabaseFreeListKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id);
  static CONTENT_EXPORT std::string EncodeMaxKey();
  int Compare(const DatabaseFreeListKey& other) const;
  std::string DebugString() const;

 private:
  int64_t database_id_;
};

class DatabaseNameKey {
 public:
  static bool Decode(std::string_view* slice, DatabaseNameKey* result);
  CONTENT_EXPORT static std::string Encode(const std::string& origin_identifier,
                                           const std::u16string& database_name);
  static std::string EncodeMinKeyForOrigin(
      const std::string& origin_identifier);
  static std::string EncodeStopKeyForOrigin(
      const std::string& origin_identifier);
  std::u16string origin() const { return origin_; }
  std::u16string database_name() const { return database_name_; }
  int Compare(const DatabaseNameKey& other);
  std::string DebugString() const;

 private:
  std::u16string origin_;  // TODO(jsbell): Store encoded strings, or just
                           // pointers.
  std::u16string database_name_;
};

class DatabaseMetaDataKey {
 public:
  enum MetaDataType {
    ORIGIN_NAME = 0,
    DATABASE_NAME = 1,
    USER_STRING_VERSION = 2,  // Obsolete
    MAX_OBJECT_STORE_ID = 3,
    USER_VERSION = 4,
    BLOB_KEY_GENERATOR_CURRENT_NUMBER = 5,
    MAX_SIMPLE_METADATA_TYPE = 6
  };

  CONTENT_EXPORT static const int64_t kAllBlobsNumber;
  CONTENT_EXPORT static const int64_t kBlobNumberGeneratorInitialNumber;
  // All keys <= 0 are invalid.  This one's just a convenient example.
  static const int64_t kInvalidBlobNumber;

  CONTENT_EXPORT static bool IsValidBlobNumber(int64_t blob_number);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           MetaDataType type);
};

class ObjectStoreMetaDataKey {
 public:
  enum MetaDataType {
    NAME = 0,
    KEY_PATH = 1,
    AUTO_INCREMENT = 2,
    EVICTABLE = 3,
    LAST_VERSION = 4,
    MAX_INDEX_ID = 5,
    HAS_KEY_PATH = 6,
    KEY_GENERATOR_CURRENT_NUMBER = 7
  };

  // From the IndexedDB specification.
  static const int64_t kKeyGeneratorInitialNumber;

  ObjectStoreMetaDataKey();
  static bool Decode(std::string_view* slice, ObjectStoreMetaDataKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           unsigned char meta_data_type);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id,
                                                 int64_t object_store_id);
  int64_t ObjectStoreId() const;
  unsigned char MetaDataType() const;
  int Compare(const ObjectStoreMetaDataKey& other);
  std::string DebugString() const;

 private:
  int64_t object_store_id_;
  unsigned char meta_data_type_;
};

class IndexMetaDataKey {
 public:
  enum MetaDataType { NAME = 0, UNIQUE = 1, KEY_PATH = 2, MULTI_ENTRY = 3 };

  IndexMetaDataKey();
  static bool Decode(std::string_view* slice, IndexMetaDataKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           int64_t index_id,
                                           unsigned char meta_data_type);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id,
                                                 int64_t object_store_id);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id,
                                                 int64_t object_store_id,
                                                 int64_t index_id);
  int Compare(const IndexMetaDataKey& other);
  std::string DebugString() const;

  int64_t IndexId() const;
  unsigned char meta_data_type() const { return meta_data_type_; }

 private:
  int64_t object_store_id_;
  int64_t index_id_;
  unsigned char meta_data_type_;
};

class ObjectStoreFreeListKey {
 public:
  ObjectStoreFreeListKey();
  static bool Decode(std::string_view* slice, ObjectStoreFreeListKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id);
  int Compare(const ObjectStoreFreeListKey& other);
  std::string DebugString() const;

 private:
  int64_t object_store_id_;
};

class IndexFreeListKey {
 public:
  IndexFreeListKey();
  static bool Decode(std::string_view* slice, IndexFreeListKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           int64_t index_id);
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id,
                                                 int64_t object_store_id);
  int Compare(const IndexFreeListKey& other);
  std::string DebugString() const;

 private:
  int64_t object_store_id_;
  int64_t index_id_;
};

class ObjectStoreNamesKey {
 public:
  // TODO(jsbell): We never use this to look up object store ids,
  // because a mapping is kept in the Database. Can the
  // mapping become unreliable?  Can we remove this?
  static bool Decode(std::string_view* slice, ObjectStoreNamesKey* result);
  CONTENT_EXPORT static std::string Encode(
      int64_t database_id,
      const std::u16string& object_store_name);
  int Compare(const ObjectStoreNamesKey& other);
  std::string DebugString() const;

  std::u16string object_store_name() const { return object_store_name_; }

 private:
  // TODO(jsbell): Store the encoded string, or just pointers to it.
  std::u16string object_store_name_;
};

class IndexNamesKey {
 public:
  IndexNamesKey();
  // TODO(jsbell): We never use this to look up index ids, because a mapping
  // is kept at a higher level.
  static bool Decode(std::string_view* slice, IndexNamesKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           const std::u16string& index_name);
  int Compare(const IndexNamesKey& other);
  std::string DebugString() const;

  std::u16string index_name() const { return index_name_; }

 private:
  int64_t object_store_id_;
  std::u16string index_name_;
};

class ObjectStoreDataKey {
 public:
  static const int64_t kSpecialIndexNumber;

  ObjectStoreDataKey();
  ~ObjectStoreDataKey();

  static bool Decode(std::string_view* slice, ObjectStoreDataKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           const std::string& encoded_user_key);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           const blink::IndexedDBKey& user_key);
  std::string DebugString() const;

  blink::IndexedDBKey DecodeUserKey() const;

 private:
  std::string encoded_user_key_;
};

class ExistsEntryKey {
 public:
  ExistsEntryKey();

  ExistsEntryKey(const ExistsEntryKey&) = delete;
  ExistsEntryKey& operator=(const ExistsEntryKey&) = delete;

  ~ExistsEntryKey();

  static bool Decode(std::string_view* slice, ExistsEntryKey* result);
  CONTENT_EXPORT static std::string Encode(int64_t database_id,
                                           int64_t object_store_id,
                                           const std::string& encoded_key);
  static std::string Encode(int64_t database_id,
                            int64_t object_store_id,
                            const blink::IndexedDBKey& user_key);
  std::string DebugString() const;

  blink::IndexedDBKey DecodeUserKey() const;

 private:
  static const int64_t kSpecialIndexNumber;

  std::string encoded_user_key_;
};

class CONTENT_EXPORT BlobEntryKey {
 public:
  BlobEntryKey() : database_id_(0), object_store_id_(0) {}
  static bool Decode(std::string_view* slice, BlobEntryKey* result);
  static bool FromObjectStoreDataKey(std::string_view* slice,
                                     BlobEntryKey* result);
  static std::string ReencodeToObjectStoreDataKey(std::string_view* slice);
  static std::string EncodeMinKeyForObjectStore(int64_t database_id,
                                                int64_t object_store_id);
  static std::string EncodeStopKeyForObjectStore(int64_t database_id,
                                                 int64_t object_store_id);
  static std::string Encode(int64_t database_id,
                            int64_t object_store_id,
                            const blink::IndexedDBKey& user_key);
  std::string Encode() const;
  std::string DebugString() const;

  int64_t database_id() const { return database_id_; }
  int64_t object_store_id() const { return object_store_id_; }

 private:
  static const int64_t kSpecialIndexNumber;

  static std::string Encode(int64_t database_id,
                            int64_t object_store_id,
                            const std::string& encoded_user_key);
  int64_t database_id_;
  int64_t object_store_id_;
  // This is the user's ObjectStoreDataKey, not the BlobEntryKey itself.
  std::string encoded_user_key_;
};

class IndexDataKey {
 public:
  CONTENT_EXPORT IndexDataKey();
  CONTENT_EXPORT IndexDataKey(IndexDataKey&& other);

  IndexDataKey(const IndexDataKey&) = delete;
  IndexDataKey& operator=(const IndexDataKey&) = delete;

  CONTENT_EXPORT ~IndexDataKey();

  CONTENT_EXPORT static bool Decode(std::string_view* slice,
                                    IndexDataKey* result);
  CONTENT_EXPORT static std::string Encode(
      int64_t database_id,
      int64_t object_store_id,
      int64_t index_id,
      const std::string_view encoded_user_key,
      const std::string_view encoded_primary_key,
      int64_t sequence_number);
  static std::string Encode(int64_t database_id,
                            int64_t object_store_id,
                            int64_t index_id,
                            const blink::IndexedDBKey& user_key);
  CONTENT_EXPORT static std::string Encode(
      int64_t database_id,
      int64_t object_store_id,
      int64_t index_id,
      const blink::IndexedDBKey& user_key,
      const blink::IndexedDBKey& user_primary_key);
  CONTENT_EXPORT static std::string EncodeMinKey(int64_t database_id,
                                                 int64_t object_store_id,
                                                 int64_t index_id);

  // An index's keys are guaranteed to fall in [EncodeMinKey(), EncodeMaxKey()]
  CONTENT_EXPORT static std::string EncodeMaxKey(int64_t database_id,
                                                 int64_t object_store_id,
                                                 int64_t index_id);
  int64_t DatabaseId() const;
  int64_t ObjectStoreId() const;
  int64_t IndexId() const;
  blink::IndexedDBKey DecodeUserKey() const;
  blink::IndexedDBKey DecodePrimaryKey() const;

  CONTENT_EXPORT std::string Encode() const;

  std::string DebugString() const;

 private:
  int64_t database_id_;
  int64_t object_store_id_;
  int64_t index_id_;
  std::string encoded_user_key_;
  std::string encoded_primary_key_;
  int64_t sequence_number_;
};

}  // namespace content::indexed_db

#endif  // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_
