blob: db05ef309b018e8d7fffb3180e1a3dc219f3f29b [file] [log] [blame] [edit]
// 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.
#ifndef LIB_ZBITL_IMAGE_H_
#define LIB_ZBITL_IMAGE_H_
#include <lib/cksum.h>
#include "checking.h"
#include "view.h"
namespace zbitl {
/// Image provides a modifiable "view" into a ZBI.
template <typename Storage, Checking Check = Checking::kStrict>
class Image : public View<Storage, Check> {
public:
using typename View<Storage, Check>::Error;
using typename View<Storage, Check>::Traits;
using typename View<Storage, Check>::iterator;
using typename View<Storage, Check>::header_type;
static_assert(Traits::CanWrite(), "zbitl::Image requires writable storage");
// Copy/move-constructible or constructible from a Storage argument, like View.
using View<Storage, Check>::View;
// Updates the underlying storage to hold an empty ZBI. It is valid to call
// this method even if the underlying storage does not already represent a
// ZBI or is too small to do so; it will attempt to extend the capacity and
// write a new container header.
fitx::result<Error> clear() { return ResetContainer(sizeof(zbi_header_t)); }
// This version of Append reserves enough space in the underlying ZBI to
// append an item corresponding to the provided header. The header is
// sanitized (via `SanitizeHeader`) with the header.length value preserved,
// as it determines the amount of payload space allocated. The sanitized
// header is immediately written to the storage and an iterator pointing to
// the partially written item is returned to the caller on success. It is
// the caller's responsibility to write the desired data to the payload
// offset (accessible via the iterator).
//
// If `header.flags` has `ZBI_FLAG_CRC32` set, then it is the caller's
// further responsibility to ensure that `header.crc32` is correct or to use
// `EditHeader` later on the returned iterator with a correct value.
fitx::result<Error, iterator> Append(const zbi_header_t& new_header) {
// Get the size from the container header directly (instead of
// size_bytes()) to ensure that the underlying storage does indeed
// represent a ZBI. If we did not check that the following would be able to
// successfully Append() to a "size 0 ZBI", which is a pathology.
uint32_t current_length;
if (auto container_result = this->container_header(); container_result.is_error()) {
return container_result.take_error();
} else {
current_length = container_result.value().length;
}
const uint32_t size = sizeof(zbi_header_t) + current_length;
const uint32_t new_item_offset = size;
const uint32_t new_size = ZBI_ALIGN(new_item_offset + sizeof(new_header) + new_header.length);
// Overflow would have happened if `new_size` is now less than or equal to
// any of the constituent elements in its defining sum, including
// `ZBI_ALIGNMENT`; this reduces to the following predicate.
if (new_size <= new_item_offset || new_size <= new_header.length) {
return fitx::error(Error{"integer overflow; new size is too big", size});
}
if (auto result = ResetContainer(new_size); result.is_error()) {
return result.take_error();
}
if (auto result = this->WriteHeader(new_header, new_item_offset); result.is_error()) {
return fitx::error{
Error{"cannot write item header", new_item_offset, std::move(result.error_value())}};
}
uint32_t padding_size = ZBI_ALIGN(new_header.length) - new_header.length;
if (padding_size > 0) {
uint32_t payload_end = new_item_offset + sizeof(new_header) + new_header.length;
constexpr std::byte kZero[ZBI_ALIGNMENT - 1] = {};
auto padding = AsBytes(kZero).substr(0, padding_size);
if (auto result = Traits::Write(this->storage(), payload_end, padding); result.is_error()) {
return fitx::error{
Error{"cannot write zero padding", payload_end, std::move(result.error_value())}};
}
}
iterator it;
it.view_ = this;
it.offset_ = new_size;
// `header_type` needs to be constructed from the return value of the
// Header trait, which might be a reference wrapper to the header in memory
// instead of the raw value.
if (auto result = Traits::Header(this->storage(), new_item_offset); result.is_error()) {
return fitx::error{
Error{"cannot read header", new_item_offset, std::move(result.error_value())}};
} else {
it.header_ = header_type(std::move(result).value());
}
if (auto result = Traits::Payload(this->storage(), it.payload_offset(), new_header.length);
result.is_error()) {
return fitx::error{Error{"cannot determine payload", it.payload_offset()}};
} else {
it.payload_ = result.value();
}
return fitx::ok(it);
}
// A simpler variation of Append, in which the provided header and payload
// data are written to underlying storage up front. `header.length` will
// automatically be set as `data.size()`. Moreover, if the ZBI_FLAG_CRC32
// flag is provided, the CRC32 will be automatically computed and set as
// well.
fitx::result<Error> Append(zbi_header_t header, ByteView data) {
header.length = static_cast<uint32_t>(data.size());
if (header.flags & ZBI_FLAG_CRC32) {
// An item's CRC32 is computed as the hash of its sanitized header with
// its crc32 field set to 0, combined with the hash of its payload.
header = SanitizeHeader(header);
header.crc32 = 0;
ByteView bytes[] = {AsBytes(header), data};
uint32_t crc = 0;
for (ByteView b : bytes) {
crc = crc32(crc, reinterpret_cast<const uint8_t*>(b.data()), b.size());
}
header.crc32 = crc;
}
if (auto result = Append(header); result.is_error()) {
return result.take_error();
} else if (data.size() > 0) {
auto it = std::move(result).value();
ZX_DEBUG_ASSERT(it != this->end());
uint32_t offset = it.payload_offset();
if (auto result = Traits::Write(this->storage(), offset, data); result.is_error()) {
return fitx::error{Error{"cannot write payload", offset, std::move(result.error_value())}};
}
}
return fitx::ok();
}
// The following aliases are introduced to improve the readability of Extend's
// signature.
template <typename ViewIterator>
using ViewType = std::decay_t<decltype(std::declval<ViewIterator>().view())>;
template <typename ViewIterator>
using ExtendError = typename ViewType<ViewIterator>::template CopyError<Storage>;
// Extends the underlying ZBI by the items corresponding to an iterator range
// another View. As this operation is inherently a copy from that view, a
// CopyError of the latter is returned.
//
// The semantics are similar to that of View's Copy(): this is a blind, bulk
// copy from [first, last) and the relevant headers are not sanitized or
// checked for correctness when written.
template <typename ViewIterator>
fitx::result<ExtendError<ViewIterator>> Extend(ViewIterator first, ViewIterator last) {
using ErrorType = ExtendError<ViewIterator>;
if (&first.view() != &last.view()) {
return fitx::error{ErrorType{"iterators from different views provided"}};
}
auto& view = first.view();
if (first == view.end()) {
if (last == view.end()) {
return fitx::ok(); // By convention, a no-op.
}
return fitx::error{ErrorType{"cannot extend by iterator range starting at a view's end."}};
}
uint32_t size = this->size_bytes();
uint32_t tail_size =
(last == view.end() ? view.size_bytes() : last.item_offset()) - first.item_offset();
uint32_t new_size = size + tail_size;
if (auto result = ResetContainer(new_size); result.is_error()) {
auto error = std::move(result).error_value();
return fitx::error{ErrorType{
.zbi_error = error.zbi_error,
.write_offset = error.item_offset,
.write_error = std::move(error.storage_error),
}};
}
return view.Copy(this->storage(), first.item_offset(), tail_size, size);
}
private:
// Resets the container as being of the provided size (which is the total
// container size and not the length of the ZBI). If possible, the
// underlying storage will be extended as needed.
fitx::result<Error> ResetContainer(uint32_t new_size) {
ZX_DEBUG_ASSERT(new_size % ZBI_ALIGNMENT == 0);
if (auto result = Traits::EnsureCapacity(this->storage(), new_size); result.is_error()) {
return fitx::error{Error{"cannot ensure sufficient capacity",
static_cast<uint32_t>(this->size_bytes()),
std::move(result.error_value())}};
}
if (auto result =
this->WriteHeader(ZBI_CONTAINER_HEADER(new_size - uint32_t{sizeof(zbi_header_t)}), 0);
result.is_error()) {
return fitx::error{
Error{"cannot write container header", 0, std::move(result.error_value())}};
}
return fitx::ok();
}
};
// Deduction guide: Image img(T{}) instantiates Image<T>.
template <typename Storage>
explicit Image(Storage) -> Image<Storage>;
// A shorthand for permissive checking.
template <typename Storage>
using PermissiveImage = Image<Storage, Checking::kPermissive>;
// A shorthand for CRC checking.
template <typename Storage>
using CrcCheckingImage = Image<Storage, Checking::kCrc>;
} // namespace zbitl
#endif // LIB_ZBITL_IMAGE_H_