blob: 9831aad12e50c2f2d9fe49f880dcd1ca2b4a0c26 [file] [log] [blame]
// Copyright 2019 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/lib/digest/hash-list.h"
#include <stddef.h>
#include <stdint.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <algorithm>
#include <fbl/algorithm.h>
#include "src/lib/digest/digest.h"
#include "src/lib/digest/node-digest.h"
namespace digest {
namespace internal {
// HashListBase<T>
zx_status_t HashListBase::Align(size_t *data_off, size_t *buf_len) const {
size_t buf_end;
if (add_overflow(*data_off, *buf_len, &buf_end) || buf_end > data_len_) {
return ZX_ERR_OUT_OF_RANGE;
}
*data_off = node_digest_.PrevAligned(*data_off);
*buf_len = std::min(node_digest_.NextAligned(buf_end), data_len_) - *data_off;
return ZX_OK;
}
zx_status_t HashListBase::SetDataLength(size_t data_len) {
if (data_len > node_digest_.MaxAligned()) {
return ZX_ERR_OUT_OF_RANGE;
}
data_off_ = 0;
data_len_ = data_len;
list_off_ = 0;
list_len_ = 0;
if (data_len_ == 0) {
return node_digest_.Reset(data_off_, data_len_);
}
return ZX_OK;
}
size_t HashListBase::GetListOffset(size_t data_off) const {
return node_digest_.ToNode(data_off) * GetDigestSize();
}
size_t HashListBase::GetListLength() const {
return std::max(GetListOffset(node_digest_.NextAligned(data_len_)), GetDigestSize());
}
bool HashListBase::IsValidRange(size_t data_off, size_t buf_len) {
size_t buf_end;
return !add_overflow(data_off, buf_len, &buf_end) && buf_end <= data_len_;
}
zx_status_t HashListBase::ProcessData(const uint8_t *buf, size_t buf_len, size_t data_off) {
if (list_len_ == 0) {
return ZX_ERR_BAD_STATE;
}
if (!IsValidRange(data_off, buf_len)) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t rc;
data_off_ = data_off;
list_off_ = GetListOffset(data_off);
size_t data_len_with_padding =
pad_data_to_node_size_ ? fbl::round_up(data_len_, GetNodeSize()) : data_len_;
while (buf_len != 0) {
if (node_digest_.IsAligned(data_off_) &&
(rc = node_digest_.Reset(data_off_, data_len_with_padding)) != ZX_OK) {
return rc;
}
size_t chunk = node_digest_.Append(buf, buf_len);
buf += chunk;
buf_len -= chunk;
data_off_ += chunk;
bool at_end_of_data = data_off_ == data_len_;
if (at_end_of_data && pad_data_to_node_size_) {
node_digest_.PadWithZeros();
}
if (node_digest_.IsAligned(data_off_) || at_end_of_data) {
HandleOne();
}
}
return ZX_OK;
}
void HashListBase::HandleOne() {
HandleOne(node_digest_.get());
list_off_ += GetDigestSize();
}
// HashList<T>
template <typename T>
zx_status_t HashList<T>::SetList(T *list, size_t list_len) {
if (list == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (list_len < GetListLength()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
// Reset the state of the list.
SetDataLength(data_len());
list_ = list;
set_list_len(list_len);
if (data_len() == 0) {
HandleOne();
}
return ZX_OK;
}
} // namespace internal
// HashListCreator
// Forward template declaration
template zx_status_t internal::HashList<uint8_t>::SetList(uint8_t *list, size_t list_len);
zx_status_t HashListCreator::Append(const void *buf, size_t buf_len) {
return this->ProcessData(static_cast<const uint8_t *>(buf), buf_len, this->data_off());
}
void HashListCreator::HandleOne(const Digest &digest) {
digest.CopyTo(list() + list_off(), GetDigestSize());
}
// HashListVerifier
// Forward template declaration
template zx_status_t internal::HashList<const uint8_t>::SetList(const uint8_t *list,
size_t list_len);
zx_status_t HashListVerifier::Verify(const void *buf, size_t buf_len, size_t data_off) {
zx_status_t rc;
verified_ = true;
if (data_len() == 0) {
rc = SetList(list(), list_len());
} else {
rc = this->ProcessData(static_cast<const uint8_t *>(buf), buf_len, data_off);
}
if (rc != ZX_OK) {
return rc;
}
return verified_ ? ZX_OK : ZX_ERR_IO_DATA_INTEGRITY;
}
bool HashListVerifier::IsValidRange(size_t data_off, size_t buf_len) {
size_t buf_end;
if (data_off == data_len() && buf_len == 0) {
return true;
} else if (!IsAligned(data_off) || add_overflow(data_off, buf_len, &buf_end)) {
return false;
} else if (buf_end < data_len()) {
return IsAligned(buf_end);
} else {
return buf_end == data_len();
}
}
void HashListVerifier::HandleOne(const Digest &digest) {
verified_ &= (digest.Equals(list() + list_off(), GetDigestSize()));
}
size_t CalculateHashListSize(size_t data_size, size_t node_size) {
NodeDigest node_digest;
ZX_ASSERT_MSG(node_digest.SetNodeSize(node_size) == ZX_OK, "node_size=%lu", node_size);
size_t digest_size = node_digest.len();
return std::max(node_digest.ToNode(node_digest.NextAligned(data_size)) * digest_size,
digest_size);
}
} // namespace digest