diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 029edc71..295b0242 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -59,6 +59,7 @@ add_library(kovri-core router/tunnel/impl.h router/tunnel/pool.h router/tunnel/transit.h + util/buffer.h util/byte_stream.h util/config.h util/exception.h diff --git a/src/core/router/context.cc b/src/core/router/context.cc index 0ba38bac..3a86b5fb 100644 --- a/src/core/router/context.cc +++ b/src/core/router/context.cc @@ -123,7 +123,7 @@ void RouterContext::Initialize(const boost::program_options::variables_map& map) std::make_pair(has_ntcp, has_ssu)); // TODO(anonimal): brittle, see TODO in header // Update context RI - m_RouterInfo.Update(router.GetBuffer(), router.GetBufferLen()); + m_RouterInfo.Update(router.data(), router.size()); } else // Keys (and RI should also) exist { @@ -178,7 +178,7 @@ void RouterContext::Initialize(const boost::program_options::variables_map& map) router.SetDefaultOptions(); // Update context RI - m_RouterInfo.Update(router.GetBuffer(), router.GetBufferLen()); + m_RouterInfo.Update(router.data(), router.size()); // Test for reachability of context's RI if (IsUnreachable()) diff --git a/src/core/router/i2np.cc b/src/core/router/i2np.cc index 73fa8ade..c3b0b6c2 100644 --- a/src/core/router/i2np.cc +++ b/src/core/router/i2np.cc @@ -289,7 +289,7 @@ std::shared_ptr CreateDatabaseStoreMsg( buf += 32; } kovri::core::Gzip compressor; - compressor.Put(router->GetBuffer(), router->GetBufferLen()); + compressor.Put(router->data(), router->size()); auto size = compressor.MaxRetrievable(); core::OutputByteStream::Write(buf, size); // size buf += 2; diff --git a/src/core/router/info.cc b/src/core/router/info.cc index 1065e866..052edbc9 100644 --- a/src/core/router/info.cc +++ b/src/core/router/info.cc @@ -54,7 +54,7 @@ namespace kovri namespace core { -RouterInfo::RouterInfo() : m_Exception(__func__), m_Buffer(nullptr) // TODO(anonimal): buffer refactor +RouterInfo::RouterInfo() : m_Exception(__func__) { } @@ -103,8 +103,7 @@ RouterInfo::RouterInfo( RouterInfo::RouterInfo(const std::string& path) : m_Exception(__func__), - m_Path(path), - m_Buffer(std::make_unique(Size::MaxBuffer)) // TODO(anonimal): buffer refactor + m_Path(path) { ReadFromFile(); ReadFromBuffer(false); @@ -112,14 +111,8 @@ RouterInfo::RouterInfo(const std::string& path) RouterInfo::RouterInfo(const std::uint8_t* buf, std::uint16_t len) : m_Exception(__func__), - m_Buffer(std::make_unique(Size::MaxBuffer)), // TODO(anonimal): buffer refactor - m_BufferLen(len) + m_Buffer(buf, len) { - if (!buf) - throw std::invalid_argument("RouterInfo: null buffer"); - if (len < Size::MinBuffer || len > Size::MaxBuffer) - throw std::length_error("RouterInfo: invalid buffer length"); - std::memcpy(m_Buffer.get(), buf, len); ReadFromBuffer(true); m_IsUpdated = true; } @@ -138,18 +131,11 @@ void RouterInfo::ReadFromFile() // Get full length of stream stream.Seekg(0, std::ios::end); - m_BufferLen = stream.Tellg(); - if (m_BufferLen < Size::MinBuffer || m_BufferLen > Size::MaxBuffer) - { - LOG(error) << "RouterInfo: buffer length = " << m_BufferLen; - throw std::runtime_error(m_Path + " is malformed"); - } + m_Buffer(stream.Tellg()); // Read in complete length of stream stream.Seekg(0, std::ios::beg); - if (!m_Buffer) - m_Buffer = std::make_unique(Size::MaxBuffer); - stream.Read(m_Buffer.get(), m_BufferLen); + stream.Read(m_Buffer.data(), m_Buffer.size()); } catch (...) { @@ -163,15 +149,15 @@ void RouterInfo::ReadFromBuffer(bool verify_signature) try { // Get + verify identity length from existing RI in buffer - std::size_t ident_len = - m_RouterIdentity.FromBuffer(m_Buffer.get(), m_BufferLen); + std::size_t const ident_len = + m_RouterIdentity.FromBuffer(m_Buffer.data(), m_Buffer.size()); if (!ident_len) throw std::length_error("null ident length"); // Parse existing RI from buffer std::string router_info( - reinterpret_cast(m_Buffer.get()) + ident_len, - m_BufferLen - ident_len); + reinterpret_cast(m_Buffer.data()) + ident_len, + m_Buffer.size() - ident_len); ParseRouterInfo(router_info); @@ -179,11 +165,10 @@ void RouterInfo::ReadFromBuffer(bool verify_signature) if (verify_signature) { // Note: signature length is guaranteed to be no less than buffer length - std::uint16_t len = m_BufferLen - m_RouterIdentity.GetSignatureLen(); + std::uint16_t const len = + m_Buffer.size() - m_RouterIdentity.GetSignatureLen(); if (!m_RouterIdentity.Verify( - reinterpret_cast(m_Buffer.get()), - len, - reinterpret_cast(m_Buffer.get() + len))) + m_Buffer.data(), len, m_Buffer.data() + len)) { LOG(error) << "RouterInfo: signature verification failed"; m_IsUnreachable = true; @@ -679,33 +664,27 @@ void RouterInfo::DisableV6() void RouterInfo::Update(const std::uint8_t* buf, std::uint16_t len) { - if (len < Size::MinBuffer || len > Size::MaxBuffer) - throw std::length_error( - "RouterInfo: " + std::string(__func__) + ": invalid buffer length"); - if (!m_Buffer) - m_Buffer = std::make_unique(Size::MaxBuffer); - m_BufferLen = len; + m_Buffer(buf, len); m_IsUpdated = true; m_IsUnreachable = false; m_SupportedTransports = 0; m_Caps = 0; m_Addresses.clear(); m_Options.clear(); - std::memcpy(m_Buffer.get(), buf, len); ReadFromBuffer(true); // don't delete buffer until saved to file } const std::uint8_t* RouterInfo::LoadBuffer() { - if (!m_Buffer) + if (!m_Buffer.size()) { ReadFromFile(); LOG(debug) << "RouterInfo: buffer for " << GetIdentHashAbbreviation() << " loaded from file"; } - return m_Buffer.get(); + return m_Buffer.data(); } void RouterInfo::CreateBuffer(const PrivateKeys& private_keys) @@ -715,23 +694,20 @@ void RouterInfo::CreateBuffer(const PrivateKeys& private_keys) // Create RI core::StringStream router_info; CreateRouterInfo(router_info, private_keys); - if (router_info.Str().size() > Size::MaxBuffer) - throw std::length_error("created RI is too big"); // Create buffer - m_BufferLen = router_info.Str().size(); - if (!m_Buffer) - m_Buffer = std::make_unique(Size::MaxBuffer); - std::memcpy(m_Buffer.get(), router_info.Str().c_str(), m_BufferLen); + m_Buffer( + reinterpret_cast(router_info.Str().c_str()), + router_info.Str().size()); // Signature // TODO(anonimal): signing should be done when creating RI, not after. Requires other refactoring. private_keys.Sign( - reinterpret_cast(m_Buffer.get()), - m_BufferLen, - reinterpret_cast(m_Buffer.get()) + m_BufferLen); + m_Buffer.data(), + m_Buffer.size(), + m_Buffer.data() + m_Buffer.size()); - m_BufferLen += private_keys.GetPublic().GetSignatureLen(); + m_Buffer(m_Buffer.size() + private_keys.GetPublic().GetSignatureLen()); } catch (...) { @@ -916,11 +892,8 @@ void RouterInfo::SaveToFile(const std::string& path) if (stream.Fail()) throw std::runtime_error("RouterInfo: cannot open " + path); - // TODO(anonimal): buffer should be guaranteed - if (!m_Buffer) - throw std::length_error("RouterInfo: cannot save file, buffer is empty"); - - if (!stream.Write(m_Buffer.get(), m_BufferLen)) + assert(m_Buffer.size()); + if (!stream.Write(m_Buffer.data(), m_Buffer.size())) throw std::runtime_error("RouterInfo: cannot save " + path); } diff --git a/src/core/router/info.h b/src/core/router/info.h index 7178e886..a834498c 100644 --- a/src/core/router/info.h +++ b/src/core/router/info.h @@ -49,6 +49,7 @@ #include "core/router/identity.h" #include "core/router/profiling.h" +#include "core/util/buffer.h" #include "core/util/exception.h" #include "core/util/tag.h" #include "core/util/filesystem.h" @@ -543,21 +544,22 @@ class RouterInfo : public RouterInfoTraits, public RoutingDestination public: /// @return Pointer to RI buffer - const std::uint8_t* GetBuffer() const + const std::uint8_t* data() const { - return m_Buffer.get(); + return m_Buffer.data(); } /// @return RI buffer length - std::uint16_t GetBufferLen() const noexcept + std::uint16_t size() const noexcept { - return m_BufferLen; + return m_Buffer.size(); } - /// @brief Deletes RI buffer - void DeleteBuffer() + /// @brief Clear RI buffer + void clear() { - m_Buffer.reset(nullptr); + // TODO(unassigned): we may also want to clear all options + m_Buffer.clear(); } /// @return RI's router identity @@ -802,10 +804,9 @@ class RouterInfo : public RouterInfoTraits, public RoutingDestination private: core::Exception m_Exception; + core::Buffer m_Buffer; std::string m_Path; IdentityEx m_RouterIdentity; - std::unique_ptr m_Buffer; - std::uint16_t m_BufferLen{}; std::uint64_t m_Timestamp{}; std::vector
m_Addresses; std::map m_Options; diff --git a/src/core/router/net_db/impl.cc b/src/core/router/net_db/impl.cc index 1bef84d3..826c6071 100644 --- a/src/core/router/net_db/impl.cc +++ b/src/core/router/net_db/impl.cc @@ -339,7 +339,7 @@ bool NetDb::Load() || timestamp < router->GetTimestamp() + Time::RouterExpiration)) { - router->DeleteBuffer(); + router->clear(); router->GetOptions().clear(); // options are not used for regular routers // TODO(anonimal): review m_RouterInfos.insert(std::make_pair(router->GetIdentHash(), router)); if (router->HasCap(RouterInfo::Cap::Floodfill)) @@ -391,7 +391,7 @@ void NetDb::SaveUpdated() { it.second->SaveToFile(f); it.second->SetUpdated(false); it.second->SetUnreachable(false); - it.second->DeleteBuffer(); + it.second->clear(); count++; } else { // RouterInfo expires after N minutes if it uses an introducer @@ -714,7 +714,7 @@ void NetDb::HandleDatabaseLookupMsg( if (router) { LOG(debug) << "NetDb: requested RouterInfo " << key << " found"; router->LoadBuffer(); - if (router->GetBuffer()) + if (router->data()) reply_msg = CreateDatabaseStoreMsg(router); } } diff --git a/src/core/util/buffer.h b/src/core/util/buffer.h new file mode 100644 index 00000000..4cd73c14 --- /dev/null +++ b/src/core/util/buffer.h @@ -0,0 +1,154 @@ +/** // + * Copyright (c) 2015-2018, The Kovri I2P Router Project // + * // + * All rights reserved. // + * // + * Redistribution and use in source and binary forms, with or without modification, are // + * permitted provided that the following conditions are met: // + * // + * 1. Redistributions of source code must retain the above copyright notice, this list of // + * conditions and the following disclaimer. // + * // + * 2. Redistributions in binary form must reproduce the above copyright notice, this list // + * of conditions and the following disclaimer in the documentation and/or other // + * materials provided with the distribution. // + * // + * 3. Neither the name of the copyright holder nor the names of its contributors may be // + * used to endorse or promote products derived from this software without specific // + * prior written permission. // + * // + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + */ + +#ifndef SRC_CORE_UTIL_BUFFER_H_ +#define SRC_CORE_UTIL_BUFFER_H_ + +#include +#include +#include +#include +#include + +namespace kovri +{ +namespace core +{ +// TODO(anonimal): Boost.Buffer? +// TODO(anonimal): SecBlock buffer +// TODO(anonimal): This class should eventually replace Tag + +/// @brief A simple mutable array with sliding scale of element count (similar to vector) +template +class Buffer final +{ + static_assert(MaxElem, "Null max size"); + + public: + Buffer() : m_Length(MaxElem) {} + + Buffer(const std::uint8_t* buf, const std::size_t len) + { + set_buffer(buf, len); + } + + explicit Buffer(const std::size_t len) + { + set_length(len); + } + + explicit Buffer(const Buffer&) = delete; + + ~Buffer() = default; + + bool operator==(Buffer& other) + { + return (get() == other.get() && size() == other.size()); + } + + bool operator!=(Buffer& other) + { + return (get() != other.get() || size() != other.size()); + } + + void operator()(const std::uint8_t* buf, const std::size_t len) + { + set_buffer(buf, len); + } + + std::size_t operator()(const std::size_t len) + { + return set_length(len); + } + + public: + const auto& get() const noexcept + { + return m_Buffer; + } + + const std::uint8_t* data() const noexcept + { + return m_Buffer.data(); + } + + std::uint8_t* data() noexcept + { + return m_Buffer.data(); + } + + std::size_t capacity() const noexcept + { + return m_Buffer.size(); + } + + std::size_t size() const noexcept + { + return m_Length; + } + + void clear() + { + m_Buffer.fill(0); + m_Length = 0; + } + + private: + void set_buffer( + const std::uint8_t* data, + const std::size_t len) + { + assert(data || len); + + if (!data) + throw std::invalid_argument("Buffer: null source"); + + std::copy(data, data + set_length(len), m_Buffer.begin()); + } + + std::size_t set_length(const std::size_t len) + { + assert(len); + + if (len < MinElem || len > MaxElem) + throw std::length_error("Buffer: invalid length" + std::to_string(len)); + + m_Length = len; + return m_Length; + } + + private: + std::array m_Buffer{{}}; + std::size_t m_Length{}; ///< Number of expected elements +}; +} // namespace core +} // namespace kovri + +#endif // SRC_CORE_UTIL_BUFFER_H_ diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 142afef2..e9be09e5 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(kovri-tests core/router/identity.cc core/router/net_db/impl.cc core/router/transports/ssu/packet.cc + core/util/buffer.cc core/util/byte_stream.cc core/util/config.cc main.cc diff --git a/tests/unit_tests/core/util/buffer.cc b/tests/unit_tests/core/util/buffer.cc new file mode 100644 index 00000000..fdb367c6 --- /dev/null +++ b/tests/unit_tests/core/util/buffer.cc @@ -0,0 +1,129 @@ +/** // + * Copyright (c) 2015-2018, The Kovri I2P Router Project // + * // + * All rights reserved. // + * // + * Redistribution and use in source and binary forms, with or without modification, are // + * permitted provided that the following conditions are met: // + * // + * 1. Redistributions of source code must retain the above copyright notice, this list of // + * conditions and the following disclaimer. // + * // + * 2. Redistributions in binary form must reproduce the above copyright notice, this list // + * of conditions and the following disclaimer in the documentation and/or other // + * materials provided with the distribution. // + * // + * 3. Neither the name of the copyright holder nor the names of its contributors may be // + * used to endorse or promote products derived from this software without specific // + * prior written permission. // + * // + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + */ + +#define BOOST_TEST_DYN_LINK + +#include + +#include "core/util/buffer.h" + +struct BufferFixture +{ + core::Buffer<> buf; + std::array arr{{1, 2, 3}}; + enum { Max = 4096 }; +}; + +BOOST_FIXTURE_TEST_SUITE(Buffer, BufferFixture) + +BOOST_AUTO_TEST_CASE(Ctor) +{ + BOOST_CHECK_NO_THROW(core::Buffer<> buf(arr.data(), arr.size())); + + core::Buffer<> buf(arr.data(), arr.size()); + BOOST_CHECK_EQUAL(std::memcmp(buf.data(), arr.data(), buf.size()), 0); +} + +BOOST_AUTO_TEST_CASE(Comparison) +{ + core::Buffer<> comp; + BOOST_CHECK(comp == buf); + + comp(arr.data(), arr.size()); + BOOST_CHECK(comp != buf); + BOOST_CHECK_NE(std::memcmp(comp.data(), buf.data(), comp.size()), 0); + + core::Buffer<> elem(100); + BOOST_CHECK(comp != elem); + comp.clear(); + + BOOST_CHECK(comp != elem); + // New buffer should still be zero initialized + BOOST_CHECK_EQUAL(std::memcmp(comp.data(), elem.data(), comp.size()), 0); +} + +BOOST_AUTO_TEST_CASE(Empty) +{ + std::array max{{}}; + BOOST_CHECK(buf.get() == max); + BOOST_CHECK_EQUAL(std::memcmp(buf.data(), max.data(), max.size()), 0); + + buf.clear(); + BOOST_CHECK_EQUAL(buf.size(), 0); + BOOST_CHECK_EQUAL(buf.capacity(), Max); +} + +BOOST_AUTO_TEST_CASE(Data) +{ + core::Buffer<123, 456> buf; + std::array data{{}}; + + BOOST_CHECK_NO_THROW(buf(data.data(), data.size())); + BOOST_CHECK(buf.get() == data); + + data.fill(1); + + BOOST_CHECK_EQUAL(buf.size(), data.size()); + BOOST_CHECK(buf.get() != data); + + BOOST_CHECK_NO_THROW(buf(data.data(), data.size())); + BOOST_CHECK(buf.get() == data); +} + +BOOST_AUTO_TEST_CASE(Size) +{ + BOOST_CHECK_EQUAL(buf.size(), Max); + BOOST_CHECK_EQUAL(buf.capacity(), Max); + + BOOST_CHECK_NO_THROW(buf(100)); + BOOST_CHECK_EQUAL(buf.size(), 100); + BOOST_CHECK_EQUAL(buf.capacity(), Max); + + BOOST_CHECK_NO_THROW(buf(arr.data(), arr.size())); + BOOST_CHECK_EQUAL(buf.size(), arr.size()); + BOOST_CHECK_EQUAL(buf.capacity(), Max); +} + +BOOST_AUTO_TEST_CASE(InvalidBuffer) +{ + using Buf = core::Buffer<0, 1024>; + BOOST_CHECK_THROW(Buf buf(Max), std::exception); + BOOST_CHECK_THROW(Buf buf(-123), std::exception); + BOOST_CHECK_THROW(core::Buffer<> buf(Max + 1), std::exception); +} + +BOOST_AUTO_TEST_CASE(DataOverwrite) +{ + core::Buffer<0, 32> bad; + std::array data{{}}; + BOOST_CHECK_THROW(bad(data.data(), data.size()), std::exception); +} + +BOOST_AUTO_TEST_SUITE_END()