io: Specialize read_exact for Span/VecStream

These will always enter infinite loops in read_exact under correct use
when there is not enough data in them to read to the buffer, so we can
throw out early for EOF in those scenarios.
This commit is contained in:
Eidolon 2023-05-20 11:19:40 -05:00 committed by James R
parent a8e122c36b
commit a2ea3bfd6c

View file

@ -12,6 +12,7 @@
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
@ -20,7 +21,8 @@
#include <tcb/span.hpp>
#include <zlib.h>
namespace srb2::io {
namespace srb2::io
{
using StreamSize = uint64_t;
using StreamOffset = int64_t;
@ -62,6 +64,11 @@ inline constexpr const bool IsStreamV = IsStream<T>::value;
template <typename T>
inline constexpr const bool IsInputOutputStreamV = IsInputOutputStream<T>::value;
class UnexpectedEof : public std::logic_error
{
using std::logic_error::logic_error;
};
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
void read_exact(I& stream, tcb::span<std::byte> buffer) {
std::size_t total = 0;
@ -441,6 +448,28 @@ public:
return head_;
}
friend void read_exact(SpanStream& stream, tcb::span<std::byte> buffer)
{
const std::size_t remaining = stream.span_.size() - stream.head_;
const std::size_t buffer_size = buffer.size();
if (buffer_size > remaining)
{
// The span's size will never change, so the generic impl of read_exact will enter an inifinite loop. We can
// throw out early.
throw UnexpectedEof("read buffer size > remaining bytes in span");
}
if (buffer_size == 0)
{
return;
}
auto copy_begin = std::next(stream.span_.begin(), stream.head_);
auto copy_end = std::next(stream.span_.begin(), stream.head_ + buffer_size);
stream.head_ += buffer_size;
std::copy(copy_begin, copy_end, buffer.begin());
}
private:
tcb::span<std::byte> span_;
std::size_t head_ {0};
@ -513,6 +542,28 @@ public:
}
std::vector<std::byte>& vector() { return vec_; }
friend void read_exact(VecStream& stream, tcb::span<std::byte> buffer)
{
const std::size_t remaining = stream.vec_.size() - stream.head_;
const std::size_t buffer_size = buffer.size();
if (buffer_size > remaining)
{
// VecStream is not thread safe, so the generic impl of read_exact would enter an infinite loop under
// correct usage. We know when we've reached the end and can throw out early.
throw UnexpectedEof("read buffer size > remaining bytes in vector");
}
if (buffer_size == 0)
{
return;
}
auto copy_begin = std::next(stream.vec_.begin(), stream.head_);
auto copy_end = std::next(stream.vec_.begin(), stream.head_ + buffer_size);
stream.head_ += buffer_size;
std::copy(copy_begin, copy_end, buffer.begin());
}
};
class ZlibException : public std::exception {