/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DOM_QUOTA_ENCRYPTEDRANDOMACCESSBLOCK_H_
#define DOM_QUOTA_ENCRYPTEDRANDOMACCESSBLOCK_H_

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>

#include "mozilla/Span.h"
#include "nsTArray.h"

namespace mozilla::dom::quota {

/**
 * On-disk layout of a single encrypted block (BlockSize = 4096 bytes):
 *
 *  --------+-----------------------------------------------+
 *   offset | field                                 size    |
 *  --------+-----------------------------------------------+
 *       0  | Version (uint16_t, little-endian)    2 bytes  |     Header
 *       2  | Reserved (zeroed)                   30 bytes  |     plaintext
 *  --------+-----------------------------------------------+
 *      32  | CipherMetadata                      32 bytes  |     plaintext
 *  --------+-----------------------------------------------+
 *      64  | CipherPayload                     4032 bytes  |     ciphertext
 *  --------+-----------------------------------------------+
 *    4096
 *
 *  This class only exposes the on-disk (= ciphertext-side) byte regions
 *  above. The internal layout of CipherMetadata and of the decrypted
 *  CipherPayload is the responsibility of separate view classes:
 *
 *    - CipherMetadata: version-specific. See
 *      EncryptedRandomAccessBlockCipherMetadataViewV1 (for V1).
 *
 *    - Decrypted CipherPayload: version-independent. See
 *      DecryptedRandomAccessBlockCipherPayloadView. The view is applied
 *      to the decrypted plaintext (held in a separate buffer by the
 *      caller), not to the encrypted bytes stored in this block.
 */
class EncryptedRandomAccessBlock {
 public:
  static constexpr size_t BlockSize = 4096;

  using VersionType = uint16_t;
  static constexpr VersionType kEncryptedRandomAccessBlockLatestVersion = 1;

  template <size_t N>
  using ConstSpan = Span<const uint8_t, N>;
  template <size_t N>
  using MutableSpan = Span<uint8_t, N>;

  explicit EncryptedRandomAccessBlock(
      VersionType aVersion = kEncryptedRandomAccessBlockLatestVersion) {
    mData.SetLength(BlockSize);

    // Zero-initialize the whole block so that reserved/unused bytes do not
    // expose stale data. This follows the same rationale as EncryptedBlock
    // (Bug 1867394).
    std::fill(mData.begin(), mData.end(), 0);

    SetVersion(aVersion);
  }

  static constexpr size_t CipherMetadataSize = 32;

  static constexpr size_t HeaderSize = 32;

 private:
  static constexpr size_t VersionSize = sizeof(VersionType);
  static_assert(VersionSize == 2, "Version should take 2 bytes on disk.");

 public:
  ConstSpan<HeaderSize> Header() const {
    return WholeBlock().Subspan<0, HeaderSize>();
  }

  VersionType Version() const {
    VersionType version = std::numeric_limits<VersionType>::max();
    memcpy(&version, mData.Elements(), VersionSize);
    return version;
  }

  void SetVersion(VersionType aVersion) {
    memcpy(mData.Elements(), &aVersion, VersionSize);
  }

  ConstSpan<HeaderSize - VersionSize> ReservedBytes() const {
    return WholeBlock().Subspan<VersionSize, HeaderSize - VersionSize>();
  }
  static_assert(HeaderSize - VersionSize == 30,
                "Reserved region should take 30 bytes on disk.");

  ConstSpan<CipherMetadataSize> CipherMetadata() const {
    return WholeBlock().Subspan<HeaderSize, CipherMetadataSize>();
  }

  MutableSpan<CipherMetadataSize> MutableCipherMetadata() {
    return MutableWholeBlock().Subspan<HeaderSize, CipherMetadataSize>();
  }

  static constexpr size_t CipherPayloadSize =
      BlockSize - HeaderSize - CipherMetadataSize;
  static_assert(CipherPayloadSize == 4032,
                "CipherPayload should take 4032 bytes on disk.");

  ConstSpan<CipherPayloadSize> CipherPayload() const {
    return WholeBlock()
        .Subspan<HeaderSize + CipherMetadataSize, CipherPayloadSize>();
  }

  MutableSpan<CipherPayloadSize> MutableCipherPayload() {
    return MutableWholeBlock()
        .Subspan<HeaderSize + CipherMetadataSize, CipherPayloadSize>();
  }

  void AssignFromBytes(ConstSpan<BlockSize> aData) {
    memcpy(mData.Elements(), aData.data(), BlockSize);
  }

  ConstSpan<BlockSize> WholeBlock() const { return mData; }

  MutableSpan<BlockSize> MutableWholeBlock() { return mData; }

 private:
  nsTArray<uint8_t> mData;
};

}  // namespace mozilla::dom::quota

#endif  // DOM_QUOTA_ENCRYPTEDRANDOMACCESSBLOCK_H_
