Implemented tcp sockets (only IPv4) and asynchronous IO (for sockets).
This commit is contained in:
@@ -35,6 +35,24 @@ namespace mijin
|
||||
|
||||
void Stream::flush() {}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
(void) buffer;
|
||||
(void) options;
|
||||
(void) outBytesRead;
|
||||
|
||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().read, "Stream advertises async read, but doesn't implement it.");
|
||||
co_return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
(void) buffer;
|
||||
|
||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().write, "Stream advertises async write, but doesn't implement it.");
|
||||
co_return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
StreamError Stream::readBinaryString(std::string& outString)
|
||||
{
|
||||
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
|
||||
@@ -55,7 +73,7 @@ StreamError Stream::readBinaryString(std::string& outString)
|
||||
|
||||
StreamError Stream::writeBinaryString(std::string_view str)
|
||||
{
|
||||
assert(str.length() <= std::numeric_limits<std::uint32_t>::max());
|
||||
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
|
||||
StreamError error = write(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
@@ -64,6 +82,35 @@ StreamError Stream::writeBinaryString(std::string_view str)
|
||||
return writeSpan(str.begin(), str.end());
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
|
||||
{
|
||||
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
|
||||
StreamError error = co_await c_read(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.resize(length);
|
||||
error = co_await c_readSpan(result.begin(), result.end());
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
outString = std::move(result);
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_writeBinaryString(std::string_view str)
|
||||
{
|
||||
MIJIN_ASSERT(str.length() <= std::numeric_limits<std::uint32_t>::max(), "Binary string is too long.");
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
|
||||
StreamError error = co_await c_write(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
co_return error;
|
||||
}
|
||||
co_return co_await c_writeSpan(str.begin(), str.end());
|
||||
}
|
||||
|
||||
StreamError Stream::getTotalLength(std::size_t& outLength)
|
||||
{
|
||||
const StreamFeatures features = getFeatures();
|
||||
@@ -106,7 +153,7 @@ StreamError Stream::readRest(TypelessBuffer& outBuffer)
|
||||
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;
|
||||
}
|
||||
@@ -118,6 +165,129 @@ StreamError Stream::readRest(TypelessBuffer& outBuffer)
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readRest(TypelessBuffer& outBuffer)
|
||||
{
|
||||
// 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();
|
||||
outBuffer.resize(length);
|
||||
if (StreamError error = co_await c_readRaw(outBuffer.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<std::byte, CHUNK_SIZE> chunk = {};
|
||||
|
||||
while (!isAtEnd())
|
||||
{
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
|
||||
outBuffer.resize(outBuffer.byteSize() + bytesRead);
|
||||
std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
|
||||
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
|
||||
}
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError Stream::readLine(std::string& outString)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
|
||||
|
||||
static constexpr std::size_t BUFFER_SIZE = 4096;
|
||||
std::array<char, BUFFER_SIZE> buffer;
|
||||
|
||||
outString.clear();
|
||||
bool done = false;
|
||||
while(!done)
|
||||
{
|
||||
// read into the buffer
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
// try to find a \n
|
||||
auto begin = buffer.begin();
|
||||
auto end = buffer.begin() + bytesRead;
|
||||
auto newline = std::find(begin, end, '\n');
|
||||
|
||||
if (newline != end)
|
||||
{
|
||||
// found the end
|
||||
outString.append(begin, newline);
|
||||
end = newline + 1;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outString.append(begin, end);
|
||||
}
|
||||
|
||||
// read again, this time to skip
|
||||
if (StreamError error = readSpan(begin, end); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readLine(std::string& outString)
|
||||
{
|
||||
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
|
||||
|
||||
static constexpr std::size_t BUFFER_SIZE = 4096;
|
||||
std::array<char, BUFFER_SIZE> buffer;
|
||||
|
||||
outString.clear();
|
||||
bool done = false;
|
||||
while(!done)
|
||||
{
|
||||
// read into the buffer
|
||||
std::size_t bytesRead = 0;
|
||||
if (StreamError error = co_await c_readRaw(buffer, {.partial = true, .peek = true}, &bytesRead); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
// try to find a \n
|
||||
auto begin = buffer.begin();
|
||||
auto end = buffer.begin() + bytesRead;
|
||||
auto newline = std::find(begin, end, '\n');
|
||||
|
||||
if (newline != end)
|
||||
{
|
||||
// found the end
|
||||
outString.append(begin, newline);
|
||||
end = newline + 1;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outString.append(begin, end);
|
||||
}
|
||||
|
||||
// read again, this time to skip
|
||||
if (StreamError error = co_await c_readSpan(begin, end); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
}
|
||||
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
if (handle) {
|
||||
@@ -172,7 +342,7 @@ void FileStream::close()
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
|
||||
@@ -181,12 +351,19 @@ StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, bool partial, st
|
||||
if (std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (!partial && readBytes < buffer.size()) {
|
||||
if (!options.partial && readBytes < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr) {
|
||||
*outBytesRead = readBytes;
|
||||
}
|
||||
if (options.peek)
|
||||
{
|
||||
if (StreamError error = seek(-static_cast<std::intptr_t>(readBytes), SeekMode::RELATIVE); error != StreamError::SUCCESS)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -265,7 +442,11 @@ StreamFeatures FileStream::getFeatures()
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
|
||||
.tell = true,
|
||||
.seek = true
|
||||
.seek = true,
|
||||
.readOptions = {
|
||||
.partial = true,
|
||||
.peek = true
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
@@ -293,10 +474,10 @@ void MemoryStream::close()
|
||||
data_ = {};
|
||||
}
|
||||
|
||||
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(isOpen());
|
||||
if (!partial && availableBytes() < buffer.size()) {
|
||||
if (!options.partial && availableBytes() < buffer.size()) {
|
||||
return StreamError::IO_ERROR; // TODO: need more errors?
|
||||
}
|
||||
const std::size_t numBytes = std::min(buffer.size(), availableBytes());
|
||||
@@ -304,7 +485,9 @@ StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial,
|
||||
if (outBytesRead) {
|
||||
*outBytesRead = numBytes;
|
||||
}
|
||||
pos_ += numBytes;
|
||||
if (!options.peek) {
|
||||
pos_ += numBytes;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -362,7 +545,10 @@ StreamFeatures MemoryStream::getFeatures()
|
||||
.read = true,
|
||||
.write = canWrite_,
|
||||
.tell = true,
|
||||
.seek = true
|
||||
.seek = true,
|
||||
.readOptions = {
|
||||
.peek = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user