diff --git a/src/streams.h b/src/streams.h --- a/src/streams.h +++ b/src/streams.h @@ -120,6 +120,70 @@ size_t nPos; }; +/** + * Minimal stream for reading from an existing vector by reference + */ +class VectorReader { +private: + const int m_type; + const int m_version; + const std::vector &m_data; + size_t m_pos = 0; + +public: + /** + * @param[in] type Serialization Type + * @param[in] version Serialization Version (including any flags) + * @param[in] data Referenced byte vector to overwrite/append + * @param[in] pos Starting position. Vector index where reads should start. + */ + VectorReader(int type, int version, const std::vector &data, + size_t pos) + : m_type(type), m_version(version), m_data(data), m_pos(pos) { + if (m_pos > m_data.size()) { + throw std::ios_base::failure( + "VectorReader(...): end of data (m_pos > m_data.size())"); + } + } + + /** + * (other params same as above) + * @param[in] args A list of items to deserialize starting at pos. + */ + template + VectorReader(int type, int version, const std::vector &data, + size_t pos, Args &&... args) + : VectorReader(type, version, data, pos) { + ::UnserializeMany(*this, std::forward(args)...); + } + + template VectorReader &operator>>(T &obj) { + // Unserialize from this stream + ::Unserialize(*this, obj); + return (*this); + } + + int GetVersion() const { return m_version; } + int GetType() const { return m_type; } + + size_t size() const { return m_data.size() - m_pos; } + bool empty() const { return m_data.size() == m_pos; } + + void read(char *dst, size_t n) { + if (n == 0) { + return; + } + + // Read from the beginning of the buffer + size_t pos_next = m_pos + n; + if (pos_next > m_data.size()) { + throw std::ios_base::failure("VectorReader::read(): end of data"); + } + memcpy(dst, m_data.data() + m_pos, n); + m_pos = pos_next; + } +}; + /** * Double ended buffer combining vector and stream-like interfaces. * diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -72,6 +72,52 @@ vch.clear(); } +BOOST_AUTO_TEST_CASE(streams_vector_reader) { + std::vector vch = {1, 255, 3, 4, 5, 6}; + + VectorReader reader(SER_NETWORK, INIT_PROTO_VERSION, vch, 0); + BOOST_CHECK_EQUAL(reader.size(), 6); + BOOST_CHECK(!reader.empty()); + + // Read a single byte as an uint8_t. + uint8_t a; + reader >> a; + BOOST_CHECK_EQUAL(a, 1); + BOOST_CHECK_EQUAL(reader.size(), 5); + BOOST_CHECK(!reader.empty()); + + // Read a single byte as a (signed) int8_t. + int8_t b; + reader >> b; + BOOST_CHECK_EQUAL(b, -1); + BOOST_CHECK_EQUAL(reader.size(), 4); + BOOST_CHECK(!reader.empty()); + + // Read a 4 bytes as an unsigned uint32_t. + uint32_t c; + reader >> c; + // 100992003 = 3,4,5,6 in little-endian base-256 + BOOST_CHECK_EQUAL(c, 100992003); + BOOST_CHECK_EQUAL(reader.size(), 0); + BOOST_CHECK(reader.empty()); + + // Reading after end of byte vector throws an error. + int32_t d; + BOOST_CHECK_THROW(reader >> d, std::ios_base::failure); + + // Read a 4 bytes as a (signed) int32_t from the beginning of the buffer. + VectorReader new_reader(SER_NETWORK, INIT_PROTO_VERSION, vch, 0); + new_reader >> d; + // 67370753 = 1,255,3,4 in little-endian base-256 + BOOST_CHECK_EQUAL(d, 67370753); + BOOST_CHECK_EQUAL(new_reader.size(), 2); + BOOST_CHECK(!new_reader.empty()); + + // Reading after end of byte vector throws an error even if the reader is + // not totally empty. + BOOST_CHECK_THROW(new_reader >> d, std::ios_base::failure); +} + BOOST_AUTO_TEST_CASE(streams_serializedata_xor) { std::vector in; std::vector expected_xor;