blob: 1956883476fdb225944e223cd98f6b0801633aba [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include <lib/zx/status.h>
#include <zircon/errors.h>
#include <limits>
#include <memory>
#include <blobfs/blob-layout.h>
#include <blobfs/format.h>
#include <digest/merkle-tree.h>
#include <digest/node-digest.h>
#include <fbl/algorithm.h>
#include <safemath/checked_math.h>
namespace blobfs {
namespace {
using ByteCountType = BlobLayout::ByteCountType;
using BlockCountType = BlobLayout::BlockCountType;
using BlockSizeType = BlobLayout::BlockSizeType;
// Rounds up |byte_count| to the next multiple of |blobfs_block_size|.
// Returns an error if the result doesn't fit in |ByteCountType|.
zx::status<ByteCountType> RoundUpToBlockMultiple(ByteCountType byte_count,
BlockSizeType blobfs_block_size) {
ByteCountType max_byte_count = std::numeric_limits<ByteCountType>::max() - blobfs_block_size + 1;
if (byte_count > max_byte_count) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(fbl::round_up(byte_count, blobfs_block_size));
}
// Returns the minimum number of blocks required to hold |byte_count| bytes.
// Returns an error if the result doesn't fit in |BlockCountType|.
zx::status<BlockCountType> BlocksRequiredForBytes(ByteCountType byte_count,
BlockSizeType blobfs_block_size) {
BlockCountType block_count;
auto block_aligned_byte_count = RoundUpToBlockMultiple(byte_count, blobfs_block_size);
if (block_aligned_byte_count.is_error()) {
return block_aligned_byte_count.take_error();
}
if (!safemath::CheckDiv(block_aligned_byte_count.value(), blobfs_block_size)
.AssignIfValid(&block_count)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(block_count);
}
// Returns the maxmimum number of bytes that can fit in |block_count| blocks.
ByteCountType BytesThatFitInBlocks(BlockCountType block_count, BlockSizeType blobfs_block_size) {
return ByteCountType{block_count} * blobfs_block_size;
}
class CompactMerkleTreeAtEndBlobLayout : public BlobLayout {
public:
CompactMerkleTreeAtEndBlobLayout(ByteCountType file_size, ByteCountType data_size,
ByteCountType merkle_tree_size, BlockSizeType blobfs_block_size)
: BlobLayout(file_size, data_size, merkle_tree_size, blobfs_block_size) {}
zx::status<BlockCountType> DataBlockOffset() const override { return zx::ok(0); }
zx::status<BlockCountType> MerkleTreeBlockOffset() const override {
// The Merkle tree is aligned to end at the end of the blob. The Merkle tree's starting block
// is the total number of blocks in the blob minus the number of blocks that the Merkle tree
// spans.
auto total_block_count = TotalBlockCount();
if (total_block_count.is_error()) {
return total_block_count.take_error();
}
auto merkle_tree_block_count = MerkleTreeBlockCount();
if (merkle_tree_block_count.is_error()) {
return merkle_tree_block_count.take_error();
}
BlockCountType merkle_tree_starting_block;
if (!safemath::CheckSub(total_block_count.value(), merkle_tree_block_count.value())
.AssignIfValid(&merkle_tree_starting_block)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(merkle_tree_starting_block);
}
ByteCountType MerkleTreeOffsetWithinBlockOffset() const override {
// | block 6 | block 7 | block 8 | block 9 |
// |<- Merkle tree size >|
// |<- ->^ Merkle tree offset within block offset
BlockSizeType blobfs_block_size = BlobfsBlockSize();
ByteCountType merkle_tree_remainder = MerkleTreeSize() % blobfs_block_size;
// If the Merkle tree size is a block multiple then it starts at the beginning of the block.
if (merkle_tree_remainder == 0) {
return 0;
}
return blobfs_block_size - merkle_tree_remainder;
}
zx::status<BlockCountType> TotalBlockCount() const override {
ByteCountType total_size;
if (!safemath::CheckAdd(DataSizeUpperBound(), MerkleTreeSize()).AssignIfValid(&total_size)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return BlocksRequiredForBytes(total_size, BlobfsBlockSize());
}
zx::status<bool> HasMerkleTreeAndDataSharedBlock() const override {
// If the number of Merkle tree blocks + data blocks is greater than the total number of blocks
// then 1 of the blocks must be shared.
auto total_block_count = TotalBlockCount();
if (total_block_count.is_error()) {
return total_block_count.take_error();
}
auto data_block_count = DataBlockCount();
if (data_block_count.is_error()) {
return data_block_count.take_error();
}
auto merkle_tree_block_count = MerkleTreeBlockCount();
if (merkle_tree_block_count.is_error()) {
return merkle_tree_block_count.take_error();
}
BlockCountType block_sum;
if (!safemath::CheckAdd(data_block_count.value(), merkle_tree_block_count.value())
.AssignIfValid(&block_sum)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(block_sum > total_block_count.value());
}
BlobLayoutFormat Format() const override { return BlobLayoutFormat::kCompactMerkleTreeAtEnd; }
static zx::status<std::unique_ptr<CompactMerkleTreeAtEndBlobLayout>> CreateFromInode(
const Inode& inode, BlockSizeType blobfs_block_size) {
if (!inode.IsCompressed()) {
// If the blob is not compressed then the size of the stored data is the file size.
return CreateFromSizes(inode.blob_size, inode.blob_size, blobfs_block_size);
}
// The exact compressed size of a blob isn't stored. An upper bound can be determined from the
// total size of the blob minus the Merkle tree size. See fxbug.dev/44547.
ByteCountType total_size = BytesThatFitInBlocks(inode.block_count, blobfs_block_size);
ByteCountType merkle_tree_size = CalulateMerkleTreeSize(inode.blob_size);
ByteCountType data_size;
if (!safemath::CheckSub(total_size, merkle_tree_size).AssignIfValid(&data_size)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(std::make_unique<CompactMerkleTreeAtEndBlobLayout>(
inode.blob_size, data_size, merkle_tree_size, blobfs_block_size));
}
static zx::status<std::unique_ptr<CompactMerkleTreeAtEndBlobLayout>> CreateFromSizes(
ByteCountType file_size, ByteCountType data_size, BlockSizeType blobfs_block_size) {
return zx::ok(std::make_unique<CompactMerkleTreeAtEndBlobLayout>(
file_size, data_size, CalulateMerkleTreeSize(file_size), blobfs_block_size));
}
private:
static ByteCountType CalulateMerkleTreeSize(ByteCountType file_size) {
// TODO(fxbug.dev/36663): Use the compact Merkle tree format.
return digest::CalculateMerkleTreeSize(file_size, digest::kDefaultNodeSize);
}
};
class PaddedMerkleTreeAtStartBlobLayout : public BlobLayout {
public:
PaddedMerkleTreeAtStartBlobLayout(ByteCountType file_size, ByteCountType data_size,
ByteCountType merkle_tree_size, BlockSizeType blobfs_block_size)
: BlobLayout(file_size, data_size, merkle_tree_size, blobfs_block_size) {}
zx::status<BlockCountType> DataBlockOffset() const override {
// The data starts at the beginning of the block following the Merkle tree.
return MerkleTreeBlockCount();
}
zx::status<BlockCountType> MerkleTreeBlockOffset() const override { return zx::ok(0); }
ByteCountType MerkleTreeOffsetWithinBlockOffset() const override { return 0; }
zx::status<BlockCountType> TotalBlockCount() const override {
// The total block count is the sum of the data blocks and the Merkle tree blocks.
auto data_block_count = DataBlockCount();
if (data_block_count.is_error()) {
return data_block_count.take_error();
}
auto merkle_tree_block_count = MerkleTreeBlockCount();
if (merkle_tree_block_count.is_error()) {
return merkle_tree_block_count.take_error();
}
BlockCountType total_block_count;
if (!safemath::CheckAdd(data_block_count.value(), merkle_tree_block_count.value())
.AssignIfValid(&total_block_count)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(total_block_count);
}
zx::status<bool> HasMerkleTreeAndDataSharedBlock() const override { return zx::ok(false); }
BlobLayoutFormat Format() const override { return BlobLayoutFormat::kPaddedMerkleTreeAtStart; }
static zx::status<std::unique_ptr<PaddedMerkleTreeAtStartBlobLayout>> CreateFromInode(
const Inode& inode, BlockSizeType blobfs_block_size) {
if (!inode.IsCompressed()) {
// If the blob is not compressed then the size of the stored data is the file size.
return CreateFromSizes(inode.blob_size, inode.blob_size, blobfs_block_size);
}
// The exact compressed size of a blob isn't stored. An upper bound can be determined from the
// total number of blocks minus the number of Merkle tree blocks. See fxbug.dev/44547.
ByteCountType merkle_tree_size = CalculateMerkleTreeSize(inode.blob_size);
auto merkle_tree_block_count = BlocksRequiredForBytes(merkle_tree_size, blobfs_block_size);
if (merkle_tree_block_count.is_error()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
BlockCountType data_block_count;
if (!safemath::CheckSub(inode.block_count, merkle_tree_block_count.value())
.AssignIfValid(&data_block_count)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
ByteCountType data_size = BytesThatFitInBlocks(data_block_count, blobfs_block_size);
return zx::ok(std::make_unique<PaddedMerkleTreeAtStartBlobLayout>(
inode.blob_size, data_size, merkle_tree_size, blobfs_block_size));
}
static zx::status<std::unique_ptr<PaddedMerkleTreeAtStartBlobLayout>> CreateFromSizes(
ByteCountType file_size, ByteCountType data_size, BlockSizeType blobfs_block_size) {
return zx::ok(std::make_unique<PaddedMerkleTreeAtStartBlobLayout>(
file_size, data_size, CalculateMerkleTreeSize(file_size), blobfs_block_size));
}
private:
static ByteCountType CalculateMerkleTreeSize(ByteCountType file_size) {
return digest::CalculateMerkleTreeSize(file_size, digest::kDefaultNodeSize);
}
};
} // namespace
BlobLayout::BlobLayout(ByteCountType file_size, ByteCountType data_size,
ByteCountType merkle_tree_size, BlockSizeType blobfs_block_size)
: file_size_(file_size),
data_size_(data_size),
merkle_tree_size_(merkle_tree_size),
blobfs_block_size_(blobfs_block_size) {}
BlobLayout::ByteCountType BlobLayout::FileSize() const { return file_size_; }
zx::status<BlobLayout::ByteCountType> BlobLayout::FileBlockAlignedSize() const {
return RoundUpToBlockMultiple(file_size_, blobfs_block_size_);
}
BlobLayout::ByteCountType BlobLayout::DataSizeUpperBound() const { return data_size_; }
zx::status<BlobLayout::ByteCountType> BlobLayout::DataBlockAlignedSize() const {
return RoundUpToBlockMultiple(data_size_, blobfs_block_size_);
}
zx::status<BlobLayout::BlockCountType> BlobLayout::DataBlockCount() const {
return BlocksRequiredForBytes(data_size_, blobfs_block_size_);
}
BlobLayout::ByteCountType BlobLayout::MerkleTreeSize() const { return merkle_tree_size_; }
zx::status<BlobLayout::ByteCountType> BlobLayout::MerkleTreeBlockAlignedSize() const {
return RoundUpToBlockMultiple(MerkleTreeSize(), blobfs_block_size_);
}
zx::status<BlobLayout::BlockCountType> BlobLayout::MerkleTreeBlockCount() const {
return BlocksRequiredForBytes(MerkleTreeSize(), blobfs_block_size_);
}
BlobLayout::BlockSizeType BlobLayout::BlobfsBlockSize() const { return blobfs_block_size_; }
zx::status<std::unique_ptr<BlobLayout>> BlobLayout::CreateFromInode(
BlobLayoutFormat format, const Inode& inode, BlockSizeType blobfs_block_size) {
switch (format) {
case BlobLayoutFormat::kPaddedMerkleTreeAtStart: {
auto layout = PaddedMerkleTreeAtStartBlobLayout::CreateFromInode(inode, blobfs_block_size);
if (layout.is_error()) {
return layout.take_error();
}
return layout.take_value();
}
case BlobLayoutFormat::kCompactMerkleTreeAtEnd: {
auto layout = CompactMerkleTreeAtEndBlobLayout::CreateFromInode(inode, blobfs_block_size);
if (layout.is_error()) {
return layout.take_error();
}
return layout.take_value();
}
}
}
zx::status<std::unique_ptr<BlobLayout>> BlobLayout::CreateFromSizes(
BlobLayoutFormat format, ByteCountType file_size, ByteCountType data_size,
BlockSizeType blobfs_block_size) {
switch (format) {
case BlobLayoutFormat::kPaddedMerkleTreeAtStart: {
auto layout = PaddedMerkleTreeAtStartBlobLayout::CreateFromSizes(file_size, data_size,
blobfs_block_size);
if (layout.is_error()) {
return layout.take_error();
}
return layout.take_value();
}
case BlobLayoutFormat::kCompactMerkleTreeAtEnd: {
auto layout = CompactMerkleTreeAtEndBlobLayout::CreateFromSizes(file_size, data_size,
blobfs_block_size);
if (layout.is_error()) {
return layout.take_error();
}
return layout.take_value();
}
}
}
} // namespace blobfs