| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /* |
| * Copyright (c) 2013-2018 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 "block.hpp" |
| #include "buffer-stream.hpp" |
| #include "encoding-buffer.hpp" |
| #include "tlv.hpp" |
| #include "../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>)); |
| static_assert(std::is_nothrow_move_constructible<Block>::value, |
| "Block must be MoveConstructible with noexcept"); |
| static_assert(std::is_nothrow_move_assignable<Block>::value, |
| "Block must be MoveAssignable with noexcept"); |
| |
| const size_t MAX_SIZE_OF_BLOCK_FROM_STREAM = MAX_NDN_PACKET_SIZE; |
| |
| // ---- constructor, creation, assignment ---- |
| |
| Block::Block() |
| : m_type(std::numeric_limits<uint32_t>::max()) |
| , m_size(0) |
| { |
| } |
| |
| 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(m_end - m_begin) |
| { |
| if (m_buffer->size() == 0) { |
| BOOST_THROW_EXCEPTION(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) { |
| BOOST_THROW_EXCEPTION(std::invalid_argument("begin/end iterators points out of 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)) { |
| BOOST_THROW_EXCEPTION(Error("TLV-LENGTH doesn't match buffer size")); |
| } |
| } |
| |
| Block::Block(const Block& block, Buffer::const_iterator begin, Buffer::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(m_end - m_begin) |
| { |
| } |
| |
| Block::Block(const uint8_t* buf, size_t bufSize) |
| { |
| const uint8_t* pos = buf; |
| const uint8_t* const end = buf + bufSize; |
| |
| m_type = tlv::readType(pos, end); |
| uint64_t length = tlv::readVarNumber(pos, end); |
| // pos now points to TLV-VALUE |
| |
| if (length > static_cast<uint64_t>(end - pos)) { |
| BOOST_THROW_EXCEPTION(Error("Not enough bytes in the buffer to fully parse TLV")); |
| } |
| size_t typeLengthSize = pos - buf; |
| m_size = typeLengthSize + length; |
| |
| m_buffer = make_shared<Buffer>(buf, m_size); |
| m_begin = m_buffer->begin(); |
| m_end = m_valueEnd = m_buffer->end(); |
| m_valueBegin = m_begin + typeLengthSize; |
| } |
| |
| 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(); |
| } |
| |
| 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) { |
| BOOST_THROW_EXCEPTION(Error("TLV-LENGTH from stream exceeds limit")); |
| } |
| |
| EncodingBuffer eb(tlSize + length, length); |
| uint8_t* valueBuf = eb.buf(); |
| is.read(reinterpret_cast<char*>(valueBuf), length); |
| if (length != static_cast<uint64_t>(is.gcount())) { |
| BOOST_THROW_EXCEPTION(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()); |
| } |
| |
| std::tuple<bool, Block> |
| Block::fromBuffer(ConstBufferPtr buffer, size_t offset) |
| { |
| const Buffer::const_iterator begin = buffer->begin() + offset; |
| Buffer::const_iterator pos = begin; |
| |
| uint32_t type = 0; |
| bool isOk = tlv::readType(pos, buffer->end(), type); |
| if (!isOk) { |
| return std::make_tuple(false, Block()); |
| } |
| uint64_t length = 0; |
| isOk = tlv::readVarNumber(pos, buffer->end(), length); |
| if (!isOk) { |
| return std::make_tuple(false, Block()); |
| } |
| // pos now points to TLV-VALUE |
| |
| if (length > static_cast<uint64_t>(buffer->end() - pos)) { |
| return std::make_tuple(false, Block()); |
| } |
| |
| return std::make_tuple(true, Block(buffer, type, begin, pos + length, pos, pos + length)); |
| } |
| |
| std::tuple<bool, Block> |
| Block::fromBuffer(const uint8_t* buf, size_t bufSize) |
| { |
| const uint8_t* pos = buf; |
| const uint8_t* const end = buf + bufSize; |
| |
| 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 |
| |
| if (length > static_cast<uint64_t>(end - pos)) { |
| return std::make_tuple(false, Block()); |
| } |
| |
| size_t typeLengthSize = pos - buf; |
| auto b = make_shared<Buffer>(buf, pos + length); |
| return std::make_tuple(true, Block(b, type, b->begin(), b->end(), |
| b->begin() + typeLengthSize, b->end())); |
| } |
| |
| // ---- wire format ---- |
| |
| bool |
| Block::hasWire() const |
| { |
| return m_buffer != nullptr && m_begin != m_end; |
| } |
| |
| void |
| Block::reset() |
| { |
| this->resetWire(); |
| |
| m_type = std::numeric_limits<uint32_t>::max(); |
| m_elements.clear(); |
| } |
| |
| void |
| Block::resetWire() |
| { |
| m_buffer.reset(); // discard underlying buffer by resetting shared_ptr |
| m_begin = m_end = m_valueBegin = m_valueEnd = Buffer::const_iterator(); |
| } |
| |
| Buffer::const_iterator |
| Block::begin() const |
| { |
| if (!hasWire()) |
| BOOST_THROW_EXCEPTION(Error("Underlying wire buffer is empty")); |
| |
| return m_begin; |
| } |
| |
| Buffer::const_iterator |
| Block::end() const |
| { |
| if (!hasWire()) |
| BOOST_THROW_EXCEPTION(Error("Underlying wire buffer is empty")); |
| |
| return m_end; |
| } |
| |
| const uint8_t* |
| Block::wire() const |
| { |
| if (!hasWire()) |
| BOOST_THROW_EXCEPTION(Error("Underlying wire buffer is empty")); |
| |
| return &*m_begin; |
| } |
| |
| size_t |
| Block::size() const |
| { |
| if (empty()) { |
| BOOST_THROW_EXCEPTION(Error("Block size cannot be determined (undefined block size)")); |
| } |
| |
| return m_size; |
| } |
| |
| // ---- value ---- |
| |
| const uint8_t* |
| Block::value() const |
| { |
| return hasValue() ? &*m_valueBegin : nullptr; |
| } |
| |
| size_t |
| Block::value_size() const |
| { |
| return hasValue() ? m_valueEnd - m_valueBegin : 0; |
| } |
| |
| Block |
| Block::blockFromValue() const |
| { |
| if (!hasValue()) |
| BOOST_THROW_EXCEPTION(Error("Block has no TLV-VALUE")); |
| |
| return Block(*this, m_valueBegin, m_valueEnd, true); |
| } |
| |
| // ---- sub elements ---- |
| |
| void |
| Block::parse() const |
| { |
| if (!m_elements.empty() || value_size() == 0) |
| return; |
| |
| Buffer::const_iterator begin = value_begin(); |
| Buffer::const_iterator end = value_end(); |
| |
| while (begin != end) { |
| Buffer::const_iterator 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(); |
| BOOST_THROW_EXCEPTION(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 |
| |
| Buffer::const_iterator subEnd = 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; |
| } |
| |
| BOOST_THROW_EXCEPTION(Error("No sub-element of type " + to_string(type) + |
| " is 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.resize(it - m_elements.begin()); |
| } |
| |
| 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); |
| } |
| |
| 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 boost::asio::const_buffer(wire(), size()); |
| } |
| |
| bool |
| operator==(const Block& lhs, const Block& rhs) |
| { |
| 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.empty()) { |
| 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(), block.value_size(), true); |
| } |
| else { |
| os << block.type() << "[empty]"; |
| } |
| |
| os.flags(oldFmt); |
| return os; |
| } |
| |
| } // namespace ndn |