blob: b4bbd02a83c92bbe38cc80b756643131952ef643 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*
* @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
*/
#include "ndn-cxx/encoding/block.hpp"
#include "ndn-cxx/encoding/buffer-stream.hpp"
#include "ndn-cxx/encoding/encoding-buffer.hpp"
#include "ndn-cxx/encoding/tlv.hpp"
#include "ndn-cxx/security/transform.hpp"
#include "ndn-cxx/util/ostream-joiner.hpp"
#include "ndn-cxx/util/string-helper.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <cstring>
namespace ndn {
BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Block>));
const size_t MAX_SIZE_OF_BLOCK_FROM_STREAM = MAX_NDN_PACKET_SIZE;
// ---- constructor, creation, assignment ----
Block::Block() = default;
Block::Block(const Block&) = default;
Block&
Block::operator=(const Block&) = default;
Block::Block(span<const uint8_t> buffer)
{
auto pos = buffer.begin();
const auto end = buffer.end();
m_type = tlv::readType(pos, end);
uint64_t length = tlv::readVarNumber(pos, end);
// pos now points to TLV-VALUE
BOOST_ASSERT(pos <= end);
if (length > static_cast<uint64_t>(std::distance(pos, end))) {
NDN_THROW(Error("Not enough bytes in the buffer to fully parse TLV"));
}
std::advance(pos, length);
// pos now points to the end of the TLV
m_buffer = std::make_shared<Buffer>(buffer.begin(), pos);
m_begin = m_buffer->begin();
m_end = m_buffer->end();
m_valueBegin = std::prev(m_end, length);
m_valueEnd = m_buffer->end();
m_size = m_buffer->size();
}
Block::Block(const EncodingBuffer& buffer)
: Block(buffer.getBuffer(), buffer.begin(), buffer.end(), true)
{
}
Block::Block(const ConstBufferPtr& buffer)
: Block(buffer, buffer->begin(), buffer->end(), true)
{
}
Block::Block(ConstBufferPtr buffer, Buffer::const_iterator begin, Buffer::const_iterator end,
bool verifyLength)
: m_buffer(std::move(buffer))
, m_begin(begin)
, m_end(end)
, m_valueBegin(m_begin)
, m_valueEnd(m_end)
, m_size(static_cast<size_t>(std::distance(m_begin, m_end)))
{
if (m_buffer->empty()) {
NDN_THROW(std::invalid_argument("Buffer is empty"));
}
const uint8_t* bufferBegin = &m_buffer->front();
const uint8_t* bufferEnd = bufferBegin + m_buffer->size();
if (&*begin < bufferBegin || &*begin > bufferEnd ||
&*end < bufferBegin || &*end > bufferEnd) {
NDN_THROW(std::invalid_argument("Begin/end iterators point outside the buffer"));
}
m_type = tlv::readType(m_valueBegin, m_valueEnd);
uint64_t length = tlv::readVarNumber(m_valueBegin, m_valueEnd);
// m_valueBegin now points to TLV-VALUE
if (verifyLength && length != static_cast<uint64_t>(m_valueEnd - m_valueBegin)) {
NDN_THROW(Error("TLV-LENGTH does not match buffer size"));
}
}
Block::Block(const Block& block, Block::const_iterator begin, Block::const_iterator end,
bool verifyLength)
: Block(block.m_buffer, begin, end, verifyLength)
{
}
Block::Block(ConstBufferPtr buffer, uint32_t type,
Buffer::const_iterator begin, Buffer::const_iterator end,
Buffer::const_iterator valueBegin, Buffer::const_iterator valueEnd)
: m_buffer(std::move(buffer))
, m_begin(begin)
, m_end(end)
, m_valueBegin(valueBegin)
, m_valueEnd(valueEnd)
, m_type(type)
, m_size(static_cast<size_t>(std::distance(m_begin, m_end)))
{
}
Block::Block(uint32_t type)
: m_type(type)
, m_size(tlv::sizeOfVarNumber(m_type) + tlv::sizeOfVarNumber(0))
{
}
Block::Block(uint32_t type, ConstBufferPtr value)
: m_buffer(std::move(value))
, m_begin(m_buffer->end())
, m_end(m_buffer->end())
, m_valueBegin(m_buffer->begin())
, m_valueEnd(m_buffer->end())
, m_type(type)
{
m_size = tlv::sizeOfVarNumber(m_type) + tlv::sizeOfVarNumber(value_size()) + value_size();
}
Block::Block(uint32_t type, const Block& value)
: m_buffer(value.m_buffer)
, m_begin(m_buffer->end())
, m_end(m_buffer->end())
, m_valueBegin(value.begin())
, m_valueEnd(value.end())
, m_type(type)
{
m_size = tlv::sizeOfVarNumber(m_type) + tlv::sizeOfVarNumber(value_size()) + value_size();
}
std::tuple<bool, Block>
Block::fromBuffer(ConstBufferPtr buffer, size_t offset)
{
auto begin = std::next(buffer->begin(), offset);
auto pos = begin;
const auto end = buffer->end();
uint32_t type = 0;
bool isOk = tlv::readType(pos, end, type);
if (!isOk) {
return std::make_tuple(false, Block());
}
uint64_t length = 0;
isOk = tlv::readVarNumber(pos, end, length);
if (!isOk) {
return std::make_tuple(false, Block());
}
// pos now points to TLV-VALUE
BOOST_ASSERT(pos <= end);
if (length > static_cast<uint64_t>(std::distance(pos, end))) {
return std::make_tuple(false, Block());
}
return std::make_tuple(true, Block(std::move(buffer), type, begin, pos + length, pos, pos + length));
}
std::tuple<bool, Block>
Block::fromBuffer(span<const uint8_t> buffer)
{
auto pos = buffer.begin();
const auto end = buffer.end();
uint32_t type = 0;
bool isOk = tlv::readType(pos, end, type);
if (!isOk) {
return std::make_tuple(false, Block());
}
uint64_t length = 0;
isOk = tlv::readVarNumber(pos, end, length);
if (!isOk) {
return std::make_tuple(false, Block());
}
// pos now points to TLV-VALUE
BOOST_ASSERT(pos <= end);
if (length > static_cast<uint64_t>(std::distance(pos, end))) {
return std::make_tuple(false, Block());
}
std::advance(pos, length);
// pos now points to the end of the TLV
auto b = std::make_shared<Buffer>(buffer.begin(), pos);
return std::make_tuple(true, Block(b, type, b->begin(), b->end(),
std::prev(b->end(), length), b->end()));
}
Block
Block::fromStream(std::istream& is)
{
std::istream_iterator<uint8_t> begin(is >> std::noskipws);
std::istream_iterator<uint8_t> end;
uint32_t type = tlv::readType(begin, end);
uint64_t length = tlv::readVarNumber(begin, end);
if (begin != end) {
is.putback(*begin);
}
size_t tlSize = tlv::sizeOfVarNumber(type) + tlv::sizeOfVarNumber(length);
if (tlSize + length > MAX_SIZE_OF_BLOCK_FROM_STREAM) {
NDN_THROW(Error("TLV-LENGTH from stream exceeds limit"));
}
EncodingBuffer eb(tlSize + length, length);
uint8_t* valueBuf = eb.data();
is.read(reinterpret_cast<char*>(valueBuf), length);
if (length != static_cast<uint64_t>(is.gcount())) {
NDN_THROW(Error("Not enough bytes from stream to fully parse TLV"));
}
eb.prependVarNumber(length);
eb.prependVarNumber(type);
// TLV-VALUE is directly written into eb.buf(), eb.end() is not incremented, but eb.getBuffer()
// has the correct layout.
return Block(eb.getBuffer());
}
// ---- wire format ----
void
Block::reset() noexcept
{
*this = {};
}
void
Block::resetWire() noexcept
{
m_buffer.reset(); // discard underlying buffer by resetting shared_ptr
m_begin = m_end = m_valueBegin = m_valueEnd = {};
}
Block::const_iterator
Block::begin() const
{
if (!hasWire())
NDN_THROW(Error("Underlying wire buffer is empty"));
return m_begin;
}
Block::const_iterator
Block::end() const
{
if (!hasWire())
NDN_THROW(Error("Underlying wire buffer is empty"));
return m_end;
}
const uint8_t*
Block::data() const
{
if (!hasWire())
NDN_THROW(Error("Underlying wire buffer is empty"));
return &*m_begin;
}
size_t
Block::size() const
{
if (!isValid()) {
NDN_THROW(Error("Cannot determine size of invalid block"));
}
return m_size;
}
// ---- value ----
const uint8_t*
Block::value() const noexcept
{
return value_size() > 0 ? &*m_valueBegin : nullptr;
}
Block
Block::blockFromValue() const
{
if (value_size() == 0) {
NDN_THROW(Error("Cannot construct block from empty TLV-VALUE"));
}
return Block(*this, m_valueBegin, m_valueEnd, true);
}
// ---- sub elements ----
void
Block::parse() const
{
if (!m_elements.empty() || value_size() == 0)
return;
auto begin = value_begin();
auto end = value_end();
while (begin != end) {
auto pos = begin;
uint32_t type = tlv::readType(pos, end);
uint64_t length = tlv::readVarNumber(pos, end);
if (length > static_cast<uint64_t>(end - pos)) {
m_elements.clear();
NDN_THROW(Error("TLV-LENGTH of sub-element of type " + to_string(type) +
" exceeds TLV-VALUE boundary of parent block"));
}
// pos now points to TLV-VALUE of sub element
auto subEnd = std::next(pos, length);
m_elements.emplace_back(m_buffer, type, begin, subEnd, pos, subEnd);
begin = subEnd;
}
}
void
Block::encode()
{
if (hasWire())
return;
EncodingEstimator estimator;
size_t estimatedSize = encode(estimator);
EncodingBuffer buffer(estimatedSize, 0);
encode(buffer);
}
size_t
Block::encode(EncodingEstimator& estimator) const
{
if (hasValue()) {
return m_size;
}
size_t len = encodeValue(estimator);
len += estimator.prependVarNumber(len);
len += estimator.prependVarNumber(m_type);
return len;
}
size_t
Block::encodeValue(EncodingEstimator& estimator) const
{
size_t len = 0;
for (const Block& element : m_elements | boost::adaptors::reversed) {
len += element.encode(estimator);
}
return len;
}
size_t
Block::encode(EncodingBuffer& encoder)
{
size_t len = 0;
m_end = encoder.begin();
if (hasValue()) {
len += encoder.prependRange(m_valueBegin, m_valueEnd);
}
else {
for (Block& element : m_elements | boost::adaptors::reversed) {
len += element.encode(encoder);
}
}
m_valueEnd = m_end;
m_valueBegin = encoder.begin();
len += encoder.prependVarNumber(len);
len += encoder.prependVarNumber(m_type);
m_begin = encoder.begin();
m_buffer = encoder.getBuffer();
m_size = len;
return len;
}
const Block&
Block::get(uint32_t type) const
{
auto it = this->find(type);
if (it != m_elements.end()) {
return *it;
}
NDN_THROW(Error("No sub-element of type " + to_string(type) +
" found in block of type " + to_string(m_type)));
}
Block::element_const_iterator
Block::find(uint32_t type) const
{
return std::find_if(m_elements.begin(), m_elements.end(),
[type] (const Block& subBlock) { return subBlock.type() == type; });
}
void
Block::remove(uint32_t type)
{
resetWire();
auto it = std::remove_if(m_elements.begin(), m_elements.end(),
[type] (const Block& subBlock) { return subBlock.type() == type; });
m_elements.erase(it, m_elements.end());
}
Block::element_iterator
Block::erase(Block::element_const_iterator position)
{
resetWire();
return m_elements.erase(position);
}
Block::element_iterator
Block::erase(Block::element_const_iterator first, Block::element_const_iterator last)
{
resetWire();
return m_elements.erase(first, last);
}
void
Block::push_back(const Block& element)
{
resetWire();
m_elements.push_back(element);
}
void
Block::push_back(Block&& element)
{
resetWire();
m_elements.push_back(std::move(element));
}
Block::element_iterator
Block::insert(Block::element_const_iterator pos, const Block& element)
{
resetWire();
return m_elements.insert(pos, element);
}
// ---- misc ----
Block::operator boost::asio::const_buffer() const
{
return {data(), size()};
}
bool
operator==(const Block& lhs, const Block& rhs) noexcept
{
return lhs.type() == rhs.type() &&
lhs.value_size() == rhs.value_size() &&
(lhs.value_size() == 0 ||
std::memcmp(lhs.value(), rhs.value(), lhs.value_size()) == 0);
}
std::ostream&
operator<<(std::ostream& os, const Block& block)
{
auto oldFmt = os.flags(std::ios_base::dec);
if (!block.isValid()) {
os << "[invalid]";
}
else if (!block.m_elements.empty()) {
EncodingEstimator estimator;
size_t tlvLength = block.encodeValue(estimator);
os << block.type() << '[' << tlvLength << "]={";
std::copy(block.elements_begin(), block.elements_end(), make_ostream_joiner(os, ','));
os << '}';
}
else if (block.value_size() > 0) {
os << block.type() << '[' << block.value_size() << "]=";
printHex(os, block.value_bytes(), true);
}
else {
os << block.type() << "[empty]";
}
os.flags(oldFmt);
return os;
}
Block
operator ""_block(const char* input, std::size_t len)
{
namespace t = security::transform;
t::StepSource ss;
OBufferStream os;
ss >> t::hexDecode() >> t::streamSink(os);
for (const char* end = input + len; input != end; ++input) {
if (std::strchr("0123456789ABCDEF", *input) != nullptr) {
ss.write({reinterpret_cast<const uint8_t*>(input), 1});
}
}
try {
ss.end();
}
catch (const t::Error&) {
NDN_THROW(std::invalid_argument("Input has odd number of hexadecimal digits"));
}
return Block(os.buf());
}
} // namespace ndn