2014-10-22 21:08:30 +02:00
|
|
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
2019-12-31 18:35:41 +01:00
|
|
|
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
2014-12-13 05:09:33 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2014-10-22 21:08:30 +02:00
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
#ifndef BITCOIN_STREAMS_H
|
|
|
|
#define BITCOIN_STREAMS_H
|
|
|
|
|
2020-03-19 23:46:56 +01:00
|
|
|
#include <serialize.h>
|
2023-09-10 21:23:19 +02:00
|
|
|
#include <span.h>
|
|
|
|
#include <support/allocators/zeroafterfree.h>
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <ios>
|
|
|
|
#include <limits>
|
2023-09-10 21:23:19 +02:00
|
|
|
#include <optional>
|
2014-10-22 21:08:30 +02:00
|
|
|
#include <stdint.h>
|
2015-01-21 01:23:25 +01:00
|
|
|
#include <stdio.h>
|
2014-10-22 21:08:30 +02:00
|
|
|
#include <string.h>
|
2023-09-10 21:23:19 +02:00
|
|
|
#include <string>
|
2014-10-22 21:08:30 +02:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
2021-05-26 14:04:39 +02:00
|
|
|
template<typename Stream>
|
|
|
|
class OverrideStream
|
|
|
|
{
|
|
|
|
Stream* stream;
|
2021-05-26 14:24:20 +02:00
|
|
|
|
2021-05-26 14:04:39 +02:00
|
|
|
const int nType;
|
|
|
|
const int nVersion;
|
|
|
|
|
2021-05-26 14:24:20 +02:00
|
|
|
public:
|
2021-05-26 14:04:39 +02:00
|
|
|
OverrideStream(Stream* stream_, int nType_, int nVersion_) : stream(stream_), nType(nType_), nVersion(nVersion_) {}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
OverrideStream<Stream>& operator<<(const T& obj)
|
|
|
|
{
|
|
|
|
// Serialize to this stream
|
2021-05-26 14:28:44 +02:00
|
|
|
::Serialize(*this, obj);
|
2021-05-26 14:04:39 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2021-05-27 16:59:09 +02:00
|
|
|
OverrideStream<Stream>& operator>>(T&& obj)
|
2021-05-26 14:04:39 +02:00
|
|
|
{
|
|
|
|
// Unserialize from this stream
|
2021-05-26 14:28:44 +02:00
|
|
|
::Unserialize(*this, obj);
|
2021-05-26 14:04:39 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
2021-05-26 14:28:44 +02:00
|
|
|
|
|
|
|
void write(const char* pch, size_t nSize)
|
|
|
|
{
|
|
|
|
stream->write(pch, nSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
void read(char* pch, size_t nSize)
|
|
|
|
{
|
|
|
|
stream->read(pch, nSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetVersion() const { return nVersion; }
|
|
|
|
int GetType() const { return nType; }
|
2021-08-03 11:15:44 +02:00
|
|
|
size_t size() const { return stream->size(); }
|
2021-05-29 22:24:52 +02:00
|
|
|
void ignore(size_t size) { return stream->ignore(size); }
|
2021-05-26 14:04:39 +02:00
|
|
|
};
|
|
|
|
|
2016-11-25 20:01:56 +01:00
|
|
|
/* Minimal stream for overwriting and/or appending to an existing byte vector
|
|
|
|
*
|
|
|
|
* The referenced vector will grow as necessary
|
|
|
|
*/
|
|
|
|
class CVectorWriter
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @param[in] nTypeIn Serialization Type
|
|
|
|
* @param[in] nVersionIn Serialization Version (including any flags)
|
|
|
|
* @param[in] vchDataIn Referenced byte vector to overwrite/append
|
|
|
|
* @param[in] nPosIn Starting position. Vector index where writes should start. The vector will initially
|
2018-02-02 11:35:42 +01:00
|
|
|
* grow as necessary to max(nPosIn, vec.size()). So to append, use vec.size().
|
2016-11-25 20:01:56 +01:00
|
|
|
*/
|
|
|
|
CVectorWriter(int nTypeIn, int nVersionIn, std::vector<unsigned char>& vchDataIn, size_t nPosIn) : nType(nTypeIn), nVersion(nVersionIn), vchData(vchDataIn), nPos(nPosIn)
|
|
|
|
{
|
|
|
|
if(nPos > vchData.size())
|
|
|
|
vchData.resize(nPos);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* (other params same as above)
|
2017-08-28 00:16:53 +02:00
|
|
|
* @param[in] args A list of items to serialize starting at nPosIn.
|
2016-11-25 20:01:56 +01:00
|
|
|
*/
|
|
|
|
template <typename... Args>
|
|
|
|
CVectorWriter(int nTypeIn, int nVersionIn, std::vector<unsigned char>& vchDataIn, size_t nPosIn, Args&&... args) : CVectorWriter(nTypeIn, nVersionIn, vchDataIn, nPosIn)
|
|
|
|
{
|
|
|
|
::SerializeMany(*this, std::forward<Args>(args)...);
|
|
|
|
}
|
|
|
|
void write(const char* pch, size_t nSize)
|
|
|
|
{
|
|
|
|
assert(nPos <= vchData.size());
|
|
|
|
size_t nOverwrite = std::min(nSize, vchData.size() - nPos);
|
|
|
|
if (nOverwrite) {
|
|
|
|
memcpy(vchData.data() + nPos, reinterpret_cast<const unsigned char*>(pch), nOverwrite);
|
|
|
|
}
|
|
|
|
if (nOverwrite < nSize) {
|
|
|
|
vchData.insert(vchData.end(), reinterpret_cast<const unsigned char*>(pch) + nOverwrite, reinterpret_cast<const unsigned char*>(pch) + nSize);
|
|
|
|
}
|
|
|
|
nPos += nSize;
|
|
|
|
}
|
|
|
|
template<typename T>
|
|
|
|
CVectorWriter& operator<<(const T& obj)
|
|
|
|
{
|
|
|
|
// Serialize to this stream
|
|
|
|
::Serialize(*this, obj);
|
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
int GetVersion() const
|
|
|
|
{
|
|
|
|
return nVersion;
|
|
|
|
}
|
|
|
|
int GetType() const
|
|
|
|
{
|
|
|
|
return nType;
|
|
|
|
}
|
2017-09-20 03:29:00 +02:00
|
|
|
size_t size() const
|
|
|
|
{
|
|
|
|
return vchData.size() - nPos;
|
|
|
|
}
|
2016-11-25 20:01:56 +01:00
|
|
|
private:
|
|
|
|
const int nType;
|
|
|
|
const int nVersion;
|
|
|
|
std::vector<unsigned char>& vchData;
|
|
|
|
size_t nPos;
|
|
|
|
};
|
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
/** Minimal stream for reading from an existing vector by reference
|
|
|
|
*/
|
|
|
|
class VectorReader
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
const int m_type;
|
|
|
|
const int m_version;
|
|
|
|
const std::vector<unsigned char>& m_data;
|
|
|
|
size_t m_pos = 0;
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
/**
|
2018-08-26 16:57:01 +02:00
|
|
|
* @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<unsigned char>& data, size_t pos)
|
2019-01-05 15:06:38 +01:00
|
|
|
: m_type(type), m_version(version), m_data(data), m_pos(pos)
|
2018-08-26 16:57:01 +02:00
|
|
|
{
|
2019-01-05 15:06:38 +01:00
|
|
|
if (m_pos > m_data.size()) {
|
|
|
|
throw std::ios_base::failure("VectorReader(...): end of data (m_pos > m_data.size())");
|
|
|
|
}
|
2018-08-26 16:57:01 +02:00
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
/**
|
2018-08-26 16:57:01 +02:00
|
|
|
* (other params same as above)
|
|
|
|
* @param[in] args A list of items to deserialize starting at pos.
|
|
|
|
*/
|
|
|
|
template <typename... Args>
|
|
|
|
VectorReader(int type, int version, const std::vector<unsigned char>& data, size_t pos,
|
|
|
|
Args&&... args)
|
|
|
|
: VectorReader(type, version, data, pos)
|
|
|
|
{
|
|
|
|
::UnserializeMany(*this, std::forward<Args>(args)...);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2021-05-10 08:22:05 +02:00
|
|
|
VectorReader& operator>>(T&& obj)
|
2018-08-26 16:57:01 +02:00
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
/** Double ended buffer combining vector and stream-like interfaces.
|
|
|
|
*
|
|
|
|
* >> and << read and write unformatted data using the above serialization templates.
|
|
|
|
* Fills with data in linear time; some stringstream implementations take N^2 time.
|
|
|
|
*/
|
|
|
|
class CDataStream
|
|
|
|
{
|
|
|
|
protected:
|
2023-09-10 21:23:19 +02:00
|
|
|
using vector_type = SerializeData;
|
2014-10-22 21:08:30 +02:00
|
|
|
vector_type vch;
|
2023-09-10 21:23:19 +02:00
|
|
|
unsigned int nReadPos{0};
|
2016-11-09 12:32:57 +01:00
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
int nType;
|
|
|
|
int nVersion;
|
|
|
|
|
2023-09-10 21:23:19 +02:00
|
|
|
public:
|
2014-10-22 21:08:30 +02:00
|
|
|
typedef vector_type::allocator_type allocator_type;
|
|
|
|
typedef vector_type::size_type size_type;
|
|
|
|
typedef vector_type::difference_type difference_type;
|
|
|
|
typedef vector_type::reference reference;
|
|
|
|
typedef vector_type::const_reference const_reference;
|
|
|
|
typedef vector_type::value_type value_type;
|
|
|
|
typedef vector_type::iterator iterator;
|
|
|
|
typedef vector_type::const_iterator const_iterator;
|
|
|
|
typedef vector_type::reverse_iterator reverse_iterator;
|
|
|
|
|
|
|
|
explicit CDataStream(int nTypeIn, int nVersionIn)
|
2023-09-10 21:23:19 +02:00
|
|
|
: nType{nTypeIn},
|
|
|
|
nVersion{nVersionIn} {}
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2021-11-04 09:17:14 +01:00
|
|
|
explicit CDataStream(Span<const value_type> sp, int nTypeIn, int nVersionIn)
|
2023-09-10 21:23:19 +02:00
|
|
|
: vch(sp.data(), sp.data() + sp.size()),
|
|
|
|
nType{nTypeIn},
|
|
|
|
nVersion{nVersionIn} {}
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2017-07-27 16:28:05 +02:00
|
|
|
template <typename... Args>
|
|
|
|
CDataStream(int nTypeIn, int nVersionIn, Args&&... args)
|
2023-09-10 21:23:19 +02:00
|
|
|
: nType{nTypeIn},
|
|
|
|
nVersion{nVersionIn}
|
2017-07-27 16:28:05 +02:00
|
|
|
{
|
2016-11-09 12:32:57 +01:00
|
|
|
::SerializeMany(*this, std::forward<Args>(args)...);
|
2017-07-27 16:28:05 +02:00
|
|
|
}
|
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
std::string str() const
|
|
|
|
{
|
|
|
|
return (std::string(begin(), end()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Vector subset
|
|
|
|
//
|
|
|
|
const_iterator begin() const { return vch.begin() + nReadPos; }
|
|
|
|
iterator begin() { return vch.begin() + nReadPos; }
|
|
|
|
const_iterator end() const { return vch.end(); }
|
|
|
|
iterator end() { return vch.end(); }
|
|
|
|
size_type size() const { return vch.size() - nReadPos; }
|
|
|
|
bool empty() const { return vch.size() == nReadPos; }
|
2021-11-04 09:17:14 +01:00
|
|
|
void resize(size_type n, value_type c = value_type{}) { vch.resize(n + nReadPos, c); }
|
2014-10-22 21:08:30 +02:00
|
|
|
void reserve(size_type n) { vch.reserve(n + nReadPos); }
|
|
|
|
const_reference operator[](size_type pos) const { return vch[pos + nReadPos]; }
|
|
|
|
reference operator[](size_type pos) { return vch[pos + nReadPos]; }
|
|
|
|
void clear() { vch.clear(); nReadPos = 0; }
|
2021-11-04 09:17:14 +01:00
|
|
|
iterator insert(iterator it, const value_type x) { return vch.insert(it, x); }
|
|
|
|
void insert(iterator it, size_type n, const value_type x) { vch.insert(it, n, x); }
|
2017-01-09 17:39:08 +01:00
|
|
|
value_type* data() { return vch.data() + nReadPos; }
|
|
|
|
const value_type* data() const { return vch.data() + nReadPos; }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2021-11-04 09:17:14 +01:00
|
|
|
void insert(iterator it, std::vector<value_type>::const_iterator first, std::vector<value_type>::const_iterator last)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
2017-04-21 17:33:22 +02:00
|
|
|
if (last == first) return;
|
|
|
|
assert(last - first > 0);
|
2014-10-22 21:08:30 +02:00
|
|
|
if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos)
|
|
|
|
{
|
|
|
|
// special case for inserting at the front when there's room
|
|
|
|
nReadPos -= (last - first);
|
|
|
|
memcpy(&vch[nReadPos], &first[0], last - first);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
vch.insert(it, first, last);
|
|
|
|
}
|
|
|
|
|
2021-11-04 09:17:14 +01:00
|
|
|
void insert(iterator it, const value_type* first, const value_type* last)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
2017-04-21 17:33:22 +02:00
|
|
|
if (last == first) return;
|
|
|
|
assert(last - first > 0);
|
2014-10-22 21:08:30 +02:00
|
|
|
if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos)
|
|
|
|
{
|
|
|
|
// special case for inserting at the front when there's room
|
|
|
|
nReadPos -= (last - first);
|
|
|
|
memcpy(&vch[nReadPos], &first[0], last - first);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
vch.insert(it, first, last);
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator erase(iterator it)
|
|
|
|
{
|
|
|
|
if (it == vch.begin() + nReadPos)
|
|
|
|
{
|
|
|
|
// special case for erasing from the front
|
|
|
|
if (++nReadPos >= vch.size())
|
|
|
|
{
|
|
|
|
// whenever we reach the end, we take the opportunity to clear the buffer
|
|
|
|
nReadPos = 0;
|
|
|
|
return vch.erase(vch.begin(), vch.end());
|
|
|
|
}
|
|
|
|
return vch.begin() + nReadPos;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return vch.erase(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator erase(iterator first, iterator last)
|
|
|
|
{
|
|
|
|
if (first == vch.begin() + nReadPos)
|
|
|
|
{
|
|
|
|
// special case for erasing from the front
|
|
|
|
if (last == vch.end())
|
|
|
|
{
|
|
|
|
nReadPos = 0;
|
|
|
|
return vch.erase(vch.begin(), vch.end());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nReadPos = (last - vch.begin());
|
|
|
|
return last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return vch.erase(first, last);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void Compact()
|
|
|
|
{
|
|
|
|
vch.erase(vch.begin(), vch.begin() + nReadPos);
|
|
|
|
nReadPos = 0;
|
|
|
|
}
|
|
|
|
|
2023-09-10 21:23:19 +02:00
|
|
|
bool Rewind(std::optional<size_type> n = std::nullopt)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
2023-09-10 21:23:19 +02:00
|
|
|
// Total rewind if no size is passed
|
|
|
|
if (!n) {
|
|
|
|
nReadPos = 0;
|
|
|
|
return true;
|
|
|
|
}
|
2014-10-22 21:08:30 +02:00
|
|
|
// Rewind by n characters if the buffer hasn't been compacted yet
|
2023-09-10 21:23:19 +02:00
|
|
|
if (*n > nReadPos)
|
2014-10-22 21:08:30 +02:00
|
|
|
return false;
|
2023-09-10 21:23:19 +02:00
|
|
|
nReadPos -= *n;
|
2014-10-22 21:08:30 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Stream subset
|
|
|
|
//
|
|
|
|
bool eof() const { return size() == 0; }
|
|
|
|
CDataStream* rdbuf() { return this; }
|
2017-08-16 02:09:10 +02:00
|
|
|
int in_avail() const { return size(); }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
void SetType(int n) { nType = n; }
|
2016-11-09 12:32:57 +01:00
|
|
|
int GetType() const { return nType; }
|
2014-10-22 21:08:30 +02:00
|
|
|
void SetVersion(int n) { nVersion = n; }
|
2016-11-09 12:32:57 +01:00
|
|
|
int GetVersion() const { return nVersion; }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void read(char* pch, size_t nSize)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
2016-11-09 12:32:57 +01:00
|
|
|
if (nSize == 0) return;
|
2017-04-21 17:33:22 +02:00
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
// Read from the beginning of the buffer
|
|
|
|
unsigned int nReadPosNext = nReadPos + nSize;
|
2017-11-09 13:15:58 +01:00
|
|
|
if (nReadPosNext > vch.size()) {
|
|
|
|
throw std::ios_base::failure("CDataStream::read(): end of data");
|
|
|
|
}
|
2020-02-06 16:45:57 +01:00
|
|
|
memcpy(pch, &vch[nReadPos], nSize);
|
2017-11-09 13:15:58 +01:00
|
|
|
if (nReadPosNext == vch.size())
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
nReadPos = 0;
|
|
|
|
vch.clear();
|
2016-11-09 12:32:57 +01:00
|
|
|
return;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
nReadPos = nReadPosNext;
|
|
|
|
}
|
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void ignore(int nSize)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
// Ignore from the beginning of the buffer
|
2016-04-26 13:23:02 +02:00
|
|
|
if (nSize < 0) {
|
|
|
|
throw std::ios_base::failure("CDataStream::ignore(): nSize negative");
|
|
|
|
}
|
2014-10-22 21:08:30 +02:00
|
|
|
unsigned int nReadPosNext = nReadPos + nSize;
|
|
|
|
if (nReadPosNext >= vch.size())
|
|
|
|
{
|
|
|
|
if (nReadPosNext > vch.size())
|
2015-01-08 11:44:25 +01:00
|
|
|
throw std::ios_base::failure("CDataStream::ignore(): end of data");
|
2014-10-22 21:08:30 +02:00
|
|
|
nReadPos = 0;
|
|
|
|
vch.clear();
|
2016-11-09 12:32:57 +01:00
|
|
|
return;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
nReadPos = nReadPosNext;
|
|
|
|
}
|
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void write(const char* pch, size_t nSize)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
// Write to the end of the buffer
|
|
|
|
vch.insert(vch.end(), pch, pch + nSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename Stream>
|
2016-11-09 12:32:57 +01:00
|
|
|
void Serialize(Stream& s) const
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
// Special case: stream << stream concatenates like stream += stream
|
|
|
|
if (!vch.empty())
|
2017-07-13 01:23:59 +02:00
|
|
|
s.write((char*)vch.data(), vch.size() * sizeof(value_type));
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
CDataStream& operator<<(const T& obj)
|
|
|
|
{
|
|
|
|
// Serialize to this stream
|
2016-11-09 12:32:57 +01:00
|
|
|
::Serialize(*this, obj);
|
2014-10-22 21:08:30 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2020-12-17 03:07:07 +01:00
|
|
|
CDataStream& operator>>(T&& obj)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
// Unserialize from this stream
|
2016-11-09 12:32:57 +01:00
|
|
|
::Unserialize(*this, obj);
|
2014-10-22 21:08:30 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
|
2015-09-08 00:22:23 +02:00
|
|
|
/**
|
|
|
|
* XOR the contents of this stream with a certain key.
|
|
|
|
*
|
|
|
|
* @param[in] key The key used to XOR the data in this stream.
|
|
|
|
*/
|
|
|
|
void Xor(const std::vector<unsigned char>& key)
|
|
|
|
{
|
|
|
|
if (key.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_type i = 0, j = 0; i != size(); i++) {
|
|
|
|
vch[i] ^= key[j++];
|
|
|
|
|
|
|
|
// This potentially acts on very many bytes of data, so it's
|
|
|
|
// important that we calculate `j`, i.e. the `key` index in this
|
|
|
|
// way instead of doing a %, which would effectively be a division
|
|
|
|
// for each byte Xor'd -- much slower than need be.
|
|
|
|
if (j == key.size())
|
|
|
|
j = 0;
|
|
|
|
}
|
|
|
|
}
|
2014-10-22 21:08:30 +02:00
|
|
|
};
|
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
template <typename IStream>
|
|
|
|
class BitStreamReader
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
IStream& m_istream;
|
|
|
|
|
|
|
|
/// Buffered byte read in from the input stream. A new byte is read into the
|
|
|
|
/// buffer when m_offset reaches 8.
|
|
|
|
uint8_t m_buffer{0};
|
|
|
|
|
|
|
|
/// Number of high order bits in m_buffer already returned by previous
|
|
|
|
/// Read() calls. The next bit to be returned is at this offset from the
|
|
|
|
/// most significant bit position.
|
|
|
|
int m_offset{8};
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit BitStreamReader(IStream& istream) : m_istream(istream) {}
|
|
|
|
|
|
|
|
/** Read the specified number of bits from the stream. The data is returned
|
2018-09-06 00:12:39 +02:00
|
|
|
* in the nbits least significant bits of a 64-bit uint.
|
2018-08-26 16:57:01 +02:00
|
|
|
*/
|
|
|
|
uint64_t Read(int nbits) {
|
|
|
|
if (nbits < 0 || nbits > 64) {
|
|
|
|
throw std::out_of_range("nbits must be between 0 and 64");
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t data = 0;
|
|
|
|
while (nbits > 0) {
|
|
|
|
if (m_offset == 8) {
|
|
|
|
m_istream >> m_buffer;
|
|
|
|
m_offset = 0;
|
|
|
|
}
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
int bits = std::min(8 - m_offset, nbits);
|
|
|
|
data <<= bits;
|
|
|
|
data |= static_cast<uint8_t>(m_buffer << m_offset) >> (8 - bits);
|
|
|
|
m_offset += bits;
|
|
|
|
nbits -= bits;
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
};
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
template <typename OStream>
|
|
|
|
class BitStreamWriter
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
OStream& m_ostream;
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
/// Buffered byte waiting to be written to the output stream. The byte is
|
|
|
|
/// written buffer when m_offset reaches 8 or Flush() is called.
|
|
|
|
uint8_t m_buffer{0};
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
/// Number of high order bits in m_buffer already written by previous
|
|
|
|
/// Write() calls and not yet flushed to the stream. The next bit to be
|
|
|
|
/// written to is at this offset from the most significant bit position.
|
|
|
|
int m_offset{0};
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
public:
|
|
|
|
explicit BitStreamWriter(OStream& ostream) : m_ostream(ostream) {}
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2018-08-26 16:57:01 +02:00
|
|
|
~BitStreamWriter()
|
|
|
|
{
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Write the nbits least significant bits of a 64-bit int to the output
|
|
|
|
* stream. Data is buffered until it completes an octet.
|
|
|
|
*/
|
|
|
|
void Write(uint64_t data, int nbits) {
|
|
|
|
if (nbits < 0 || nbits > 64) {
|
|
|
|
throw std::out_of_range("nbits must be between 0 and 64");
|
|
|
|
}
|
|
|
|
|
|
|
|
while (nbits > 0) {
|
|
|
|
int bits = std::min(8 - m_offset, nbits);
|
|
|
|
m_buffer |= (data << (64 - nbits)) >> (64 - 8 + m_offset);
|
|
|
|
m_offset += bits;
|
|
|
|
nbits -= bits;
|
|
|
|
|
|
|
|
if (m_offset == 8) {
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Flush any unwritten bits to the output stream, padding with 0's to the
|
|
|
|
* next byte boundary.
|
|
|
|
*/
|
|
|
|
void Flush() {
|
|
|
|
if (m_offset == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ostream << m_buffer;
|
|
|
|
m_buffer = 0;
|
|
|
|
m_offset = 0;
|
|
|
|
}
|
|
|
|
};
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Non-refcounted RAII wrapper for FILE*
|
|
|
|
*
|
|
|
|
* Will automatically close the file when it goes out of scope if not null.
|
|
|
|
* If you're returning the file pointer, return file.release().
|
|
|
|
* If you need to close the file early, use file.fclose() instead of fclose(file).
|
|
|
|
*/
|
|
|
|
class CAutoFile
|
|
|
|
{
|
|
|
|
private:
|
2016-11-09 12:32:57 +01:00
|
|
|
const int nType;
|
|
|
|
const int nVersion;
|
|
|
|
|
2020-07-29 03:23:12 +02:00
|
|
|
FILE* file;
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
public:
|
2016-11-09 12:32:57 +01:00
|
|
|
CAutoFile(FILE* filenew, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
file = filenew;
|
|
|
|
}
|
|
|
|
|
|
|
|
~CAutoFile()
|
|
|
|
{
|
|
|
|
fclose();
|
|
|
|
}
|
|
|
|
|
2017-09-22 07:33:06 +02:00
|
|
|
// Disallow copies
|
|
|
|
CAutoFile(const CAutoFile&) = delete;
|
|
|
|
CAutoFile& operator=(const CAutoFile&) = delete;
|
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
void fclose()
|
|
|
|
{
|
|
|
|
if (file) {
|
|
|
|
::fclose(file);
|
2019-08-06 05:08:33 +02:00
|
|
|
file = nullptr;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get wrapped FILE* with transfer of ownership.
|
|
|
|
* @note This will invalidate the CAutoFile object, and makes it the responsibility of the caller
|
|
|
|
* of this function to clean up the returned FILE*.
|
|
|
|
*/
|
2019-08-06 05:08:33 +02:00
|
|
|
FILE* release() { FILE* ret = file; file = nullptr; return ret; }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
/** Get wrapped FILE* without transfer of ownership.
|
|
|
|
* @note Ownership of the FILE* will remain with this class. Use this only if the scope of the
|
|
|
|
* CAutoFile outlives use of the passed pointer.
|
|
|
|
*/
|
|
|
|
FILE* Get() const { return file; }
|
|
|
|
|
2019-08-06 05:08:33 +02:00
|
|
|
/** Return true if the wrapped FILE* is nullptr, false otherwise.
|
2014-10-22 21:08:30 +02:00
|
|
|
*/
|
2019-08-06 05:08:33 +02:00
|
|
|
bool IsNull() const { return (file == nullptr); }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// Stream subset
|
|
|
|
//
|
2016-11-09 12:32:57 +01:00
|
|
|
int GetType() const { return nType; }
|
|
|
|
int GetVersion() const { return nVersion; }
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void read(char* pch, size_t nSize)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
if (!file)
|
2019-08-06 05:08:33 +02:00
|
|
|
throw std::ios_base::failure("CAutoFile::read: file handle is nullptr");
|
2014-10-22 21:08:30 +02:00
|
|
|
if (fread(pch, 1, nSize, file) != nSize)
|
2015-01-08 11:44:25 +01:00
|
|
|
throw std::ios_base::failure(feof(file) ? "CAutoFile::read: end of file" : "CAutoFile::read: fread failed");
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void ignore(size_t nSize)
|
2016-04-26 13:23:02 +02:00
|
|
|
{
|
|
|
|
if (!file)
|
2019-08-06 05:08:33 +02:00
|
|
|
throw std::ios_base::failure("CAutoFile::ignore: file handle is nullptr");
|
2016-04-26 13:23:02 +02:00
|
|
|
unsigned char data[4096];
|
|
|
|
while (nSize > 0) {
|
|
|
|
size_t nNow = std::min<size_t>(nSize, sizeof(data));
|
|
|
|
if (fread(data, 1, nNow, file) != nNow)
|
|
|
|
throw std::ios_base::failure(feof(file) ? "CAutoFile::ignore: end of file" : "CAutoFile::read: fread failed");
|
|
|
|
nSize -= nNow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
void write(const char* pch, size_t nSize)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
if (!file)
|
2019-08-06 05:08:33 +02:00
|
|
|
throw std::ios_base::failure("CAutoFile::write: file handle is nullptr");
|
2014-10-22 21:08:30 +02:00
|
|
|
if (fwrite(pch, 1, nSize, file) != nSize)
|
2015-01-08 11:44:25 +01:00
|
|
|
throw std::ios_base::failure("CAutoFile::write: write failed");
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
CAutoFile& operator<<(const T& obj)
|
|
|
|
{
|
|
|
|
// Serialize to this stream
|
|
|
|
if (!file)
|
2019-08-06 05:08:33 +02:00
|
|
|
throw std::ios_base::failure("CAutoFile::operator<<: file handle is nullptr");
|
2016-11-09 12:32:57 +01:00
|
|
|
::Serialize(*this, obj);
|
2014-10-22 21:08:30 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2020-12-17 03:07:07 +01:00
|
|
|
CAutoFile& operator>>(T&& obj)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
|
|
|
// Unserialize from this stream
|
|
|
|
if (!file)
|
2019-08-06 05:08:33 +02:00
|
|
|
throw std::ios_base::failure("CAutoFile::operator>>: file handle is nullptr");
|
2016-11-09 12:32:57 +01:00
|
|
|
::Unserialize(*this, obj);
|
2014-10-22 21:08:30 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Non-refcounted RAII wrapper around a FILE* that implements a ring buffer to
|
|
|
|
* deserialize from. It guarantees the ability to rewind a given number of bytes.
|
|
|
|
*
|
|
|
|
* Will automatically close the file when it goes out of scope if not null.
|
|
|
|
* If you need to close the file early, use file.fclose() instead of fclose(file).
|
|
|
|
*/
|
|
|
|
class CBufferedFile
|
|
|
|
{
|
|
|
|
private:
|
2016-11-09 12:32:57 +01:00
|
|
|
const int nType;
|
|
|
|
const int nVersion;
|
2014-10-22 21:08:30 +02:00
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
FILE *src; //!< source file
|
|
|
|
uint64_t nSrcPos; //!< how many bytes have been read from source
|
|
|
|
uint64_t nReadPos; //!< how many bytes have been read from this
|
|
|
|
uint64_t nReadLimit; //!< up to which position we're allowed to read
|
|
|
|
uint64_t nRewind; //!< how many bytes we guarantee to rewind
|
|
|
|
std::vector<char> vchBuf; //!< the buffer
|
2014-10-22 21:08:30 +02:00
|
|
|
|
|
|
|
protected:
|
2019-01-27 16:11:07 +01:00
|
|
|
//! read data from the source to fill the buffer
|
2014-10-22 21:08:30 +02:00
|
|
|
bool Fill() {
|
|
|
|
unsigned int pos = nSrcPos % vchBuf.size();
|
|
|
|
unsigned int readNow = vchBuf.size() - pos;
|
|
|
|
unsigned int nAvail = vchBuf.size() - (nSrcPos - nReadPos) - nRewind;
|
|
|
|
if (nAvail < readNow)
|
|
|
|
readNow = nAvail;
|
|
|
|
if (readNow == 0)
|
|
|
|
return false;
|
2017-03-03 15:48:18 +01:00
|
|
|
size_t nBytes = fread((void*)&vchBuf[pos], 1, readNow, src);
|
|
|
|
if (nBytes == 0) {
|
2015-01-08 11:44:25 +01:00
|
|
|
throw std::ios_base::failure(feof(src) ? "CBufferedFile::Fill: end of file" : "CBufferedFile::Fill: fread failed");
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
Merge #16577: util: CBufferedFile fixes and unit test
efd2474d17098c754367b844ec646ebececc7c74 util: CBufferedFile fixes (Larry Ruane)
Pull request description:
The `CBufferedFile` object guarantees its user is able to "rewind" the data stream (that's being read from a file) up to a certain number of bytes, as specified by the user in the constructor. This guarantee is not honored due to a bug in the `SetPos` method.
Such rewinding is done in `LoadExternalBlockFile()` (currently the only user of this object), which deserializes a series of `CBlock` objects. If that function encounters something unexpected in the data stream, which is coming from a `blocks/blk00???.dat` file, it "rewinds" to an earlier position in the stream to try to get in sync again. The `CBufferedFile` object does not actually rewind its file offset; it simply repositions its internal offset, `nReadPos`, to an earlier position within the object's private buffer; this is why there's a limit to how far the user may rewind.
If `LoadExternalBlockFile()` needs to rewind (call `blkdat.SetPos()`), the stream may not be positioned as it should be, causing errors in deserialization. This need to rewind is probably rare, which is likely why this bug hasn't been noticed already. But if this object is used elsewhere in the future, this could be a serious problem, especially as, due to the nature of the bug, the `SetPos()` _sometimes_ works.
This PR adds a unit test for `CBufferedFile` that fails due to this bug. (Until now it has had no unit tests.) The unit test provides good documentation and examples for developers trying to understand `LoadExternalBlockFile()` and for future users of this object.
This PR also adds code to throw an exception from the constructor if the rewind argument is not less than the buffer size (since that doesn't make any sense).
Finally, I discovered that the object is too restrictive in one respect: When the deserialization methods call this object's `read` method, a check ensures that the number of bytes being requested is less than the size of the buffer (adjusting for the rewind size), else it throws an exception. This restriction is unnecessary; the object being deserialized can be larger than the buffer because multiple reads from disk can satisfy the request.
ACKs for top commit:
laanwj:
ACK ~after squash.~ efd2474d17098c754367b844ec646ebececc7c74
mzumsande:
I had intended to follow up earlier on my last comment, ACK efd2474d17098c754367b844ec646ebececc7c74. I reviewed the code, ran tests and did a successful reindex on testnet with this branch.
Tree-SHA512: 695529e0af38bae2af4e0cc2895dda56a71b9059c3de04d32e09c0165a50f6aacee499f2042156ab5eaa6f0349bab6bcca4ef9f6f9ded4e60d4483beab7e4554
2019-09-26 13:37:06 +02:00
|
|
|
nSrcPos += nBytes;
|
|
|
|
return true;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
CBufferedFile(FILE *fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) :
|
2018-12-06 15:35:13 +01:00
|
|
|
nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, 0)
|
2014-10-22 21:08:30 +02:00
|
|
|
{
|
Merge #16577: util: CBufferedFile fixes and unit test
efd2474d17098c754367b844ec646ebececc7c74 util: CBufferedFile fixes (Larry Ruane)
Pull request description:
The `CBufferedFile` object guarantees its user is able to "rewind" the data stream (that's being read from a file) up to a certain number of bytes, as specified by the user in the constructor. This guarantee is not honored due to a bug in the `SetPos` method.
Such rewinding is done in `LoadExternalBlockFile()` (currently the only user of this object), which deserializes a series of `CBlock` objects. If that function encounters something unexpected in the data stream, which is coming from a `blocks/blk00???.dat` file, it "rewinds" to an earlier position in the stream to try to get in sync again. The `CBufferedFile` object does not actually rewind its file offset; it simply repositions its internal offset, `nReadPos`, to an earlier position within the object's private buffer; this is why there's a limit to how far the user may rewind.
If `LoadExternalBlockFile()` needs to rewind (call `blkdat.SetPos()`), the stream may not be positioned as it should be, causing errors in deserialization. This need to rewind is probably rare, which is likely why this bug hasn't been noticed already. But if this object is used elsewhere in the future, this could be a serious problem, especially as, due to the nature of the bug, the `SetPos()` _sometimes_ works.
This PR adds a unit test for `CBufferedFile` that fails due to this bug. (Until now it has had no unit tests.) The unit test provides good documentation and examples for developers trying to understand `LoadExternalBlockFile()` and for future users of this object.
This PR also adds code to throw an exception from the constructor if the rewind argument is not less than the buffer size (since that doesn't make any sense).
Finally, I discovered that the object is too restrictive in one respect: When the deserialization methods call this object's `read` method, a check ensures that the number of bytes being requested is less than the size of the buffer (adjusting for the rewind size), else it throws an exception. This restriction is unnecessary; the object being deserialized can be larger than the buffer because multiple reads from disk can satisfy the request.
ACKs for top commit:
laanwj:
ACK ~after squash.~ efd2474d17098c754367b844ec646ebececc7c74
mzumsande:
I had intended to follow up earlier on my last comment, ACK efd2474d17098c754367b844ec646ebececc7c74. I reviewed the code, ran tests and did a successful reindex on testnet with this branch.
Tree-SHA512: 695529e0af38bae2af4e0cc2895dda56a71b9059c3de04d32e09c0165a50f6aacee499f2042156ab5eaa6f0349bab6bcca4ef9f6f9ded4e60d4483beab7e4554
2019-09-26 13:37:06 +02:00
|
|
|
if (nRewindIn >= nBufSize)
|
|
|
|
throw std::ios_base::failure("Rewind limit must be less than buffer size");
|
2014-10-22 21:08:30 +02:00
|
|
|
src = fileIn;
|
|
|
|
}
|
|
|
|
|
|
|
|
~CBufferedFile()
|
|
|
|
{
|
|
|
|
fclose();
|
|
|
|
}
|
|
|
|
|
2017-09-22 07:33:06 +02:00
|
|
|
// Disallow copies
|
|
|
|
CBufferedFile(const CBufferedFile&) = delete;
|
|
|
|
CBufferedFile& operator=(const CBufferedFile&) = delete;
|
|
|
|
|
2016-11-09 12:32:57 +01:00
|
|
|
int GetVersion() const { return nVersion; }
|
|
|
|
int GetType() const { return nType; }
|
|
|
|
|
2014-10-22 21:08:30 +02:00
|
|
|
void fclose()
|
|
|
|
{
|
|
|
|
if (src) {
|
|
|
|
::fclose(src);
|
2019-08-06 05:08:33 +02:00
|
|
|
src = nullptr;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! check whether we're at the end of the source file
|
2014-10-22 21:08:30 +02:00
|
|
|
bool eof() const {
|
|
|
|
return nReadPos == nSrcPos && feof(src);
|
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! read a number of bytes
|
2016-11-09 12:32:57 +01:00
|
|
|
void read(char *pch, size_t nSize) {
|
2014-10-22 21:08:30 +02:00
|
|
|
if (nSize + nReadPos > nReadLimit)
|
|
|
|
throw std::ios_base::failure("Read attempted past buffer limit");
|
|
|
|
while (nSize > 0) {
|
|
|
|
if (nReadPos == nSrcPos)
|
|
|
|
Fill();
|
|
|
|
unsigned int pos = nReadPos % vchBuf.size();
|
|
|
|
size_t nNow = nSize;
|
|
|
|
if (nNow + pos > vchBuf.size())
|
|
|
|
nNow = vchBuf.size() - pos;
|
|
|
|
if (nNow + nReadPos > nSrcPos)
|
|
|
|
nNow = nSrcPos - nReadPos;
|
|
|
|
memcpy(pch, &vchBuf[pos], nNow);
|
|
|
|
nReadPos += nNow;
|
|
|
|
pch += nNow;
|
|
|
|
nSize -= nNow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! return the current reading position
|
2017-08-16 02:09:10 +02:00
|
|
|
uint64_t GetPos() const {
|
2014-10-22 21:08:30 +02:00
|
|
|
return nReadPos;
|
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! rewind to a given reading position
|
2014-10-22 21:08:30 +02:00
|
|
|
bool SetPos(uint64_t nPos) {
|
Merge #16577: util: CBufferedFile fixes and unit test
efd2474d17098c754367b844ec646ebececc7c74 util: CBufferedFile fixes (Larry Ruane)
Pull request description:
The `CBufferedFile` object guarantees its user is able to "rewind" the data stream (that's being read from a file) up to a certain number of bytes, as specified by the user in the constructor. This guarantee is not honored due to a bug in the `SetPos` method.
Such rewinding is done in `LoadExternalBlockFile()` (currently the only user of this object), which deserializes a series of `CBlock` objects. If that function encounters something unexpected in the data stream, which is coming from a `blocks/blk00???.dat` file, it "rewinds" to an earlier position in the stream to try to get in sync again. The `CBufferedFile` object does not actually rewind its file offset; it simply repositions its internal offset, `nReadPos`, to an earlier position within the object's private buffer; this is why there's a limit to how far the user may rewind.
If `LoadExternalBlockFile()` needs to rewind (call `blkdat.SetPos()`), the stream may not be positioned as it should be, causing errors in deserialization. This need to rewind is probably rare, which is likely why this bug hasn't been noticed already. But if this object is used elsewhere in the future, this could be a serious problem, especially as, due to the nature of the bug, the `SetPos()` _sometimes_ works.
This PR adds a unit test for `CBufferedFile` that fails due to this bug. (Until now it has had no unit tests.) The unit test provides good documentation and examples for developers trying to understand `LoadExternalBlockFile()` and for future users of this object.
This PR also adds code to throw an exception from the constructor if the rewind argument is not less than the buffer size (since that doesn't make any sense).
Finally, I discovered that the object is too restrictive in one respect: When the deserialization methods call this object's `read` method, a check ensures that the number of bytes being requested is less than the size of the buffer (adjusting for the rewind size), else it throws an exception. This restriction is unnecessary; the object being deserialized can be larger than the buffer because multiple reads from disk can satisfy the request.
ACKs for top commit:
laanwj:
ACK ~after squash.~ efd2474d17098c754367b844ec646ebececc7c74
mzumsande:
I had intended to follow up earlier on my last comment, ACK efd2474d17098c754367b844ec646ebececc7c74. I reviewed the code, ran tests and did a successful reindex on testnet with this branch.
Tree-SHA512: 695529e0af38bae2af4e0cc2895dda56a71b9059c3de04d32e09c0165a50f6aacee499f2042156ab5eaa6f0349bab6bcca4ef9f6f9ded4e60d4483beab7e4554
2019-09-26 13:37:06 +02:00
|
|
|
size_t bufsize = vchBuf.size();
|
|
|
|
if (nPos + bufsize < nSrcPos) {
|
|
|
|
// rewinding too far, rewind as far as possible
|
|
|
|
nReadPos = nSrcPos - bufsize;
|
2014-10-22 21:08:30 +02:00
|
|
|
return false;
|
Merge #16577: util: CBufferedFile fixes and unit test
efd2474d17098c754367b844ec646ebececc7c74 util: CBufferedFile fixes (Larry Ruane)
Pull request description:
The `CBufferedFile` object guarantees its user is able to "rewind" the data stream (that's being read from a file) up to a certain number of bytes, as specified by the user in the constructor. This guarantee is not honored due to a bug in the `SetPos` method.
Such rewinding is done in `LoadExternalBlockFile()` (currently the only user of this object), which deserializes a series of `CBlock` objects. If that function encounters something unexpected in the data stream, which is coming from a `blocks/blk00???.dat` file, it "rewinds" to an earlier position in the stream to try to get in sync again. The `CBufferedFile` object does not actually rewind its file offset; it simply repositions its internal offset, `nReadPos`, to an earlier position within the object's private buffer; this is why there's a limit to how far the user may rewind.
If `LoadExternalBlockFile()` needs to rewind (call `blkdat.SetPos()`), the stream may not be positioned as it should be, causing errors in deserialization. This need to rewind is probably rare, which is likely why this bug hasn't been noticed already. But if this object is used elsewhere in the future, this could be a serious problem, especially as, due to the nature of the bug, the `SetPos()` _sometimes_ works.
This PR adds a unit test for `CBufferedFile` that fails due to this bug. (Until now it has had no unit tests.) The unit test provides good documentation and examples for developers trying to understand `LoadExternalBlockFile()` and for future users of this object.
This PR also adds code to throw an exception from the constructor if the rewind argument is not less than the buffer size (since that doesn't make any sense).
Finally, I discovered that the object is too restrictive in one respect: When the deserialization methods call this object's `read` method, a check ensures that the number of bytes being requested is less than the size of the buffer (adjusting for the rewind size), else it throws an exception. This restriction is unnecessary; the object being deserialized can be larger than the buffer because multiple reads from disk can satisfy the request.
ACKs for top commit:
laanwj:
ACK ~after squash.~ efd2474d17098c754367b844ec646ebececc7c74
mzumsande:
I had intended to follow up earlier on my last comment, ACK efd2474d17098c754367b844ec646ebececc7c74. I reviewed the code, ran tests and did a successful reindex on testnet with this branch.
Tree-SHA512: 695529e0af38bae2af4e0cc2895dda56a71b9059c3de04d32e09c0165a50f6aacee499f2042156ab5eaa6f0349bab6bcca4ef9f6f9ded4e60d4483beab7e4554
2019-09-26 13:37:06 +02:00
|
|
|
}
|
|
|
|
if (nPos > nSrcPos) {
|
|
|
|
// can't go this far forward, go as far as possible
|
2014-10-22 21:08:30 +02:00
|
|
|
nReadPos = nSrcPos;
|
|
|
|
return false;
|
|
|
|
}
|
Merge #16577: util: CBufferedFile fixes and unit test
efd2474d17098c754367b844ec646ebececc7c74 util: CBufferedFile fixes (Larry Ruane)
Pull request description:
The `CBufferedFile` object guarantees its user is able to "rewind" the data stream (that's being read from a file) up to a certain number of bytes, as specified by the user in the constructor. This guarantee is not honored due to a bug in the `SetPos` method.
Such rewinding is done in `LoadExternalBlockFile()` (currently the only user of this object), which deserializes a series of `CBlock` objects. If that function encounters something unexpected in the data stream, which is coming from a `blocks/blk00???.dat` file, it "rewinds" to an earlier position in the stream to try to get in sync again. The `CBufferedFile` object does not actually rewind its file offset; it simply repositions its internal offset, `nReadPos`, to an earlier position within the object's private buffer; this is why there's a limit to how far the user may rewind.
If `LoadExternalBlockFile()` needs to rewind (call `blkdat.SetPos()`), the stream may not be positioned as it should be, causing errors in deserialization. This need to rewind is probably rare, which is likely why this bug hasn't been noticed already. But if this object is used elsewhere in the future, this could be a serious problem, especially as, due to the nature of the bug, the `SetPos()` _sometimes_ works.
This PR adds a unit test for `CBufferedFile` that fails due to this bug. (Until now it has had no unit tests.) The unit test provides good documentation and examples for developers trying to understand `LoadExternalBlockFile()` and for future users of this object.
This PR also adds code to throw an exception from the constructor if the rewind argument is not less than the buffer size (since that doesn't make any sense).
Finally, I discovered that the object is too restrictive in one respect: When the deserialization methods call this object's `read` method, a check ensures that the number of bytes being requested is less than the size of the buffer (adjusting for the rewind size), else it throws an exception. This restriction is unnecessary; the object being deserialized can be larger than the buffer because multiple reads from disk can satisfy the request.
ACKs for top commit:
laanwj:
ACK ~after squash.~ efd2474d17098c754367b844ec646ebececc7c74
mzumsande:
I had intended to follow up earlier on my last comment, ACK efd2474d17098c754367b844ec646ebececc7c74. I reviewed the code, ran tests and did a successful reindex on testnet with this branch.
Tree-SHA512: 695529e0af38bae2af4e0cc2895dda56a71b9059c3de04d32e09c0165a50f6aacee499f2042156ab5eaa6f0349bab6bcca4ef9f6f9ded4e60d4483beab7e4554
2019-09-26 13:37:06 +02:00
|
|
|
nReadPos = nPos;
|
|
|
|
return true;
|
2014-10-22 21:08:30 +02:00
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! prevent reading beyond a certain position
|
|
|
|
//! no argument removes the limit
|
2018-12-06 15:35:13 +01:00
|
|
|
bool SetLimit(uint64_t nPos = std::numeric_limits<uint64_t>::max()) {
|
2014-10-22 21:08:30 +02:00
|
|
|
if (nPos < nReadPos)
|
|
|
|
return false;
|
|
|
|
nReadLimit = nPos;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2020-12-17 03:07:07 +01:00
|
|
|
CBufferedFile& operator>>(T&& obj) {
|
2014-10-22 21:08:30 +02:00
|
|
|
// Unserialize from this stream
|
2016-11-09 12:32:57 +01:00
|
|
|
::Unserialize(*this, obj);
|
2014-10-22 21:08:30 +02:00
|
|
|
return (*this);
|
|
|
|
}
|
|
|
|
|
2019-01-27 16:11:07 +01:00
|
|
|
//! search for a given byte in the stream, and remain positioned on it
|
2014-10-22 21:08:30 +02:00
|
|
|
void FindByte(char ch) {
|
|
|
|
while (true) {
|
|
|
|
if (nReadPos == nSrcPos)
|
|
|
|
Fill();
|
|
|
|
if (vchBuf[nReadPos % vchBuf.size()] == ch)
|
|
|
|
break;
|
|
|
|
nReadPos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif // BITCOIN_STREAMS_H
|