Files
sousa-gecko/dom/quota/EncryptedRandomAccessBlock.h
T
canalun 05ca68f9b0 Bug 1975760 - Part 8: Implement the EncryptedRandomAccessStream write path. r=jari,dom-storage-reviewers
Implement Write and WriteSegments, make Flush persist pending data, and let
Close flush the current block. A write can overwrite existing data without
changing the logical size, grow the stream by writing through the end, or write
past the end, in which case the gap between the old end and the new data is
filled with zeros.

WriteFrom is not implemented, because the other implementations like
EncryptingOutputStream don't have that. So align the behavior in this patch.

Also give |EncryptedRandomAccessBlock| a constructor that stamps the version so
a fresh block can be built on the write path, and document the stream
invariants in the class header.

Differential Revision: https://phabricator.services.mozilla.com/D305265
2026-06-30 23:10:49 +00:00

137 lines
4.5 KiB
C++

/* 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_