Implemented tcp sockets (only IPv4) and asynchronous IO (for sockets).

This commit is contained in:
2024-08-16 21:29:33 +02:00
parent da348c1f40
commit f0d0ee17ea
7 changed files with 758 additions and 38 deletions

View File

@@ -11,7 +11,9 @@
#include <span>
#include <stdexcept>
#include <string>
#include "../async/coroutine.hpp"
#include "../container/typeless_buffer.hpp"
#include "../types/result.hpp"
#include "../util/exception.hpp"
namespace mijin
@@ -36,12 +38,21 @@ enum class SeekMode
RELATIVE_TO_END
};
struct ReadOptions
{
bool partial : 1 = false;
bool peek : 1 = false;
};
struct StreamFeatures
{
bool read : 1 = false;
bool write : 1 = false;
bool tell : 1 = false;
bool seek : 1 = false;
bool async : 1 = false;
ReadOptions readOptions = {};
};
enum class FileOpenMode
@@ -54,10 +65,11 @@ enum class FileOpenMode
enum class [[nodiscard]] StreamError
{
SUCCESS,
IO_ERROR,
NOT_SUPPORTED,
UNKNOWN_ERROR
SUCCESS = 0,
IO_ERROR = 1,
NOT_SUPPORTED = 2,
CONNECTION_CLOSED = 3,
UNKNOWN_ERROR = -1
};
class Stream
@@ -66,7 +78,12 @@ public:
virtual ~Stream() = default;
public:
virtual StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0;
virtual StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) = 0;
[[deprecated("Partial parameter has been replaced with options.")]]
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead = nullptr)
{
return readRaw(buffer, {.partial = partial}, outBytesRead);
}
virtual StreamError writeRaw(std::span<const std::uint8_t> buffer) = 0;
virtual std::size_t tell() = 0;
virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0;
@@ -74,74 +91,163 @@ public:
virtual bool isAtEnd() = 0;
virtual StreamFeatures getFeatures() = 0;
inline StreamError readRaw(void* outData, std::size_t bytes, bool partial = false, std::size_t* outBytesRead = nullptr)
// async interface (requires getFeatures().async to be set)
virtual mijin::Task<StreamError> c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr);
virtual mijin::Task<StreamError> c_writeRaw(std::span<const std::uint8_t> buffer);
StreamError readRaw(void* outData, std::size_t bytes, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead);
return readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
}
[[deprecated("Partial parameter has been replaced with options.")]]
StreamError readRaw(void* outData, std::size_t bytes, bool partial, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return readRaw(std::span(ptr, ptr + bytes), {.partial = partial}, outBytesRead);
}
mijin::Task<StreamError> c_readRaw(void* outData, std::size_t bytes, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
std::uint8_t* ptr = static_cast<std::uint8_t*>(outData);
return c_readRaw(std::span(ptr, ptr + bytes), options, outBytesRead);
}
template<std::ranges::contiguous_range TRange>
inline StreamError readRaw(TRange& range, bool partial = false, std::size_t* outBytesRead = nullptr)
StreamError readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return readRaw(&*range.begin(), bytes, partial, outBytesRead);
return readRaw(&*range.begin(), bytes, options, outBytesRead);
}
inline StreamError writeRaw(const void* data, std::size_t bytes)
template<std::ranges::contiguous_range TRange>
[[deprecated("Partial parameter has been replaced with options.")]]
StreamError readRaw(TRange& range, bool partial, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead);
}
template<std::ranges::contiguous_range TRange>
mijin::Task<StreamError> c_readRaw(TRange& range, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return c_readRaw(&*range.begin(), bytes, options, outBytesRead);
}
StreamError writeRaw(const void* data, std::size_t bytes)
{
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
return writeRaw(std::span(ptr, ptr + bytes));
}
mijin::Task<StreamError> c_writeRaw(const void* data, std::size_t bytes)
{
const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
return c_writeRaw(std::span(ptr, ptr + bytes));
}
template<std::ranges::contiguous_range TRange>
inline StreamError writeRaw(const TRange& range)
StreamError writeRaw(const TRange& range)
{
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return writeRaw(&*range.begin(), bytes);
}
template<typename T>
inline StreamError read(T& value)
template<std::ranges::contiguous_range TRange>
mijin::Task<StreamError> c_writeRaw(const TRange& range)
{
return readRaw(&value, sizeof(T));
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return c_writeRaw(&*range.begin(), bytes);
}
template<typename T>
inline StreamError readSpan(T& values)
StreamError read(T& value, const ReadOptions& options = {})
{
MIJIN_ASSERT(!options.partial, "Cannot partially read a value.");
return readRaw(&value, sizeof(T), options);
}
template<typename T>
mijin::Task<StreamError> c_read(T& value, const ReadOptions& options = {})
{
MIJIN_ASSERT(!options.partial, "Cannot partially read a value.");
return c_readRaw(&value, sizeof(T), options);
}
template<typename T>
StreamError readSpan(T& values, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
auto asSpan = std::span(values);
return readRaw(asSpan.data(), asSpan.size_bytes());
return readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
}
template<typename T>
mijin::Task<StreamError> c_readSpan(T& values, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
auto asSpan = std::span(values);
return c_readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
}
template<typename TItBegin, typename TItEnd>
inline StreamError readSpan(TItBegin&& begin, TItEnd&& end)
StreamError readSpan(TItBegin&& begin, TItEnd&& end, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
return readRaw(asSpan.data(), asSpan.size_bytes());
return readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
}
template<typename TItBegin, typename TItEnd>
mijin::Task<StreamError> c_readSpan(TItBegin&& begin, TItEnd&& end, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr)
{
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
return c_readRaw(asSpan.data(), asSpan.size_bytes(), options, outBytesRead);
}
template<typename T>
inline StreamError write(const T& value)
StreamError write(const T& value) requires(std::is_trivial_v<T>)
{
return writeRaw(&value, sizeof(T));
}
template<typename T>
inline StreamError writeSpan(const T& values)
mijin::Task<StreamError> c_write(const T& value) requires(std::is_trivial_v<T>)
{
return c_writeRaw(&value, sizeof(T));
}
template<typename T>
StreamError writeSpan(const T& values)
{
auto asSpan = std::span(values);
return writeRaw(asSpan.data(), asSpan.size_bytes());
}
template<typename T>
mijin::Task<StreamError> c_writeSpan(const T& values)
{
auto asSpan = std::span(values);
return c_writeRaw(asSpan.data(), asSpan.size_bytes());
}
template<typename TItBegin, typename TItEnd>
inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
{
return writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
}
template<typename TItBegin, typename TItEnd>
mijin::Task<StreamError> c_writeSpan(TItBegin&& begin, TItEnd&& end)
{
return c_writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
}
StreamError readBinaryString(std::string& outString);
StreamError writeBinaryString(std::string_view str);
mijin::Task<StreamError> c_readBinaryString(std::string& outString);
mijin::Task<StreamError> c_writeBinaryString(std::string_view str);
[[deprecated("Use readBinaryString() or readAsString() instead.")]]
inline StreamError readString(std::string& outString) { return readBinaryString(outString); }
@@ -150,14 +256,26 @@ public:
StreamError getTotalLength(std::size_t& outLength);
StreamError readRest(TypelessBuffer& outBuffer);
mijin::Task<StreamError> c_readRest(TypelessBuffer& outBuffer);
StreamError readLine(std::string& outString);
mijin::Task<StreamError> c_readLine(std::string& outString);
template<typename TChar = char>
StreamError readAsString(std::basic_string<TChar>& outString);
inline StreamError writeText(std::string_view str)
template<typename TChar = char>
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar>& outString);
StreamError writeText(std::string_view str)
{
return writeSpan(str);
}
mijin::Task<StreamError> c_writeText(std::string_view str)
{
return c_writeSpan(str);
}
};
class FileStream : public Stream
@@ -177,7 +295,7 @@ public:
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
// Stream overrides
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
@@ -210,7 +328,7 @@ public:
}
// Stream overrides
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
StreamError readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
@@ -220,6 +338,9 @@ private:
void openROImpl(const void* data, std::size_t bytes);
};
template<typename TSuccess>
using StreamResult = ResultBase<TSuccess, StreamError>;
//
// public functions
//
@@ -251,7 +372,7 @@ StreamError Stream::readAsString(std::basic_string<TChar>& outString)
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS)
if (StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
@@ -260,6 +381,42 @@ StreamError Stream::readAsString(std::basic_string<TChar>& outString)
return StreamError::SUCCESS;
}
template<typename TChar>
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outString.resize(length);
if (StreamError error = co_await c_readRaw(outString.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<TChar, CHUNK_SIZE> chunk;
outString.clear();
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outString.append(chunk.data(), chunk.data() + bytesRead);
}
co_return StreamError::SUCCESS;
}
inline const char* errorName(StreamError error) noexcept
{
@@ -271,6 +428,8 @@ inline const char* errorName(StreamError error) noexcept
return "IO error";
case StreamError::NOT_SUPPORTED:
return "not supported";
case StreamError::CONNECTION_CLOSED:
return "connection closed";
case StreamError::UNKNOWN_ERROR:
return "unknown error";
}