blob: 2cad34c09e30fd6d6c45f87477196dfd4a70cbe1 [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 "src/devices/block/drivers/block-verity/geometry.h"
#include <zircon/assert.h>
#include <cstdio>
#include <utility>
namespace block_verity {
IntegrityShape IntegrityShapeFor(uint32_t block_size, uint32_t hash_size,
uint64_t data_block_count) {
ZX_ASSERT(block_size % hash_size == 0);
uint32_t hashes_per_block = block_size / hash_size;
uint64_t direct_hash_blocks_needed_floored = data_block_count / hashes_per_block;
uint32_t remainder = data_block_count % hashes_per_block;
uint64_t direct_hash_blocks_needed = direct_hash_blocks_needed_floored + (remainder != 0 ? 1 : 0);
if (direct_hash_blocks_needed == 1) {
// Base case - fits in a block, which goes at the root
return IntegrityShape{1, 1};
}
IntegrityShape indirect = IntegrityShapeFor(block_size, hash_size, direct_hash_blocks_needed);
uint64_t total_blocks_needed = direct_hash_blocks_needed + indirect.integrity_block_count;
uint32_t tree_depth = 1 + indirect.tree_depth;
return IntegrityShape{total_blocks_needed, tree_depth};
}
BlockAllocation BestSplitFor(uint32_t block_size, uint32_t hash_size, uint64_t total_blocks) {
// Block_size must be a multiple of hash_size, because we don't want to deal
// with padding and both are almost always powers of two anyway.
ZX_ASSERT(block_size % hash_size == 0);
// must have at least three blocks to split - one superblock, one data block, one integrity block.
ZX_ASSERT(total_blocks >= 3);
uint64_t superblocks = 1;
uint64_t largest_possible_data_blocks = 1;
uint64_t smallest_impossible_data_blocks = total_blocks - 2;
IntegrityShape best_integrity_shape_yet;
while (largest_possible_data_blocks + 1 < smallest_impossible_data_blocks) {
// Binary search to find most data blocks we can support.
uint64_t test_data_blocks =
largest_possible_data_blocks +
((smallest_impossible_data_blocks - largest_possible_data_blocks) / 2);
IntegrityShape i = IntegrityShapeFor(block_size, hash_size, test_data_blocks);
if (test_data_blocks + i.integrity_block_count + superblocks <= total_blocks) {
// Having test_data_blocks is satisfiable.
largest_possible_data_blocks = test_data_blocks;
best_integrity_shape_yet = i;
} else {
smallest_impossible_data_blocks = test_data_blocks;
}
}
// It's possible at the margins that we can't make use of the entirety of the
// block device -- if we were to add a data block, we'd need an additional
// integrity block, because we're at the edge of an integrity block boundary
// too, but we have none left to allocate. In this case we allocate the
// additional block (or blocks) to the end of the integrity section, where it
// will sit unused. That is: those blocks contribute to
// padded_integrity_block_count in `BlockAllocation` here, but not to
// IntegrityShape's `integrity_block_count`.
uint64_t padded_integrity_blocks = total_blocks - superblocks - largest_possible_data_blocks;
return BlockAllocation{superblocks, padded_integrity_blocks, largest_possible_data_blocks,
best_integrity_shape_yet};
}
Geometry::Geometry(uint32_t block_size, uint32_t hash_size, uint64_t total_blocks)
: total_blocks_(total_blocks),
hash_size_(hash_size),
block_size_(block_size),
hashes_per_block_(block_size / hash_size),
allocation_(BestSplitFor(block_size, hash_size, total_blocks)) {}
HashLocation Geometry::IntegrityDataLocationForDataBlock(DataBlockIndex data_block_index) const {
uint64_t to_pass = data_block_index / hashes_per_block_;
uint64_t block_offset = 0;
while (to_pass > 0) {
// Skipping `to_pass` blocks
block_offset += to_pass;
to_pass = to_pass / hashes_per_block_;
}
uint32_t hash_offset = data_block_index % hashes_per_block_;
return HashLocation{block_offset, hash_offset};
}
HashLocation Geometry::NextIntegrityBlockUp(uint32_t distance_from_leaf,
IntegrityBlockIndex integrity_block_index) const {
//
// If, for example hashes_per_block_ were 128, the integrity data would look
// like this, where reading blocks left to right (and the contents of the
// boxes) indicates the block offset within the integrity section, and each
// block in tier N+1 contains the hashes of the `hashes_per_block_` preceding
// blocks from tier N (and blocks in tier 0 contain hashes of blocks from the
// data section.
//
// tier 2 |16512| ...
// tier 1 |128| |257| ... |16511| ...
// tier 0 |0| |1| |2| ... |127| |129| |130| ... |256| ... |16513| ...
//
// The integrity block number for a given index is the first block to the
// right of it in the next tier up. So 2 -> 128, and 128 -> 16512.
//
// So, in this hypothetical example, if you passed distance_from_leaf = 0 and
// integrity_block_index 2, you'd expect to get back a hash location with
// integrity_block 128 and hash_in_block 2.
//
// Note: `distance_from_leaf` is inferrable from integrity_block_number, and
// what its value in base (hashes_per_block_+1) is, but that involves test
// division which is slow. Better to track the distance and save some integer
// division/modular arithmetic.
//
// If integrity_block_index is a leaf node, distance_from_leaf should be 0.
// TODO: clean up these printfs once verified read is implemented/tested
// printf(" NextIntegrityBlockUp(block: %lu, tier %u)\n",
// integrity_block_index, distance_from_leaf);
uint64_t one_indexed_integrity_block_index = integrity_block_index + 1;
// Convert to one-indexed arithmetic for the next bit. It's simpler for some
// of the modular arithmetic around tier strides.
// The "stride" of a tier is the difference in (physical) block index between
// two blocks within the same tier, if the tier were to be completely full.
// So tier 0 always has stride 1, tier 1 has stride (hashes_per_block_ + 1),
// and so on.
uint64_t current_tier_stride = 1;
for (uint32_t i = 0; i < distance_from_leaf; i++) {
current_tier_stride *= hashes_per_block_;
current_tier_stride += 1;
}
uint64_t next_tier_stride = current_tier_stride * hashes_per_block_ + 1;
// TODO: clean up these printfs once verified read is implemented/tested
// printf(" current tier stride %lu, next tier stride
// %lu\n",
// current_tier_stride, next_tier_stride);
// Compute which hash in the containing integrity block (which sits up one
// tier from `integrity_block_index`) contains the hash of the integrity data
// at `integrity_block_index`.
// We can achieve this by operating modulo the next larger tier stride,
// by dividing by the current tier stride (which we know from above our current index is an
// exact multiple of, unless it is in the last block of this tier).
//
// `block_in_tier_chunk` represents the block offset within the bounds of
// one next-tier-up-stride.
uint64_t block_in_tier_chunk = one_indexed_integrity_block_index % next_tier_stride;
// `unadjusted_offset_within_block` represents "If I'm scanning blocks within
// the current next-tier-up stride, how many `current_tier_stride`s in the
// next-tier-up stride do I pass over before I reach this block?". It's the
// relative offset within that block. It's guaranteed not to overflow
// uint32_t because we cap total block count at (2**20 - 1), so we'll never
// even get close to having an integrity block offset of 2**32.
uint32_t unadjusted_offset_within_block =
static_cast<uint32_t>(block_in_tier_chunk / current_tier_stride);
// The last block at each tier in a non-full tree (which the vast majority of
// trees will be) might be placed earlier than it would in a full tree. We
// fill the tree from left to right so that only the last block in each tier
// may require padding.
//
// To see how this might happen, consider a tree where hashes_per_block_ is
// 128, we have (129 * 128) = 16512 data blocks and 131 integrity blocks:
//
// tier 2: 131
// tier 1: 128 130
// tier 0: 0 1 2 ...127 129
//
// Integrity block 129 is full of hashes of data blocks.
// Integrity block 130 contains the hash of block 129 and zeroes padding it
// out to the block size.
// Integrity block 131 contains (hash of block 128), (hash of block 130), and
// zeroes padding it out to the block size.
// The hash of block 131 is stored in the superblock.
// The hash of the superblock is the seal produced by CloseAndGenerateSeal.
// `block_in_tier_chunk` should be a perfect multiple of `current_tier_stride`,
// *unless* it is in the last block of this tier *and* that tier is not full,
// which would cause the value of `unadjusted_offset_within_block` to truncate
// when dividing. We'd like to round that truncated bit up.
//
// In the former case, we need to subtract one to return to zero-indexing.
// In the latter case, we need to subtract one to return to zero-indexing and
// we need to add one to compensate for the truncating division, which means
// we can just take the value as-is.
uint32_t offset_within_block =
(unadjusted_offset_within_block * current_tier_stride == block_in_tier_chunk)
? unadjusted_offset_within_block - 1
: unadjusted_offset_within_block;
// Round up to the next multiple of the next tier size, by shaving off the
// residue mod next_tier_stride, then adding in the full next_tier_stride.
// There's probably another way to compute this in closed form that's faster
// and uses smaller numbers by reusing offset_within_block.
uint64_t one_indexed_containing_block_index =
(one_indexed_integrity_block_index - (one_indexed_integrity_block_index % next_tier_stride) +
next_tier_stride);
uint64_t zero_indexed_containing_block_index = one_indexed_containing_block_index - 1;
// Just as we applied adjustments to `unadjusted_offset_within_block` to
// account for incomplete trees, we need to adjust the block index itself.
// The only blocks that need this sort of adjustment are ones which were
// padded with zeroes because the tree is not full, and those will only ever
// be the last block within a tier.
//
// So we can clamp the block index to the total number of populated integrity
// blocks, and solve backwards from the end of the integrity section what the
// last block at that tier's maximum index could be.
uint64_t max_block_index_at_tier =
allocation_.integrity_shape.integrity_block_count -
(allocation_.integrity_shape.tree_depth - 1 - distance_from_leaf);
uint64_t clamped_containing_block_index = zero_indexed_containing_block_index;
if (zero_indexed_containing_block_index > max_block_index_at_tier) {
// Don't reach too far
clamped_containing_block_index = max_block_index_at_tier;
// TODO: clean up these printfs once verified read is implemented/tested
// printf("clamped %u to %u\n", zero_indexed_containing_block_index, max_block_index_at_tier);
}
return HashLocation{clamped_containing_block_index, offset_within_block};
}
uint64_t Geometry::AbsoluteLocationForIntegrity(IntegrityBlockIndex index) const {
return allocation_.superblock_count + index;
}
uint64_t Geometry::AbsoluteLocationForData(DataBlockIndex index) const {
return allocation_.superblock_count + allocation_.padded_integrity_block_count + index;
}
} // namespace block_verity