diff --git a/README.md b/README.md index 248cf79..f2812ba 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,4 @@ Currently no examples except in test. * Windows/Mac Support * Buffer flexibility * Multithreaded conveyor communication +* Logger implementation diff --git a/SConstruct b/SConstruct index 6f01c49..917c22f 100644 --- a/SConstruct +++ b/SConstruct @@ -29,7 +29,7 @@ def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, ta sources.append( self.StaticObject( target=target_name, source=path ) ) pass -env=Environment(CPPPATH=['#source','#','#driver'], +env=Environment(CPPPATH=['#source/kelgin','#source','#','#driver'], CXX='c++', CPPDEFINES=['GIN_UNIX'], CXXFLAGS=['-std=c++17','-g','-Wall','-Wextra'], @@ -41,7 +41,7 @@ env.headers = [] env.objects = [] Export('env') -SConscript('source/SConscript') +SConscript('source/kelgin/SConscript') SConscript('driver/SConscript') # Library build diff --git a/driver/io-unix.cpp b/driver/io-unix.cpp index 02e858f..dce0dad 100644 --- a/driver/io-unix.cpp +++ b/driver/io-unix.cpp @@ -1,4 +1,3 @@ -#ifdef GIN_UNIX #include "driver/io-unix.h" #include @@ -18,11 +17,6 @@ IFdOwner::~IFdOwner() { } } -UnixIoStream::UnixIoStream(UnixEventPort &event_port, int file_descriptor, - int fd_flags, uint32_t event_mask) - : IFdOwner{event_port, file_descriptor, fd_flags, event_mask | EPOLLRDHUP} { -} - ssize_t UnixIoStream::dataRead(void *buffer, size_t length) { return ::read(fd(), buffer, length); } @@ -30,6 +24,84 @@ ssize_t UnixIoStream::dataRead(void *buffer, size_t length) { ssize_t UnixIoStream::dataWrite(const void *buffer, size_t length) { return ::write(fd(), buffer, length); } +/* +void UnixIoStream::readStep() { + if (read_ready) { + read_ready->feed(); + } + while (!read_tasks.empty()) { + ReadIoTask &task = read_tasks.front(); + + ssize_t n = ::read(fd(), task.buffer, task.max_length); + + if (n <= 0) { + if (n == 0) { + if (on_read_disconnect) { + on_read_disconnect->feed(); + } + break; + } + int error = errno; + if (error == EAGAIN || error == EWOULDBLOCK) { + break; + } else { + if (read_done) { + read_done->fail(criticalError("Read failed")); + } + read_tasks.pop(); + } + } else if (static_cast(n) >= task.min_length && + static_cast(n) <= task.max_length) { + if (read_done) { + read_done->feed(static_cast(n)); + } + size_t max_len = task.max_length; + read_tasks.pop(); + } else { + task.buffer = reinterpret_cast(task.buffer) + n; + task.min_length -= static_cast(n); + task.max_length -= static_cast(n); + } + } +} + +void UnixIoStream::writeStep() { + if (write_ready) { + write_ready->feed(); + } + while (!write_tasks.empty()) { + WriteIoTask &task = write_tasks.front(); + + ssize_t n = ::write(fd(), task.buffer, task.length); + + if (n < 0) { + int error = errno; + if (error == EAGAIN || error == EWOULDBLOCK) { + break; + } else { + if (write_done) { + write_done->fail(criticalError("Write failed")); + } + write_tasks.pop(); + } + } else if (static_cast(n) == task.length) { + if (write_done) { + write_done->feed(static_cast(task.length)); + } + write_tasks.pop(); + } else { + task.buffer = reinterpret_cast(task.buffer) + + static_cast(n); + task.length -= static_cast(n); + } + } +} +*/ + +UnixIoStream::UnixIoStream(UnixEventPort &event_port, int file_descriptor, + int fd_flags, uint32_t event_mask) + : IFdOwner{event_port, file_descriptor, fd_flags, event_mask | EPOLLRDHUP} { +} void UnixIoStream::read(void *buffer, size_t min_length, size_t max_length) { bool is_ready = read_helper.read_tasks.empty(); @@ -81,11 +153,11 @@ Conveyor UnixIoStream::writeReady() { void UnixIoStream::notify(uint32_t mask) { if (mask & EPOLLOUT) { - write_helper.writeStep(*this); + writeStep(); } if (mask & EPOLLIN) { - read_helper.readStep(*this); + readStep(); } if (mask & EPOLLRDHUP) { @@ -153,15 +225,15 @@ Own UnixNetworkAddress::listen() { return heap(event_port, fd, 0); } -Own UnixNetworkAddress::connect() { +Conveyor> UnixNetworkAddress::connect() { assert(addresses.size() > 0); if (addresses.size() == 0) { - return nullptr; + return Conveyor>{criticalError("No address found")}; } int fd = addresses.front().socket(SOCK_STREAM); if (fd < 0) { - return nullptr; + return Conveyor>{criticalError("Couldn't open socket")}; } Own io_stream = @@ -176,43 +248,46 @@ Own UnixNetworkAddress::connect() { * But edge triggered epolling means that it'll * be ready when the signal is triggered */ + + /// @todo Add limit node when implemented if (error == EINPROGRESS) { - Conveyor write_ready = io_stream->writeReady(); - break; /* - * Future function return + Conveyor write_ready = io_stream->writeReady(); return write_ready.then( - [io_stream{std::move(io_stream)}]() mutable { - io_stream->write_ready = nullptr; - return std::move(io_stream); + [ios{std::move(io_stream)}]() mutable { + ios->write_ready = nullptr; + return std::move(ios); }); */ + break; } else if (error != EINTR) { - return nullptr; + /// @todo Push error message from + return Conveyor>{ + criticalError("Some error happened.")}; } } else { break; } } - return io_stream; - // @todo change function into a safe return type. - // return Conveyor>{std::move(io_stream)}; + return Conveyor>{std::move(io_stream)}; } std::string UnixNetworkAddress::toString() const { - std::ostringstream oss; - oss << "Address: " << path; - if (port_hint > 0) { - oss << "\nPort: " << port_hint; + try { + std::ostringstream oss; + oss << "Address: " << path; + if (port_hint > 0) { + oss << "\nPort: " << port_hint; + } + return oss.str(); + } catch (std::bad_alloc &) { + return {}; } - return oss.str(); } -UnixNetwork::UnixNetwork(UnixEventPort &event_port) : event_port{event_port} {} - -Own UnixNetwork::parseAddress(const std::string &path, - uint16_t port_hint) { +Conveyor> UnixNetwork::parseAddress(const std::string &path, + uint16_t port_hint) { std::string_view addr_view{path}; { std::string_view begins_with = "unix:"; @@ -224,34 +299,38 @@ Own UnixNetwork::parseAddress(const std::string &path, std::list addresses = SocketAddress::parse(addr_view, port_hint); - return heap(event_port, path, port_hint, - std::move(addresses)); + return Conveyor>{heap( + event_port, path, port_hint, std::move(addresses))}; } UnixAsyncIoProvider::UnixAsyncIoProvider(UnixEventPort &port_ref, - Own &&port) - : event_port{port_ref}, event_loop{std::move(port)}, wait_scope{event_loop}, - unix_network{port_ref} {} + Own port) + : event_port{port_ref}, event_loop{std::move(port)}, unix_network{ + port_ref} {} Own UnixAsyncIoProvider::wrapInputFd(int fd) { return heap(event_port, fd, 0, EPOLLIN); } +Network &UnixAsyncIoProvider::network() { + return static_cast(unix_network); +} + EventLoop &UnixAsyncIoProvider::eventLoop() { return event_loop; } -WaitScope &UnixAsyncIoProvider::waitScope() { return wait_scope; } +ErrorOr setupAsyncIo() { + try { + Own prt = heap(); + UnixEventPort &prt_ref = *prt; -Network &UnixAsyncIoProvider::network() { return unix_network; } + Own io_provider = + heap(prt_ref, std::move(prt)); -AsyncIoContext setupAsyncIo() { - Own prt = heap(); - UnixEventPort &prt_ref = *prt; - Own io_provider = - heap(prt_ref, std::move(prt)); + EventLoop &loop_ref = io_provider->eventLoop(); - EventLoop &event_loop = io_provider->eventLoop(); - WaitScope &wait_scope = io_provider->waitScope(); - return {std::move(io_provider), prt_ref, wait_scope}; + return {{std::move(io_provider), loop_ref, prt_ref}}; + } catch (std::bad_alloc &) { + return criticalError("Out of memory"); + } } } // namespace gin -#endif // GIN_UNIX diff --git a/driver/io-unix.h b/driver/io-unix.h index d8c9de8..c32c660 100644 --- a/driver/io-unix.h +++ b/driver/io-unix.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,8 @@ private: std::unordered_multimap>> signal_conveyors; + int pipefds[2]; + std::vector toUnixSignal(Signal signal) const { switch (signal) { case Signal::User1: @@ -106,13 +109,12 @@ private: if (nfds < 0) { /// @todo error_handling - assert(false); return false; } for (int i = 0; i < nfds; ++i) { if (events[i].data.u64 == 0) { - for (;;) { + while (1) { struct ::signalfd_siginfo siginfo; ssize_t n = ::read(signal_fd, &siginfo, sizeof(siginfo)); @@ -123,6 +125,17 @@ private: notifySignalListener(siginfo.ssi_signo); } + } else if (events[i].data.u64 == 1) { + uint8_t i; + if (pipefds[0] < 0) { + continue; + } + while (1) { + ssize_t n = ::recv(pipefds[0], &i, sizeof(i), 0); + if (n < 0) { + break; + } + } } else { IFdOwner *owner = reinterpret_cast(events[i].data.ptr); @@ -156,11 +169,22 @@ public: event.events = EPOLLIN; event.data.u64 = 0; ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, signal_fd, &event); + + int rc = ::pipe2(pipefds, O_NONBLOCK | O_CLOEXEC); + if (rc < 0) { + return; + } + memset(&event, 0, sizeof(event)); + event.events = EPOLLIN; + event.data.u64 = 1; + ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipefds[0], &event); } ~UnixEventPort() { ::close(epoll_fd); ::close(signal_fd); + ::close(pipefds[0]); + ::close(pipefds[1]); } Conveyor onSignal(Signal signal) override { @@ -202,6 +226,16 @@ public: } } + void wake() override { + /// @todo pipe() in the beginning and write something minor into it like + /// uint8_t or sth the value itself doesn't matter + if (pipefds[1] < 0) { + return; + } + uint8_t i = 0; + ::send(pipefds[1], &i, sizeof(i), MSG_DONTWAIT); + } + void subscribe(IFdOwner &owner, int fd, uint32_t event_mask) { if (epoll_fd < 0 || fd < 0) { return; @@ -240,6 +274,9 @@ private: ssize_t dataRead(void *buffer, size_t length) override; ssize_t dataWrite(const void *buffer, size_t length) override; + void readStep(); + void writeStep(); + public: UnixIoStream(UnixEventPort &event_port, int file_descriptor, int fd_flags, uint32_t event_mask); @@ -357,7 +394,7 @@ public: addresses{std::move(addr)} {} Own listen() override; - Own connect() override; + Conveyor> connect() override; std::string toString() const override; }; @@ -369,26 +406,24 @@ private: public: UnixNetwork(UnixEventPort &event_port); - Own parseAddress(const std::string &, - uint16_t port_hint = 0) override; + Conveyor> parseAddress(const std::string &, + uint16_t port_hint = 0) override; }; class UnixAsyncIoProvider final : public AsyncIoProvider { private: UnixEventPort &event_port; EventLoop event_loop; - WaitScope wait_scope; UnixNetwork unix_network; public: - UnixAsyncIoProvider(UnixEventPort &port_ref, Own &&port); + UnixAsyncIoProvider(UnixEventPort &port_ref, Own port); + + Network &network() override; Own wrapInputFd(int fd) override; EventLoop &eventLoop(); - WaitScope &waitScope(); - - Network &network() override; }; } // namespace gin diff --git a/source/buffer.cpp b/source/buffer.cpp deleted file mode 100644 index 509c2fa..0000000 --- a/source/buffer.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "buffer.h" - -#include -#include -#include -#include -#include - -namespace gin { -RingBuffer::RingBuffer() : read_position{0}, write_position{0} { - buffer.resize(RING_BUFFER_MAX_SIZE); -} - -RingBuffer::RingBuffer(size_t size) : read_position{0}, write_position{0} { - buffer.resize(size); -} - -size_t RingBuffer::readPosition() const { return read_position; } - -/* - * If write is ahead of read it is a simple distance, but if read ist ahead of - * write then there are two segments - * - */ -size_t RingBuffer::readCompositeLength() const { - return writePosition() < readPosition() - ? buffer.size() - (readPosition() - writePosition()) - : (write_reached_read ? buffer.size() - : writePosition() - readPosition()); -} - -/* - * If write is ahead then it's the simple distance again. If read is ahead it's - * until the end of the buffer/segment - */ -size_t RingBuffer::readSegmentLength() const { - return writePosition() < readPosition() - ? (buffer.size() - readPosition()) - : (write_reached_read - ? (buffer.size() - readPosition()) - : writePosition() - - readPosition()); //(writePosition() - - // readPosition()) : - //(write_reached_read ? () : 0 ); -} - -void RingBuffer::readAdvance(size_t bytes) { - assert(bytes <= readCompositeLength()); - size_t advanced = read_position + bytes; - read_position = advanced >= buffer.size() ? advanced - buffer.size() - : advanced; - write_reached_read = bytes > 0 ? false : write_reached_read; -} - -uint8_t &RingBuffer::read(size_t i) { - assert(i < readCompositeLength()); - size_t pos = read_position + i; - pos = pos >= buffer.size() ? pos - buffer.size() : pos; - return buffer[pos]; -} - -const uint8_t &RingBuffer::read(size_t i) const { - assert(i < readCompositeLength()); - size_t pos = read_position + i; - pos = pos >= buffer.size() ? pos - buffer.size() : pos; - return buffer[pos]; -} - -size_t RingBuffer::writePosition() const { return write_position; } - -size_t RingBuffer::writeCompositeLength() const { - return readPosition() > writePosition() - ? (readPosition() - writePosition()) - : (write_reached_read - ? 0 - : buffer.size() - (writePosition() - readPosition())); -} - -size_t RingBuffer::writeSegmentLength() const { - return readPosition() > writePosition() - ? (readPosition() - writePosition()) - : (write_reached_read ? 0 : (buffer.size() - writePosition())); -} - -void RingBuffer::writeAdvance(size_t bytes) { - assert(bytes <= writeCompositeLength()); - size_t advanced = write_position + bytes; - write_position = advanced >= buffer.size() ? advanced - buffer.size() - : advanced; - - write_reached_read = - (write_position == read_position && bytes > 0 ? true : false); -} - -uint8_t &RingBuffer::write(size_t i) { - assert(i < writeCompositeLength()); - size_t pos = write_position + i; - pos = pos >= buffer.size() ? pos - buffer.size() : pos; - return buffer[pos]; -} - -const uint8_t &RingBuffer::write(size_t i) const { - assert(i < writeCompositeLength()); - size_t pos = write_position + i; - pos = pos >= buffer.size() ? pos - buffer.size() : pos; - return buffer[pos]; -} -/* - Error RingBuffer::increaseSize(size_t size){ - size_t old_size = buffer.size(); - size_t new_size = old_size + size; - buffer.resize(new_size); - if(readPosition() > writePosition() || (readPosition() == - writePosition() && write_reached_read)){ size_t remaining = old_size - - writePosition(); size_t real_remaining = 0; while(remaining > 0){ size_t - segment = std::min(remaining, size); memcpy(&buffer[new_size-segment], - &buffer[old_size-segment], segment); remaining -= segment; size -= segment; - old_size -= segment; - new_size -= segment; - } - } - - return noError(); - } -*/ -Error RingBuffer::push(const uint8_t &value) { - size_t write_remain = writeCompositeLength(); - if (write_remain > 0) { - write() = value; - writeAdvance(1); - } else { - return recoverableError("Buffer too small"); - } - return noError(); -} - -Error RingBuffer::push(const uint8_t &buffer, size_t size) { - if (writeCompositeLength() >= size) { - const uint8_t *buffer_ptr = &buffer; - while (size > 0) { - size_t segment = std::min(writeSegmentLength(), size); - memcpy(&write(), buffer_ptr, segment); - writeAdvance(segment); - size -= segment; - buffer_ptr += segment; - } - } else { - return recoverableError("Buffer too small"); - } - return noError(); -} - -Error RingBuffer::pop(uint8_t &value) { - if (readCompositeLength() > 0) { - value = read(); - readAdvance(1); - } else { - return recoverableError("Buffer too small"); - } - return noError(); -} - -Error RingBuffer::pop(uint8_t &buffer, size_t size) { - if (readCompositeLength() >= size) { - uint8_t *buffer_ptr = &buffer; - while (size > 0) { - size_t segment = std::min(readSegmentLength(), size); - memcpy(buffer_ptr, &read(), segment); - readAdvance(segment); - size -= segment; - buffer_ptr += segment; - } - } else { - return recoverableError("Buffer too small"); - } - return noError(); -} - -std::string RingBuffer::toString() const { - std::ostringstream oss; - for (size_t i = 0; i < readCompositeLength(); ++i) { - oss << read(i); - } - return oss.str(); -} - -std::string RingBuffer::toHex() const { - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for (size_t i = 0; i < readCompositeLength(); ++i) { - oss << std::setw(2) << (uint16_t)read(i); - if ((i + 1) < readCompositeLength()) { - oss << ((i % 4 == 3) ? '\n' : ' '); - } - } - return oss.str(); -} -} // namespace gin diff --git a/source/error.cpp b/source/error.cpp deleted file mode 100644 index fefee2b..0000000 --- a/source/error.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "error.h" - -namespace gin { -Error::Error() : error_{0} {} - -Error::Error(const std::string &msg) : error_message{msg}, error_{1} {} - -Error::Error(const std::string &msg, int8_t code) - : error_message{msg}, error_{code} {} - -Error::Error(const Error &error) - : error_message{error.error_message}, error_{error.error_} {} - -Error::Error(Error &&error) - : error_message{std::move(error.error_message)}, error_{std::move( - error.error_)} {} - -const std::string &Error::message() const { return error_message; } - -bool Error::failed() const { return error_ != 0; } - -bool Error::isCritical() const { return error_ < 0; } - -bool Error::isRecoverable() const { return error_ > 0; } - -Error criticalError(const std::string &msg) { return Error{msg, -1}; } - -Error recoverableError(const std::string &msg) { return Error{msg, 1}; } - -Error noError() { return Error{}; } -} // namespace gin diff --git a/source/error.h b/source/error.h deleted file mode 100644 index ee4b715..0000000 --- a/source/error.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include - -#include "common.h" - -namespace gin { -class Error { -private: - std::string error_message; - int8_t error_; - -public: - Error(); - Error(const std::string &msg); - Error(const std::string &msg, int8_t code); - Error(const Error &error); - Error(Error &&error); - - Error &operator=(const Error &) = default; - Error &operator=(Error &&) = default; - - const std::string &message() const; - bool failed() const; - - bool isCritical() const; - bool isRecoverable() const; -}; - -Error criticalError(const std::string &msg); -Error recoverableError(const std::string &msg); -Error noError(); - -template class ErrorOr; - -class ErrorOrValue { -public: - virtual ~ErrorOrValue() = default; - - template ErrorOr &as() { - return reinterpret_cast &>(*this); - } - - template const ErrorOr &as() const { - return reinterpret_cast &>(*this); - } -}; - -template class ErrorOr : public ErrorOrValue { -private: - std::variant value_or_error; - -public: - ErrorOr() = default; - ErrorOr(const T &value) : value_or_error{value} {} - - ErrorOr(T &&value) : value_or_error{std::move(value)} {} - - ErrorOr(const Error &error) : value_or_error{error} {} - ErrorOr(Error &&error) : value_or_error{std::move(error)} {} - - bool isValue() const { return std::holds_alternative(value_or_error); } - - bool isError() const { - return std::holds_alternative(value_or_error); - } - - Error &error() { return std::get(value_or_error); } - - const Error &error() const { return std::get(value_or_error); } - - T &value() { return std::get(value_or_error); } - - const T &value() const { return std::get(value_or_error); } -}; - -} // namespace gin \ No newline at end of file diff --git a/source/json.h b/source/json.h deleted file mode 100644 index 5df597e..0000000 --- a/source/json.h +++ /dev/null @@ -1,840 +0,0 @@ -#pragma once - -#include "buffer.h" -#include "message.h" -#include "message_dynamic.h" - -#include "error.h" - -#include -#include -#include -#include -#include - -#include - -namespace gin { -template struct JsonEncodeImpl; - -template struct JsonEncodeImpl> { - static Error encode(typename MessagePrimitive::Reader data, - Buffer &buffer) { - std::string stringified = std::to_string(data.get()); - Error error = - buffer.push(*reinterpret_cast(stringified.data()), - stringified.size()); - if (error.failed()) { - return error; - } - return Error{}; - } -}; - -template <> struct JsonEncodeImpl> { - static Error encode(typename MessagePrimitive::Reader data, - Buffer &buffer) { - std::string str = - std::string{"\""} + std::string{data.get()} + std::string{"\""}; - Error error = buffer.push( - *reinterpret_cast(str.data()), str.size()); - if (error.failed()) { - return error; - } - return Error{}; - } -}; - -template struct JsonEncodeImpl> { - template - static typename std::enable_if::type - encodeMembers(typename MessageList::Reader data, Buffer &buffer) { - (void)data; - (void)buffer; - return Error{}; - } - template - static typename std::enable_if < - i::type - encodeMembers(typename MessageList::Reader data, Buffer &buffer) { - { - Error error = - JsonEncodeImpl::type>:: - encode(data.template get(), buffer); - if (error.failed()) { - return error; - } - } - if constexpr ((i + 1u) < sizeof...(T)) { - if (buffer.push(',').failed()) { - return recoverableError("Failed buffer push"); - } - } - { - Error error = - JsonEncodeImpl>::encodeMembers(data, - buffer); - if (error.failed()) { - return error; - } - } - return noError(); - } - - static Error encode(typename MessageList::Reader data, - Buffer &buffer) { - if (buffer.push('[').failed()) { - return recoverableError("Failed buffer push"); - } - Error error = - JsonEncodeImpl>::encodeMembers<0>(data, buffer); - if (error.failed()) { - return error; - } - if (buffer.push(']').failed()) { - return recoverableError("Failed buffer push"); - } - return noError(); - } -}; - -template -struct JsonEncodeImpl...>> { - template - static typename std::enable_if::type - encodeMembers( - typename MessageStruct...>::Reader data, - Buffer &buffer) { - (void)data; - (void)buffer; - return Error{}; - } - template - static typename std::enable_if < - i::type encodeMembers( - typename MessageStruct...>::Reader data, - Buffer &buffer) { - { - Error error = buffer.push('\"'); - if (error.failed()) { - return error; - } - std::string_view view = ParameterPackType::type::view(); - error = buffer.push(*reinterpret_cast(view.data()), - view.size()); - if (error.failed()) { - return error; - } - error = buffer.push('\"'); - if (error.failed()) { - return error; - } - error = buffer.push(':'); - if (error.failed()) { - return error; - } - } - { - Error error = - JsonEncodeImpl::type>:: - encode(data.template get(), buffer); - if (error.failed()) { - return error; - } - } - if constexpr ((i + 1u) < sizeof...(V)) { - if (buffer.push(',').failed()) { - return recoverableError("Failed buffer push"); - } - } - { - Error error = - JsonEncodeImpl...>>:: - encodeMembers(data, buffer); - if (error.failed()) { - return error; - } - } - return noError(); - } - - static Error - encode(typename MessageStruct...>::Reader data, - Buffer &buffer) { - if (buffer.push('{').failed()) { - return recoverableError("Failed buffer push"); - } - Error error = - JsonEncodeImpl...>>:: - encodeMembers<0>(data, buffer); - if (error.failed()) { - return error; - } - if (buffer.push('}').failed()) { - return recoverableError("Failed buffer push"); - } - return noError(); - } -}; - -template -struct JsonEncodeImpl...>> { - template - static typename std::enable_if::type encodeMember( - typename MessageUnion...>::Reader data, - Buffer &buffer) { - (void)data; - (void)buffer; - return noError(); - } - template - static typename std::enable_if < - i::type encodeMember( - typename MessageUnion...>::Reader reader, - Buffer &buffer) { - /// @todo only encode if alternative is set, skip in other cases - /// use holds_alternative - - if (reader.template holdsAlternative< - typename ParameterPackType::type>()) { - { - Error error = buffer.push('{'); - if (error.failed()) { - return error; - } - } - { - Error error = buffer.push('\"'); - if (error.failed()) { - return error; - } - std::string_view view = - ParameterPackType::type::view(); - error = - buffer.push(*reinterpret_cast(view.data()), - view.size()); - if (error.failed()) { - return error; - } - error = buffer.push('\"'); - if (error.failed()) { - return error; - } - error = buffer.push(':'); - if (error.failed()) { - return error; - } - } - - Error error = - JsonEncodeImpl::type>:: - encode(reader.template get(), buffer); - if (error.failed()) { - return error; - } - { - Error error = buffer.push('}'); - if (error.failed()) { - return error; - } - } - return noError(); - } - - Error error = - JsonEncodeImpl...>>:: - encodeMember(reader, buffer); - if (error.failed()) { - return error; - } - - return noError(); - } - - static Error - encode(typename MessageUnion...>::Reader reader, - Buffer &buffer) { - return encodeMember<0>(reader, buffer); - } -}; - -/* - * For JSON decoding we need a dynamic where we can query information from - */ -template struct JsonDecodeImpl; - -template struct JsonDecodeImpl> { - // static void decode(BufferView view, typename - // MessagePrimitive::Builder){} - static Error decode(typename MessagePrimitive::Builder, - DynamicMessage::DynamicReader) { - return noError(); - } -}; -template <> struct JsonDecodeImpl> { - // static void decode(BufferView view, typename - // MessagePrimitive::Builder){} - static Error decode(typename MessagePrimitive::Builder data, - DynamicMessage::DynamicReader reader) { - if (reader.type() != DynamicMessage::Type::Signed) { - return criticalError("Not an integer"); - } - DynamicMessageSigned::Reader s_reader = - reader.as(); - data.set(s_reader.get()); - return noError(); - } -}; -template <> struct JsonDecodeImpl> { - // static void decode(BufferView view, typename - // MessagePrimitive::Builder){} - static Error decode(typename MessagePrimitive::Builder builder, - DynamicMessage::DynamicReader reader) { - if (reader.type() != DynamicMessage::Type::Signed) { - return criticalError("Not an integer"); - } - DynamicMessageSigned::Reader s_reader = - reader.as(); - int64_t val = s_reader.get(); - if (val < 0) { - return criticalError("Not an unsigned integer"); - } - builder.set(static_cast(val)); - return noError(); - } -}; -template <> struct JsonDecodeImpl> { - // static void decode(BufferView view, typename - // MessagePrimitive::Builder){} - static Error decode(typename MessagePrimitive::Builder data, - DynamicMessage::DynamicReader reader) { - if (reader.type() != DynamicMessage::Type::Signed) { - return criticalError("Not an integer"); - } - DynamicMessageSigned::Reader s_reader = - reader.as(); - int64_t val = s_reader.get(); - data.set(static_cast(val)); - return noError(); - } -}; - -template <> struct JsonDecodeImpl> { - static Error decode(typename MessagePrimitive::Builder builder, - DynamicMessage::DynamicReader reader) { - if (reader.type() != DynamicMessage::Type::String) { - return criticalError("Not a string"); - } - DynamicMessageString::Reader s_reader = - reader.as(); - builder.set(s_reader.get()); - - return noError(); - } -}; - -template -struct JsonDecodeImpl...>> { - template - static typename std::enable_if::type - decodeMembers(typename MessageStruct...>::Builder, - DynamicMessageStruct::Reader reader) { - return noError(); - } - template - static typename std::enable_if < - i::type decodeMembers( - typename MessageStruct...>::Builder - builder, - DynamicMessageStruct::Reader reader) { - DynamicMessage::DynamicReader member_reader = - reader.get(ParameterPackType::type::view()); - { - Error error = - JsonDecodeImpl::type>:: - decode(builder.template init(), member_reader); - if (error.failed()) { - return error; - } - } - { - Error error = - JsonDecodeImpl...>>:: - decodeMembers(builder, reader); - if (error.failed()) { - return error; - } - } - return noError(); - } - static Error decode( - typename MessageStruct...>::Builder builder, - DynamicMessage::DynamicReader reader) { - if (reader.type() != DynamicMessage::Type::Struct) { - return criticalError("Not a struct"); - } - Error error = - JsonDecodeImpl...>>:: - decodeMembers<0>(builder, reader.as()); - if (error.failed()) { - return error; - } - return noError(); - } -}; - -class JsonCodec { -private: - bool isWhitespace(int8_t letter) { - return letter == '\t' || letter == ' ' || letter == '\r' || - letter == '\n'; - } - - void skipWhitespace(Buffer &buffer) { - while (buffer.readCompositeLength() > 0 && - isWhitespace(buffer.read())) { - buffer.readAdvance(1); - } - } - - Error decodeBool(DynamicMessageBool::Builder message, Buffer &buffer) { - /// @todo unimplemented - return noError(); - } - - // Not yet clear if double or integer - Error decodeNumber(Own &message, Buffer &buffer) { - assert((buffer.read() >= '0' && buffer.read() <= '9') || - buffer.read() == '+' || buffer.read() == '-'); - size_t offset = 0; - - if (buffer.read() == '-') { - ++offset; - } else if (buffer.read() == '+') { - return criticalError("Not a valid number with +"); - } - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - bool integer = true; - if (buffer.read(offset) >= '1' && buffer.read(offset) <= '9') { - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - - while (1) { - if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - continue; - } - break; - } - } else if (buffer.read(offset) == '0') { - ++offset; - } else { - return criticalError("Not a JSON number"); - } - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - if (buffer.read(offset) == '.') { - integer = false; - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - - size_t partial_start = offset; - - while (1) { - if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - continue; - } - break; - } - - if (offset == partial_start) { - return criticalError("No numbers after '.'"); - } - } - if (buffer.read(offset) == 'e' || buffer.read(offset) == 'E') { - integer = false; - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - - if (buffer.read(offset) == '+' || buffer.read(offset) == '-') { - ++offset; - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - } - - size_t exp_start = offset; - - while (1) { - if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { - ++offset; - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - continue; - } - break; - } - if (offset == exp_start) { - return criticalError("No numbers after exponent token"); - } - } - - if (offset >= buffer.readCompositeLength()) { - return recoverableError("Buffer too short"); - } - - std::string_view number_view{reinterpret_cast(&buffer.read()), - offset}; - if (integer) { - int64_t result; - auto fc_result = std::from_chars( - number_view.data(), number_view.data() + number_view.size(), - result); - if (fc_result.ec != std::errc{}) { - return criticalError("Not an integer"); - } - auto int_msg = std::make_unique(); - DynamicMessageSigned::Builder builder{*int_msg}; - builder.set(result); - message = std::move(int_msg); - } else { - std::string number_copy{number_view}; - double result; - try { - result = std::stod(number_copy); - } catch (std::exception &e) { - return criticalError("Not a double"); - } - // - auto dbl_msg = std::make_unique(); - DynamicMessageDouble::Builder builder{*dbl_msg}; - builder.set(result); - message = std::move(dbl_msg); - } - - buffer.readAdvance(offset); - skipWhitespace(buffer); - return noError(); - } - - Error decodeNull(Buffer &buffer) { - /// @todo unimplemented - return noError(); - } - - Error decodeValue(Own &message, Buffer &buffer) { - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - - switch (buffer.read()) { - case '"': { - std::string str; - Error error = decodeRawString(str, buffer); - if (error.failed()) { - return error; - } - Own msg_string = - std::make_unique(); - DynamicMessageString::Builder builder{*msg_string}; - builder.set(std::move(str)); - message = std::move(msg_string); - } break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': { - Error error = decodeNumber(message, buffer); - if (error.failed()) { - return error; - } - } break; - case 't': - case 'T': - case 'f': - case 'F': { - Own msg_bool = - std::make_unique(); - decodeBool(DynamicMessageBool::Builder{*msg_bool}, buffer); - message = std::move(msg_bool); - } break; - case '{': { - Own msg_struct = - std::make_unique(); - Error error = decodeStruct( - DynamicMessageStruct::Builder{*msg_struct}, buffer); - if (error.failed()) { - return error; - } - message = std::move(msg_struct); - } break; - case '[': { - Own msg_list = - std::make_unique(); - decodeList(DynamicMessageList::Builder{*msg_list}, buffer); - message = std::move(msg_list); - } break; - case 'n': - case 'N': { - Own msg_null = - std::make_unique(); - decodeNull(buffer); - message = std::move(msg_null); - } break; - default: { - return criticalError("Cannot identify next JSON value"); - } - } - - skipWhitespace(buffer); - return noError(); - } - - Error decodeRawString(std::string &raw, Buffer &buffer) { - assert(buffer.read() == '"'); - buffer.readAdvance(1); - std::stringstream iss; - bool string_done = false; - while (!string_done) { - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - switch (buffer.read()) { - case '\\': - buffer.readAdvance(1); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - switch (buffer.read()) { - case '\\': - case '/': - case '"': - iss << buffer.read(); - break; - case 'b': - iss << '\b'; - break; - case 'f': - iss << '\f'; - break; - case 'n': - iss << '\n'; - break; - case 'r': - iss << '\r'; - break; - case 't': - iss << '\t'; - break; - case 'u': { - buffer.readAdvance(1); - if (buffer.readCompositeLength() < 4) { - return recoverableError( - "Broken unicode or short buffer"); - } - /// @todo correct unicode handling - iss << '?'; // dummy line - iss << '?'; - iss << '?'; - iss << '?'; - // There is alway a skip at the end so here we skip 3 - // instead of 4 bytes - buffer.readAdvance(3); - } break; - } - break; - case '"': - string_done = true; - break; - default: - iss << buffer.read(); - break; - } - buffer.readAdvance(1); - } - raw = iss.str(); - return noError(); - } - - Error decodeList(DynamicMessageList::Builder builder, Buffer &buffer) { - assert(buffer.read() == '['); - buffer.readAdvance(1); - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - if (buffer.read() == ']') { - return noError(); - } - - Own message = nullptr; - { Error error = decodeValue(message, buffer); } - - while (buffer.read() != ']') { - } - /// @todo unimplemented - return noError(); - } - - Error decodeStruct(DynamicMessageStruct::Builder message, Buffer &buffer) { - assert(buffer.read() == '{'); - buffer.readAdvance(1); - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - if (buffer.read() == '}') { - buffer.readAdvance(1); - return noError(); - } - - while (buffer.read() != '}') { - if (buffer.read() == '"') { - std::string key_string; - { - Error error = decodeRawString(key_string, buffer); - if (error.failed()) { - return error; - } - } - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - if (buffer.read() != ':') { - return criticalError("Expecting a ':' token"); - } - buffer.readAdvance(1); - Own msg = nullptr; - { - Error error = decodeValue(msg, buffer); - if (error.failed()) { - return error; - } - } - message.init(key_string, std::move(msg)); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - - /* - skipWhitespace(buffer); - if(buffer.readCompositeLength() == 0){ - return recoverableError("Buffer too short"); - } - /// @todo value decode - skipWhitespace(buffer); - */ - switch (buffer.read()) { - case '}': - break; - case ',': - buffer.readAdvance(1); - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - break; - default: - return criticalError("Not a JSON Object"); - } - } else { - return criticalError("Not a JSON Object"); - } - } - buffer.readAdvance(1); - return noError(); - } - - ErrorOr> decodeDynamic(Buffer &buffer) { - skipWhitespace(buffer); - if (buffer.readCompositeLength() == 0) { - return recoverableError("Buffer too short"); - } - if (buffer.read() == '{') { - - Own message = - std::make_unique(); - Error error = - decodeStruct(DynamicMessageStruct::Builder{*message}, buffer); - if (error.failed()) { - return error; - } - skipWhitespace(buffer); - - return Own{std::move(message)}; - } else if (buffer.read() == '[') { - - Own message = - std::make_unique(); - Error error = decodeList(*message, buffer); - if (error.failed()) { - return error; - } - skipWhitespace(buffer); - - return Own{std::move(message)}; - } else { - return criticalError("Not a JSON Object"); - } - } - -public: - template - Error encode(typename T::Reader reader, Buffer &buffer) { - return JsonEncodeImpl::encode(reader, buffer); - } - - template - Error decode(typename T::Builder builder, Buffer &buffer) { - ErrorOr> error_or_message = decodeDynamic(buffer); - if (error_or_message.isError()) { - return error_or_message.error(); - } - - Own message = std::move(error_or_message.value()); - if (!message) { - return criticalError("No message object created"); - } - if (message->type() == DynamicMessage::Type::Null) { - return criticalError("Can't decode to json"); - } - DynamicMessage::DynamicReader reader{*message}; - Error static_error = JsonDecodeImpl::decode(builder, reader); - return static_error; - } -}; - -} // namespace gin diff --git a/source/SConscript b/source/kelgin/SConscript similarity index 100% rename from source/SConscript rename to source/kelgin/SConscript diff --git a/source/async.cpp b/source/kelgin/async.cpp similarity index 94% rename from source/async.cpp rename to source/kelgin/async.cpp index efe6938..65ee9b8 100644 --- a/source/async.cpp +++ b/source/kelgin/async.cpp @@ -29,6 +29,7 @@ void ConveyorStorage::setParent(ConveyorStorage *p) { assert(!parent); armNext(); } + parent = p; } @@ -37,8 +38,7 @@ ConveyorBase::ConveyorBase(Own &&node_p, : node{std::move(node_p)}, storage{storage_p} {} Error PropagateError::operator()(const Error &error) const { - Error err{error}; - return err; + return error.copyError(); } Error PropagateError::operator()(Error &&error) { return std::move(error); } @@ -143,6 +143,8 @@ void Event::disarm() { bool Event::isArmed() const { return prev != nullptr; } +SinkConveyor::SinkConveyor() : node{nullptr} {} + SinkConveyor::SinkConveyor(Own &&node_p) : node{std::move(node_p)} {} @@ -275,15 +277,22 @@ void ConveyorSinks::fail(Error &&error) { /// @todo call error_handler } +ConveyorSinks::ConveyorSinks(EventLoop &event_loop) : Event{event_loop} {} + void ConveyorSinks::add(Conveyor &&sink) { auto nas = Conveyor::fromConveyor(std::move(sink)); - Own sink_node = - heap(std::move(nas.first), *this); + + Own sink_node = nullptr; + try { + sink_node = heap(std::move(nas.first), *this); + } catch (std::bad_alloc &) { + return; + } if (nas.second) { nas.second->setParent(sink_node.get()); } - sink_nodes.push_back(std::move(sink_node)); + sink_nodes.emplace_back(std::move(sink_node)); } void ConveyorSinks::fire() { diff --git a/source/async.h b/source/kelgin/async.h similarity index 55% rename from source/async.h rename to source/kelgin/async.h index f550318..ad970be 100644 --- a/source/async.h +++ b/source/kelgin/async.h @@ -24,6 +24,10 @@ public: }; class EventLoop; +/* + * Event class similar to capn'proto. + * https://github.com/capnproto/capnproto + */ class Event { private: EventLoop &loop; @@ -84,11 +88,18 @@ template Conveyor chainedConveyorType(T *); template Conveyor chainedConveyorType(Conveyor *); +template T reduceErrorOrType(T *); + +template T reduceErrorOrType(ErrorOr *); + +template +using ReduceErrorOr = decltype(reduceErrorOrType((T *)nullptr)); + template using ChainedConveyors = decltype(chainedConveyorType((T *)nullptr)); template -using ConveyorResult = ChainedConveyors>; +using ConveyorResult = ChainedConveyors>>; struct PropagateError { public: @@ -101,63 +112,88 @@ private: Own node; public: + SinkConveyor(); SinkConveyor(Own &&node); SinkConveyor(SinkConveyor &&) = default; SinkConveyor &operator=(SinkConveyor &&) = default; }; -template class Conveyor : public ConveyorBase { +/** + * Main interface for async operations. + */ +template class Conveyor final : public ConveyorBase { public: - /* - * Construct a immediately fulfilled node + /** + * Construct an immediately fulfilled node */ Conveyor(FixVoid value); - /* - * empty promise - * @todo remove this + + /** + * Construct an immediately failed node + */ + Conveyor(Error &&error); + + /** + * Construct a conveyor with a child node and the next storage point */ Conveyor(Own &&node_p, ConveyorStorage *storage_p); Conveyor(Conveyor &&) = default; Conveyor &operator=(Conveyor &&) = default; - /* - * This method converts passed values or errors from children + /** + * This method converts values or errors from children */ template ConveyorResult then(Func &&func, ErrorFunc &&error_func = PropagateError()); - /* - * This method adds a buffer node in the conveyor chains and acts as a - * scheduler interruption point. + /** + * This method adds a buffer node in the conveyor chains which acts as a + * scheduler interrupt point and collects elements up to the supplied limit. */ Conveyor buffer(size_t limit = std::numeric_limits::max()); - /* - * This method just takes ownership of any supplied types + /** + * This method just takes ownership of any supplied types, + * which are destroyed when the chain gets destroyed. + * Useful for resource lifetime control. */ - template Conveyor attach(Args &&... args); + template Conveyor attach(Args &&...args); - /* - * + /** @todo implement + * This method limits the total amount of passed elements + * Be careful where you place this node into the chain. + * If you meant to fork it and destroy paths you shouldn't place + * an interrupt point between the fork and this limiter */ - Conveyor limit(size_t val = std::numeric_limits::max()); + Conveyor limit(size_t val = 1); - /* - * + /** + * Moves the conveyor chain into a thread local storage point which drops + * every element. Use sink() if you want to control the lifetime of a + * conveyor chain */ - template void detach(ErrorFunc &&err_func); + template + void detach(ErrorFunc &&err_func = PropagateError()); - /* - * + /** + * Creates a local sink which drops elements, but lifetime control remains + * in your hand. */ - template SinkConveyor sink(ErrorFunc &&error_func); + template + SinkConveyor sink(ErrorFunc &&error_func = PropagateError()); - // Waiting and resolving + /** + * If no sink() or detach() is used you have to take elements out of the + * chain yourself. + */ ErrorOr> take(); + /** @todo implement + * Specifically pump elements through this chain + */ void poll(); // helper @@ -169,6 +205,13 @@ public: fromConveyor(Conveyor &&conveyor); }; +/* + * Join Conveyors into a single one + */ +// template +// Conveyor> joinConveyors(std::tuple>& +// conveyors); + template class ConveyorFeeder { public: virtual ~ConveyorFeeder() = default; @@ -202,6 +245,11 @@ template ConveyorAndFeeder oneTimeConveyorAndFeeder(); enum class Signal : uint8_t { Terminate, User1 }; +/** + * Class which acts as a correspondent between the running framework and outside + * events which may be signals from the operating system or just other threads. + * Default EventPorts are supplied by setupAsyncIo() in io.h + */ class EventPort { public: virtual ~EventPort() = default; @@ -212,11 +260,13 @@ public: virtual void wait() = 0; virtual void wait(const std::chrono::steady_clock::duration &) = 0; virtual void wait(const std::chrono::steady_clock::time_point &) = 0; + + virtual void wake() = 0; }; class SinkConveyorNode; -class ConveyorSinks : public Event { +class ConveyorSinks final : public Event { private: friend class SinkConveyorNode; @@ -231,12 +281,17 @@ private: public: ConveyorSinks() = default; + ConveyorSinks(EventLoop &event_loop); void add(Conveyor &&node); void fire() override; }; +/* + * EventLoop class similar to capn'proto. + * https://github.com/capnproto/capnproto + */ class EventLoop { private: friend class Event; @@ -266,6 +321,9 @@ public: EventLoop(Own &&port); ~EventLoop(); + EventLoop(EventLoop &&) = default; + EventLoop &operator=(EventLoop &&) = default; + bool wait(); bool wait(const std::chrono::steady_clock::duration &); bool wait(const std::chrono::steady_clock::time_point &); @@ -276,6 +334,10 @@ public: ConveyorSinks &daemon(); }; +/* + * WaitScope class similar to capn'proto. + * https://github.com/capnproto/capnproto + */ class WaitScope { private: EventLoop &loop; @@ -332,7 +394,7 @@ template <> struct FixVoidCaller { template class AdaptConveyorNode; template -class AdaptConveyorFeeder : public ConveyorFeeder> { +class AdaptConveyorFeeder final : public ConveyorFeeder> { private: AdaptConveyorNode *feedee = nullptr; @@ -349,7 +411,7 @@ public: }; template -class AdaptConveyorNode : public ConveyorNode, public ConveyorStorage { +class AdaptConveyorNode final : public ConveyorNode, public ConveyorStorage { private: AdaptConveyorFeeder *feeder = nullptr; @@ -379,7 +441,7 @@ public: template class OneTimeConveyorNode; template -class OneTimeConveyorFeeder : public ConveyorFeeder> { +class OneTimeConveyorFeeder final : public ConveyorFeeder> { private: OneTimeConveyorNode *feedee = nullptr; @@ -396,7 +458,7 @@ public: }; template -class OneTimeConveyorNode : public ConveyorNode, public ConveyorStorage { +class OneTimeConveyorNode final : public ConveyorNode, public ConveyorStorage { private: OneTimeConveyorFeeder *feeder = nullptr; @@ -433,7 +495,7 @@ public: }; template -class QueueBufferConveyorNode : public QueueBufferConveyorNodeBase { +class QueueBufferConveyorNode final : public QueueBufferConveyorNodeBase { private: std::queue> storage; size_t max_store; @@ -487,16 +549,18 @@ public: AttachConveyorNodeBase(Own &&dep) : ConveyorNode(std::move(dep)) {} + virtual ~AttachConveyorNodeBase() = default; + void getResult(ErrorOrValue &err_or_val) override; }; template -class AttachConveyorNode : public AttachConveyorNodeBase { +class AttachConveyorNode final : public AttachConveyorNodeBase { private: std::tuple attached_data; public: - AttachConveyorNode(Own &&dep, Args &&... args) + AttachConveyorNode(Own &&dep, Args &&...args) : AttachConveyorNodeBase(std::move(dep)), attached_data{ std::move(args...)} {} }; @@ -504,6 +568,7 @@ public: class ConvertConveyorNodeBase : public ConveyorNode { public: ConvertConveyorNodeBase(Own &&dep); + virtual ~ConvertConveyorNodeBase() = default; void getResult(ErrorOrValue &err_or_val) override; @@ -511,7 +576,7 @@ public: }; template -class ConvertConveyorNode : public ConvertConveyorNodeBase { +class ConvertConveyorNode final : public ConvertConveyorNodeBase { private: Func func; ErrorFunc error_func; @@ -522,14 +587,22 @@ public: : ConvertConveyorNodeBase(std::move(dep)), func{std::move(func)}, error_func{std::move(error_func)} {} - void getImpl(ErrorOrValue &err_or_val) override { + void getImpl(ErrorOrValue &err_or_val) noexcept override { ErrorOr dep_eov; ErrorOr &eov = err_or_val.as(); if (child) { child->getResult(dep_eov); if (dep_eov.isValue()) { - eov = FixVoidCaller::apply(func, - std::move(dep_eov.value())); + try { + eov = FixVoidCaller::apply( + func, std::move(dep_eov.value())); + } catch (const std::bad_alloc &) { + eov = criticalError("Out of memory"); + } catch (const std::exception &) { + eov = criticalError( + "Exception in chain occured. Return ErrorOr if you " + "want to handle errors which are recoverable"); + } } else if (dep_eov.isError()) { eov = error_func(std::move(dep_eov.error())); } else { @@ -541,7 +614,7 @@ public: } }; -class SinkConveyorNode : public ConveyorNode, public ConveyorStorage { +class SinkConveyorNode final : public ConveyorNode, public ConveyorStorage { private: ConveyorSinks *conveyor_sink; @@ -569,7 +642,7 @@ public: size_t queued() const override { return 0; } // ConveyorNode - void getResult(ErrorOrValue &err_or_val) override { + void getResult(ErrorOrValue &err_or_val) noexcept override { err_or_val.as() = criticalError("In a sink node no result can be returned"); } @@ -596,16 +669,18 @@ public: class ImmediateConveyorNodeBase : public ConveyorNode, public ConveyorStorage { private: public: + virtual ~ImmediateConveyorNodeBase() = default; }; template -class ImmediateConveyorNode : public ImmediateConveyorNodeBase { +class ImmediateConveyorNode final : public ImmediateConveyorNodeBase { private: - FixVoid value; - bool retrieved; + ErrorOr> value; + uint8_t retrieved; public: ImmediateConveyorNode(FixVoid &&val); + ImmediateConveyorNode(Error &&error); // ConveyorStorage size_t space() const override; @@ -614,337 +689,19 @@ public: void childFired() override; // ConveyorNode - void getResult(ErrorOrValue &err_or_val) override { + void getResult(ErrorOrValue &err_or_val) noexcept override { if (retrieved) { err_or_val.as>() = criticalError("Already taken value"); } else { err_or_val.as>() = std::move(value); - retrieved = true; } + ++retrieved; } // Event void fire() override; }; -template -ImmediateConveyorNode::ImmediateConveyorNode(FixVoid &&val) - : value{std::move(val)}, retrieved{false} {} - -template size_t ImmediateConveyorNode::space() const { - return 0; -} - -template size_t ImmediateConveyorNode::queued() const { - return retrieved ? 0 : 1; -} - -template void ImmediateConveyorNode::childFired() { - // Impossible -} - -template void ImmediateConveyorNode::fire() { - if (parent) { - parent->childFired(); - } -} - } // namespace gin -#include -// Template inlining -namespace gin { -template T reduceErrorOrType(T *); -template T reduceErrorOrType(ErrorOr *); - -template -using ReduceErrorOr = decltype(reduceErrorOrType((T *)nullptr)); - -template -Conveyor::Conveyor(FixVoid value) : ConveyorBase(nullptr, nullptr) { - // Is there any way to do this? - // @todo new ConveyorBase constructor for Immediate values - auto immediate = heap>>(std::move(value)); - storage = reinterpret_cast(immediate.get()); - node = std::move(immediate); -} - -template -Conveyor::Conveyor(Own &&node_p, ConveyorStorage *storage_p) - : ConveyorBase(std::move(node_p), storage_p) {} - -template -template -ConveyorResult Conveyor::then(Func &&func, ErrorFunc &&error_func) { - Own conversion_node = - heap>>, - FixVoid, Func, ErrorFunc>>( - std::move(node), std::move(func), std::move(error_func)); - - return Conveyor>>::toConveyor( - std::move(conversion_node), storage); -} - -template Conveyor Conveyor::buffer(size_t size) { - Own>> storage_node = - heap>>(std::move(node), size); - ConveyorStorage *storage_ptr = - static_cast(storage_node.get()); - storage->setParent(storage_ptr); - return Conveyor{std::move(storage_node), storage_ptr}; -} - -template -template -Conveyor Conveyor::attach(Args &&... args) { - Own> attach_node = - heap>(std::move(node), std::move(args...)); - return Conveyor{std::move(attach_node), storage}; -} - -template <> -template -SinkConveyor Conveyor::sink(ErrorFunc &&error_func) { - Own sink_node = heap(std::move(node)); - ConveyorStorage *storage_ptr = - static_cast(sink_node.get()); - if (storage) { - storage->setParent(storage_ptr); - } - return SinkConveyor{std::move(sink_node)}; -} - -void detachConveyor(Conveyor &&conveyor); - -template -template -void Conveyor::detach(ErrorFunc &&func) { - detachConveyor(std::move(then([](T &&) {}, std::move(func)))); -} - -template <> -template -void Conveyor::detach(ErrorFunc &&func) { - detachConveyor(std::move(then([]() {}, std::move(func)))); -} - -template -Conveyor Conveyor::toConveyor(Own &&node, - ConveyorStorage *storage) { - return Conveyor{std::move(node), storage}; -} - -template -std::pair, ConveyorStorage *> -Conveyor::fromConveyor(Conveyor &&conveyor) { - return std::make_pair(std::move(conveyor.node), conveyor.storage); -} - -template ErrorOr> Conveyor::take() { - if (storage) { - if (storage->queued() > 0) { - ErrorOr> result; - node->getResult(result); - return ErrorOr>{result}; - } else { - return ErrorOr>{ - recoverableError("Conveyor buffer has no elements")}; - } - } else { - return ErrorOr>{criticalError("Conveyor in invalid state")}; - } -} - -template ConveyorAndFeeder newConveyorAndFeeder() { - Own>> feeder = - heap>>(); - Own>> node = - heap>>(); - - feeder->setFeedee(node.get()); - node->setFeeder(feeder.get()); - - ConveyorStorage *storage_ptr = static_cast(node.get()); - - return ConveyorAndFeeder{ - std::move(feeder), - Conveyor::toConveyor(std::move(node), storage_ptr)}; -} - -template AdaptConveyorFeeder::~AdaptConveyorFeeder() { - if (feedee) { - feedee->setFeeder(nullptr); - feedee = nullptr; - } -} - -template -void AdaptConveyorFeeder::setFeedee(AdaptConveyorNode *feedee_p) { - feedee = feedee_p; -} - -template void AdaptConveyorFeeder::feed(T &&value) { - if (feedee) { - feedee->feed(std::move(value)); - } -} - -template void AdaptConveyorFeeder::fail(Error &&error) { - if (feedee) { - feedee->fail(std::move(error)); - } -} - -template size_t AdaptConveyorFeeder::queued() const { - if (feedee) { - return feedee->queued(); - } - return 0; -} - -template size_t AdaptConveyorFeeder::space() const { - if (feedee) { - return feedee->space(); - } - return 0; -} - -template AdaptConveyorNode::~AdaptConveyorNode() { - if (feeder) { - feeder->setFeedee(nullptr); - feeder = nullptr; - } -} - -template -void AdaptConveyorNode::setFeeder(AdaptConveyorFeeder *feeder_p) { - feeder = feeder_p; -} - -template void AdaptConveyorNode::feed(T &&value) { - storage.push(std::move(value)); - armNext(); -} - -template void AdaptConveyorNode::fail(Error &&error) { - storage.push(std::move(error)); - armNext(); -} - -template size_t AdaptConveyorNode::queued() const { - return storage.size(); -} - -template size_t AdaptConveyorNode::space() const { - return std::numeric_limits::max() - storage.size(); -} - -template -void AdaptConveyorNode::getResult(ErrorOrValue &err_or_val) { - if (!storage.empty()) { - err_or_val.as() = std::move(storage.front()); - storage.pop(); - } else { - err_or_val.as() = - criticalError("Signal for retrieval of storage sent even though no " - "data is present"); - } -} - -template void AdaptConveyorNode::fire() { - if (parent) { - parent->childFired(); - - if (storage.size() > 0) { - armLater(); - } - } -} - -template OneTimeConveyorFeeder::~OneTimeConveyorFeeder() { - if (feedee) { - feedee->setFeeder(nullptr); - feedee = nullptr; - } -} - -template -void OneTimeConveyorFeeder::setFeedee(OneTimeConveyorNode *feedee_p) { - feedee = feedee_p; -} - -template void OneTimeConveyorFeeder::feed(T &&value) { - if (feedee) { - feedee->feed(std::move(value)); - } -} - -template void OneTimeConveyorFeeder::fail(Error &&error) { - if (feedee) { - feedee->fail(std::move(error)); - } -} - -template size_t OneTimeConveyorFeeder::queued() const { - if (feedee) { - return feedee->queued(); - } - return 0; -} - -template size_t OneTimeConveyorFeeder::space() const { - if (feedee) { - return feedee->space(); - } - return 0; -} - -template OneTimeConveyorNode::~OneTimeConveyorNode() { - if (feeder) { - feeder->setFeedee(nullptr); - feeder = nullptr; - } -} - -template -void OneTimeConveyorNode::setFeeder(OneTimeConveyorFeeder *feeder_p) { - feeder = feeder_p; -} - -template void OneTimeConveyorNode::feed(T &&value) { - storage = std::move(value); - armNext(); -} - -template void OneTimeConveyorNode::fail(Error &&error) { - storage = std::move(error); - armNext(); -} - -template size_t OneTimeConveyorNode::queued() const { - return storage.has_value() ? 1 : 0; -} - -template size_t OneTimeConveyorNode::space() const { - return passed ? 0 : 1; -} - -template -void OneTimeConveyorNode::getResult(ErrorOrValue &err_or_val) { - if (storage.has_value()) { - err_or_val.as() = std::move(storage.value()); - storage = std::nullopt; - } else { - err_or_val.as() = - criticalError("Signal for retrieval of storage sent even though no " - "data is present"); - } -} - -template void OneTimeConveyorNode::fire() { - if (parent) { - parent->childFired(); - } -} - -} // namespace gin +#include "async.tmpl.h" diff --git a/source/kelgin/async.tmpl.h b/source/kelgin/async.tmpl.h new file mode 100644 index 0000000..12518b2 --- /dev/null +++ b/source/kelgin/async.tmpl.h @@ -0,0 +1,344 @@ +#pragma once + +#include +// Template inlining +namespace gin { + +template +ImmediateConveyorNode::ImmediateConveyorNode(FixVoid &&val) + : value{std::move(val)}, retrieved{0} {} + +template +ImmediateConveyorNode::ImmediateConveyorNode(Error &&error) + : value{std::move(error)}, retrieved{0} {} + +template size_t ImmediateConveyorNode::space() const { + return 0; +} + +template size_t ImmediateConveyorNode::queued() const { + return retrieved > 1 ? 0 : 1; +} + +template void ImmediateConveyorNode::childFired() { + // Impossible case + assert(false); +} + +template void ImmediateConveyorNode::fire() { + if (parent) { + parent->childFired(); + } + if (queued() > 0) { + armLast(); + } +} + +template +Conveyor::Conveyor(FixVoid value) : ConveyorBase(nullptr, nullptr) { + // Is there any way to do this? + // @todo new ConveyorBase constructor for Immediate values + + Own>> immediate = + heap>>(std::move(value)); + + if (!immediate) { + return; + } + + storage = static_cast(immediate.get()); + node = std::move(immediate); +} + +template +Conveyor::Conveyor(Error &&error) : ConveyorBase(nullptr, nullptr) { + Own>> immediate = + heap>>(std::move(error)); + + if (!immediate) { + return; + } + + storage = static_cast(immediate.get()); + node = std::move(immediate); +} + +template +Conveyor::Conveyor(Own &&node_p, ConveyorStorage *storage_p) + : ConveyorBase(std::move(node_p), storage_p) {} + +template +template +ConveyorResult Conveyor::then(Func &&func, ErrorFunc &&error_func) { + Own conversion_node = + heap>>, + FixVoid, Func, ErrorFunc>>( + std::move(node), std::move(func), std::move(error_func)); + + return Conveyor>>::toConveyor( + std::move(conversion_node), storage); +} + +template Conveyor Conveyor::buffer(size_t size) { + Own>> storage_node = + heap>>(std::move(node), size); + ConveyorStorage *storage_ptr = + static_cast(storage_node.get()); + storage->setParent(storage_ptr); + return Conveyor{std::move(storage_node), storage_ptr}; +} + +template +template +Conveyor Conveyor::attach(Args &&...args) { + Own> attach_node = + heap>(std::move(node), std::move(args...)); + return Conveyor{std::move(attach_node), storage}; +} + +template <> +template +SinkConveyor Conveyor::sink(ErrorFunc &&error_func) { + Own sink_node = heap(std::move(node)); + ConveyorStorage *storage_ptr = + static_cast(sink_node.get()); + if (storage) { + storage->setParent(storage_ptr); + } + return SinkConveyor{std::move(sink_node)}; +} + +void detachConveyor(Conveyor &&conveyor); + +template +template +void Conveyor::detach(ErrorFunc &&func) { + detachConveyor(std::move(then([](T &&) {}, std::move(func)))); +} + +template <> +template +void Conveyor::detach(ErrorFunc &&func) { + detachConveyor(std::move(then([]() {}, std::move(func)))); +} + +template +Conveyor Conveyor::toConveyor(Own &&node, + ConveyorStorage *storage) { + return Conveyor{std::move(node), storage}; +} + +template +std::pair, ConveyorStorage *> +Conveyor::fromConveyor(Conveyor &&conveyor) { + return std::make_pair(std::move(conveyor.node), conveyor.storage); +} + +template ErrorOr> Conveyor::take() { + if (storage) { + if (storage->queued() > 0) { + ErrorOr> result; + node->getResult(result); + return result; + } else { + return ErrorOr>{ + recoverableError("Conveyor buffer has no elements")}; + } + } else { + return ErrorOr>{criticalError("Conveyor in invalid state")}; + } +} + +template ConveyorAndFeeder newConveyorAndFeeder() { + Own>> feeder = + heap>>(); + Own>> node = + heap>>(); + + feeder->setFeedee(node.get()); + node->setFeeder(feeder.get()); + + ConveyorStorage *storage_ptr = static_cast(node.get()); + + return ConveyorAndFeeder{ + std::move(feeder), + Conveyor::toConveyor(std::move(node), storage_ptr)}; +} + +template AdaptConveyorFeeder::~AdaptConveyorFeeder() { + if (feedee) { + feedee->setFeeder(nullptr); + feedee = nullptr; + } +} + +template +void AdaptConveyorFeeder::setFeedee(AdaptConveyorNode *feedee_p) { + feedee = feedee_p; +} + +template void AdaptConveyorFeeder::feed(T &&value) { + if (feedee) { + feedee->feed(std::move(value)); + } +} + +template void AdaptConveyorFeeder::fail(Error &&error) { + if (feedee) { + feedee->fail(std::move(error)); + } +} + +template size_t AdaptConveyorFeeder::queued() const { + if (feedee) { + return feedee->queued(); + } + return 0; +} + +template size_t AdaptConveyorFeeder::space() const { + if (feedee) { + return feedee->space(); + } + return 0; +} + +template AdaptConveyorNode::~AdaptConveyorNode() { + if (feeder) { + feeder->setFeedee(nullptr); + feeder = nullptr; + } +} + +template +void AdaptConveyorNode::setFeeder(AdaptConveyorFeeder *feeder_p) { + feeder = feeder_p; +} + +template void AdaptConveyorNode::feed(T &&value) { + storage.push(std::move(value)); + armNext(); +} + +template void AdaptConveyorNode::fail(Error &&error) { + storage.push(std::move(error)); + armNext(); +} + +template size_t AdaptConveyorNode::queued() const { + return storage.size(); +} + +template size_t AdaptConveyorNode::space() const { + return std::numeric_limits::max() - storage.size(); +} + +template +void AdaptConveyorNode::getResult(ErrorOrValue &err_or_val) { + if (!storage.empty()) { + err_or_val.as() = std::move(storage.front()); + storage.pop(); + } else { + err_or_val.as() = + criticalError("Signal for retrieval of storage sent even though no " + "data is present"); + } +} + +template void AdaptConveyorNode::fire() { + if (parent) { + parent->childFired(); + + if (storage.size() > 0) { + armLater(); + } + } +} + +template OneTimeConveyorFeeder::~OneTimeConveyorFeeder() { + if (feedee) { + feedee->setFeeder(nullptr); + feedee = nullptr; + } +} + +template +void OneTimeConveyorFeeder::setFeedee(OneTimeConveyorNode *feedee_p) { + feedee = feedee_p; +} + +template void OneTimeConveyorFeeder::feed(T &&value) { + if (feedee) { + feedee->feed(std::move(value)); + } +} + +template void OneTimeConveyorFeeder::fail(Error &&error) { + if (feedee) { + feedee->fail(std::move(error)); + } +} + +template size_t OneTimeConveyorFeeder::queued() const { + if (feedee) { + return feedee->queued(); + } + return 0; +} + +template size_t OneTimeConveyorFeeder::space() const { + if (feedee) { + return feedee->space(); + } + return 0; +} + +template OneTimeConveyorNode::~OneTimeConveyorNode() { + if (feeder) { + feeder->setFeedee(nullptr); + feeder = nullptr; + } +} + +template +void OneTimeConveyorNode::setFeeder(OneTimeConveyorFeeder *feeder_p) { + feeder = feeder_p; +} + +template void OneTimeConveyorNode::feed(T &&value) { + storage = std::move(value); + armNext(); +} + +template void OneTimeConveyorNode::fail(Error &&error) { + storage = std::move(error); + armNext(); +} + +template size_t OneTimeConveyorNode::queued() const { + return storage.has_value() ? 1 : 0; +} + +template size_t OneTimeConveyorNode::space() const { + return passed ? 0 : 1; +} + +template +void OneTimeConveyorNode::getResult(ErrorOrValue &err_or_val) { + if (storage.has_value()) { + err_or_val.as() = std::move(storage.value()); + storage = std::nullopt; + } else { + err_or_val.as() = + criticalError("Signal for retrieval of storage sent even though no " + "data is present"); + } +} + +template void OneTimeConveyorNode::fire() { + if (parent) { + parent->childFired(); + } +} + +} // namespace gin diff --git a/source/kelgin/buffer.cpp b/source/kelgin/buffer.cpp new file mode 100644 index 0000000..509dfb9 --- /dev/null +++ b/source/kelgin/buffer.cpp @@ -0,0 +1,431 @@ +#include "buffer.h" + +#include +#include +#include +#include +#include + +namespace gin { +Error Buffer::push(const uint8_t &value) { + size_t write_remain = writeCompositeLength(); + if (write_remain > 0) { + write() = value; + writeAdvance(1); + } else { + return recoverableError("Buffer too small"); + } + return noError(); +} + +Error Buffer::push(const uint8_t &buffer, size_t size) { + Error error = writeRequireLength(size); + if (error.failed()) { + return error; + } + const uint8_t *buffer_ptr = &buffer; + while (size > 0) { + size_t segment = std::min(writeSegmentLength(), size); + memcpy(&write(), buffer_ptr, segment); + writeAdvance(segment); + size -= segment; + buffer_ptr += segment; + } + return noError(); +} + +Error Buffer::pop(uint8_t &value) { + if (readCompositeLength() > 0) { + value = read(); + readAdvance(1); + } else { + return recoverableError("Buffer too small"); + } + return noError(); +} + +Error Buffer::pop(uint8_t &buffer, size_t size) { + if (readCompositeLength() >= size) { + uint8_t *buffer_ptr = &buffer; + while (size > 0) { + size_t segment = std::min(readSegmentLength(), size); + memcpy(buffer_ptr, &read(), segment); + readAdvance(segment); + size -= segment; + buffer_ptr += segment; + } + } else { + return recoverableError("Buffer too small"); + } + return noError(); +} + +std::string Buffer::toString() const { + std::ostringstream oss; + for (size_t i = 0; i < readCompositeLength(); ++i) { + oss << read(i); + } + return oss.str(); +} + +std::string Buffer::toHex() const { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (size_t i = 0; i < readCompositeLength(); ++i) { + oss << std::setw(2) << (uint16_t)read(i); + if ((i + 1) < readCompositeLength()) { + oss << ((i % 4 == 3) ? '\n' : ' '); + } + } + return oss.str(); +} + +BufferView::BufferView(Buffer &buffer) + : buffer{buffer}, read_offset{0}, write_offset{0} {} + +size_t BufferView::readPosition() const { + return read_offset + buffer.readPosition(); +} + +size_t BufferView::readCompositeLength() const { + assert(read_offset <= buffer.readCompositeLength()); + if (read_offset > buffer.readCompositeLength()) { + return 0; + } + + return buffer.readCompositeLength() - read_offset; +} + +size_t BufferView::readSegmentLength(size_t offset) const { + size_t off = offset + read_offset; + assert(off <= buffer.readCompositeLength()); + if (off > buffer.readCompositeLength()) { + return 0; + } + + return buffer.readSegmentLength(off); +} + +void BufferView::readAdvance(size_t bytes) { + size_t offset = bytes + read_offset; + assert(offset <= buffer.readCompositeLength()); + if (offset > buffer.readCompositeLength()) { + read_offset += buffer.readCompositeLength(); + return; + } + + read_offset += bytes; +} + +uint8_t &BufferView::read(size_t i) { + size_t pos = i + read_offset; + + assert(pos < buffer.readCompositeLength()); + + return buffer.read(pos); +} + +const uint8_t &BufferView::read(size_t i) const { + size_t pos = i + read_offset; + + assert(pos < buffer.readCompositeLength()); + + return buffer.read(pos); +} + +size_t BufferView::writePosition() const { + return write_offset + buffer.writePosition(); +} + +size_t BufferView::writeCompositeLength() const { + assert(write_offset <= buffer.writeCompositeLength()); + if (write_offset > buffer.writeCompositeLength()) { + return 0; + } + + return buffer.writeCompositeLength() - write_offset; +} + +size_t BufferView::writeSegmentLength(size_t offset) const { + size_t off = offset + write_offset; + assert(off <= buffer.writeCompositeLength()); + if (off > buffer.writeCompositeLength()) { + return 0; + } + + return buffer.writeSegmentLength(off); +} + +void BufferView::writeAdvance(size_t bytes) { + size_t offset = bytes + write_offset; + assert(offset <= buffer.writeCompositeLength()); + if (offset > buffer.writeCompositeLength()) { + write_offset += buffer.writeCompositeLength(); + return; + } + + write_offset += bytes; +} + +uint8_t &BufferView::write(size_t i) { + size_t pos = i + write_offset; + + assert(pos < buffer.writeCompositeLength()); + + return buffer.write(pos); +} + +const uint8_t &BufferView::write(size_t i) const { + size_t pos = i + write_offset; + + assert(pos < buffer.writeCompositeLength()); + + return buffer.write(pos); +} + +Error BufferView::writeRequireLength(size_t bytes) { + return buffer.writeRequireLength(bytes + write_offset); +} + +size_t BufferView::readOffset() const { return read_offset; } + +size_t BufferView::writeOffset() const { return write_offset; } + +RingBuffer::RingBuffer() : read_position{0}, write_position{0} { + buffer.resize(RING_BUFFER_MAX_SIZE); +} + +RingBuffer::RingBuffer(size_t size) : read_position{0}, write_position{0} { + buffer.resize(size); +} + +size_t RingBuffer::readPosition() const { return read_position; } + +/* + * If write is ahead of read it is a simple distance, but if read ist ahead of + * write then there are two segments + * + */ +size_t RingBuffer::readCompositeLength() const { + return writePosition() < readPosition() + ? buffer.size() - (readPosition() - writePosition()) + : (write_reached_read ? buffer.size() + : writePosition() - readPosition()); +} + +/* + * If write is ahead then it's the simple distance again. If read is ahead it's + * until the end of the buffer/segment + */ +size_t RingBuffer::readSegmentLength(size_t offset) const { + size_t read_composite = readCompositeLength(); + assert(offset <= read_composite); + offset = std::min(offset, read_composite); + size_t remaining = read_composite - offset; + + size_t read_offset = readPosition() + offset; + read_offset = read_offset >= buffer.size() ? read_offset - buffer.size() + : read_offset; + + // case 1 write is located before read and reached read + // then offset can be used normally + // case 2 write is located at read, but read reached write + // then it is set to zero by readCompositeLength() + // case 3 write is located after read + // since std::min you can use simple subtraction + if (writePosition() < read_offset) { + return buffer.size() - read_offset; + } + + if (writePosition() == read_offset) { + if (remaining > 0) { + return buffer.size() - read_offset; + } else { + return 0; + } + } + + return writePosition() - read_offset; +} + +void RingBuffer::readAdvance(size_t bytes) { + size_t read_composite = readCompositeLength(); + assert(bytes <= read_composite); + bytes = std::min(bytes, read_composite); + size_t advanced = read_position + bytes; + read_position = advanced >= buffer.size() ? advanced - buffer.size() + : advanced; + write_reached_read = bytes > 0 ? false : write_reached_read; +} + +uint8_t &RingBuffer::read(size_t i) { + assert(i < readCompositeLength()); + size_t pos = read_position + i; + pos = pos >= buffer.size() ? pos - buffer.size() : pos; + return buffer[pos]; +} + +const uint8_t &RingBuffer::read(size_t i) const { + assert(i < readCompositeLength()); + size_t pos = read_position + i; + pos = pos >= buffer.size() ? pos - buffer.size() : pos; + return buffer[pos]; +} + +size_t RingBuffer::writePosition() const { return write_position; } + +size_t RingBuffer::writeCompositeLength() const { + return readPosition() > writePosition() + ? (readPosition() - writePosition()) + : (write_reached_read + ? 0 + : buffer.size() - (writePosition() - readPosition())); +} + +size_t RingBuffer::writeSegmentLength(size_t offset) const { + size_t write_composite = writeCompositeLength(); + assert(offset <= write_composite); + offset = std::min(offset, write_composite); + + size_t write_offset = writePosition() + offset; + write_offset = write_offset >= buffer.size() ? write_offset - buffer.size() + : write_offset; + + if (read_position > write_offset) { + return read_position - write_offset; + } + + if (write_reached_read) { + return 0; + } + + return buffer.size() - write_offset; +} + +void RingBuffer::writeAdvance(size_t bytes) { + assert(bytes <= writeCompositeLength()); + size_t advanced = write_position + bytes; + write_position = advanced >= buffer.size() ? advanced - buffer.size() + : advanced; + + write_reached_read = + (write_position == read_position && bytes > 0 ? true : false); +} + +uint8_t &RingBuffer::write(size_t i) { + assert(i < writeCompositeLength()); + size_t pos = write_position + i; + pos = pos >= buffer.size() ? pos - buffer.size() : pos; + return buffer[pos]; +} + +const uint8_t &RingBuffer::write(size_t i) const { + assert(i < writeCompositeLength()); + size_t pos = write_position + i; + pos = pos >= buffer.size() ? pos - buffer.size() : pos; + return buffer[pos]; +} +/* + Error RingBuffer::increaseSize(size_t size){ + size_t old_size = buffer.size(); + size_t new_size = old_size + size; + buffer.resize(new_size); + if(readPosition() > writePosition() || (readPosition() == + writePosition() && write_reached_read)){ size_t remaining = old_size - + writePosition(); size_t real_remaining = 0; while(remaining > 0){ size_t + segment = std::min(remaining, size); memcpy(&buffer[new_size-segment], + &buffer[old_size-segment], segment); remaining -= segment; size -= segment; + old_size -= segment; + new_size -= segment; + } + } + + return noError(); + } +*/ +Error RingBuffer::writeRequireLength(size_t bytes) { + size_t write_remain = writeCompositeLength(); + if (bytes > write_remain) { + return recoverableError("Buffer too small"); + } + return noError(); +} + +ArrayBuffer::ArrayBuffer(size_t size) : read_position{0}, write_position{0} { + buffer.resize(size); +} + +size_t ArrayBuffer::readPosition() const { return read_position; } + +size_t ArrayBuffer::readCompositeLength() const { + return write_position - read_position; +} + +size_t ArrayBuffer::readSegmentLength(size_t offset) const { + size_t read_composite = readCompositeLength(); + assert(offset <= read_composite); + + offset = std::min(read_composite, offset); + size_t read_offset = read_position + offset; + + return write_position - read_offset; +} + +void ArrayBuffer::readAdvance(size_t bytes) { + assert(bytes <= readCompositeLength()); + read_position += bytes; +} + +uint8_t &ArrayBuffer::read(size_t i) { + assert(i < readCompositeLength()); + + return buffer[i + read_position]; +} + +const uint8_t &ArrayBuffer::read(size_t i) const { + assert(i + read_position < buffer.size()); + + return buffer[i + read_position]; +} + +size_t ArrayBuffer::writePosition() const { return write_position; } + +size_t ArrayBuffer::writeCompositeLength() const { + assert(write_position <= buffer.size()); + return buffer.size() - write_position; +} + +size_t ArrayBuffer::writeSegmentLength(size_t offset) const { + assert(write_position <= buffer.size()); + size_t write_composite = writeCompositeLength(); + + assert(offset <= write_composite); + offset = std::min(write_composite, offset); + size_t write_offset = write_position + offset; + + return buffer.size() - write_offset; +} + +void ArrayBuffer::writeAdvance(size_t bytes) { + assert(bytes <= writeCompositeLength()); + write_position += bytes; +} + +uint8_t &ArrayBuffer::write(size_t i) { + assert(i < writeCompositeLength()); + return buffer[i + write_position]; +} + +const uint8_t &ArrayBuffer::write(size_t i) const { + assert(i < writeCompositeLength()); + return buffer[i + write_position]; +} +Error ArrayBuffer::writeRequireLength(size_t bytes) { + size_t write_remain = writeCompositeLength(); + if (bytes > write_remain) { + return recoverableError("Buffer too small"); + } + return noError(); +} + +} // namespace gin diff --git a/source/buffer.h b/source/kelgin/buffer.h similarity index 56% rename from source/buffer.h rename to source/kelgin/buffer.h index 39f8a2f..1ea71b2 100644 --- a/source/buffer.h +++ b/source/kelgin/buffer.h @@ -14,14 +14,13 @@ namespace gin { * Access class to reduce templated BufferSegments bloat */ class Buffer { -private: - friend class RingBuffer; +protected: ~Buffer() = default; public: virtual size_t readPosition() const = 0; virtual size_t readCompositeLength() const = 0; - virtual size_t readSegmentLength() const = 0; + virtual size_t readSegmentLength(size_t offset = 0) const = 0; virtual void readAdvance(size_t bytes) = 0; virtual uint8_t &read(size_t i = 0) = 0; @@ -29,37 +28,72 @@ public: virtual size_t writePosition() const = 0; virtual size_t writeCompositeLength() const = 0; - virtual size_t writeSegmentLength() const = 0; + virtual size_t writeSegmentLength(size_t offset = 0) const = 0; virtual void writeAdvance(size_t bytes) = 0; virtual uint8_t &write(size_t i = 0) = 0; virtual const uint8_t &write(size_t i = 0) const = 0; /* - * Sometime buffers need to grow with a little more control - * than with push and pop for more efficient calls. - * There is nothing you can do if read hasn't been filled, but at - * least write can be increased if it is demanded. - */ - /// @todo uncomment and implement in child classes - // virtual Error writeRequireLength(size_t bytes) = 0; + * Sometime buffers need to grow with a little more control + * than with push and pop for more efficient calls. + * There is nothing you can do if read hasn't been filled, but at + * least write can be increased if it is demanded. + */ + virtual Error writeRequireLength(size_t bytes) = 0; - virtual Error push(const uint8_t &value) = 0; - virtual Error push(const uint8_t &buffer, size_t size) = 0; - virtual Error pop(uint8_t &value) = 0; - virtual Error pop(uint8_t &buffer, size_t size) = 0; + Error push(const uint8_t &value); + Error push(const uint8_t &buffer, size_t size); + Error pop(uint8_t &value); + Error pop(uint8_t &buffer, size_t size); - virtual std::string toString() const = 0; - virtual std::string toHex() const = 0; + std::string toString() const; + std::string toHex() const; }; + /* -* Buffer size meant for default allocation size of the ringbuffer since -* this class currently doesn't support proper resizing -*/ + * A viewer class for buffers. + * Working on the reference buffer invalidates the buffer view + */ +class BufferView : public Buffer { +private: + Buffer &buffer; + size_t read_offset; + size_t write_offset; + +public: + BufferView(Buffer &); + + size_t readPosition() const override; + size_t readCompositeLength() const override; + size_t readSegmentLength(size_t offset = 0) const override; + void readAdvance(size_t bytes) override; + + uint8_t &read(size_t i = 0) override; + const uint8_t &read(size_t i = 0) const override; + + size_t writePosition() const override; + size_t writeCompositeLength() const override; + size_t writeSegmentLength(size_t offset = 0) const override; + void writeAdvance(size_t bytes) override; + + uint8_t &write(size_t i = 0) override; + const uint8_t &write(size_t i = 0) const override; + + Error writeRequireLength(size_t bytes) override; + + size_t readOffset() const; + size_t writeOffset() const; +}; + +/* + * Buffer size meant for default allocation size of the ringbuffer since + * this class currently doesn't support proper resizing + */ constexpr size_t RING_BUFFER_MAX_SIZE = 4096; /* -* Buffer wrapping around if read caught up -*/ + * Buffer wrapping around if read caught up + */ class RingBuffer final : public Buffer { private: std::vector buffer; @@ -78,7 +112,7 @@ public: size_t readPosition() const override; size_t readCompositeLength() const override; - size_t readSegmentLength() const override; + size_t readSegmentLength(size_t offset = 0) const override; void readAdvance(size_t bytes) override; uint8_t &read(size_t i = 0) override; @@ -86,19 +120,13 @@ public: size_t writePosition() const override; size_t writeCompositeLength() const override; - size_t writeSegmentLength() const override; + size_t writeSegmentLength(size_t offset = 0) const override; void writeAdvance(size_t bytes) override; uint8_t &write(size_t i = 0) override; const uint8_t &write(size_t i = 0) const override; - Error push(const uint8_t &value) override; - Error push(const uint8_t &buffer, size_t size) override; - Error pop(uint8_t &value) override; - Error pop(uint8_t &buffer, size_t size) override; - - std::string toString() const override; - std::string toHex() const override; + Error writeRequireLength(size_t bytes) override; }; /* @@ -116,7 +144,7 @@ public: size_t readPosition() const override; size_t readCompositeLength() const override; - size_t readSegmentLength() const override; + size_t readSegmentLength(size_t offset = 0) const override; void readAdvance(size_t bytes) override; uint8_t &read(size_t i = 0) override; @@ -124,16 +152,13 @@ public: size_t writePosition() const override; size_t writeCompositeLength() const override; - size_t writeSegmentLength() const override; + size_t writeSegmentLength(size_t offset = 0) const override; void writeAdvance(size_t bytes) override; uint8_t &write(size_t i = 0) override; const uint8_t &write(size_t i = 0) const override; - Error push(const uint8_t &value) override; - Error push(const uint8_t &buffer, size_t size) override; - Error pop(uint8_t &value) override; - Error pop(uint8_t &buffer, size_t size) override; + Error writeRequireLength(size_t bytes) override; }; class ChainArrayBuffer : public Buffer { @@ -148,7 +173,7 @@ public: size_t readPosition() const override; size_t readCompositeLength() const override; - size_t readSegmentLength() const override; + size_t readSegmentLength(size_t offset = 0) const override; void readAdvance(size_t bytes) override; uint8_t &read(size_t i = 0) override; @@ -156,15 +181,12 @@ public: size_t writePosition() const override; size_t writeCompositeLength() const override; - size_t writeSegmentLength() const override; + size_t writeSegmentLength(size_t offset = 0) const override; void writeAdvance(size_t bytes) override; uint8_t &write(size_t i = 0) override; const uint8_t &write(size_t i = 0) const override; - Error push(const uint8_t &value) override; - Error push(const uint8_t &buffer, size_t size) override; - Error pop(uint8_t &value) override; - Error pop(uint8_t &buffer, size_t size) override; + Error writeRequireLength(size_t bytes) override; }; } // namespace gin diff --git a/source/common.h b/source/kelgin/common.h similarity index 84% rename from source/common.h rename to source/kelgin/common.h index a8de312..9611db6 100644 --- a/source/common.h +++ b/source/kelgin/common.h @@ -15,6 +15,8 @@ namespace gin { classname(const classname &) = delete; \ classname &operator=(const classname &) = delete +#define GIN_ASSERT(expression) assert(expression) if (!expression) + template using Maybe = std::optional; template using Own = std::unique_ptr; @@ -23,11 +25,11 @@ template using Our = std::shared_ptr; template using Lent = std::weak_ptr; -template Own heap(Args &&... args) { - return std::make_unique(std::forward(args)...); +template Own heap(Args &&...args) { + return Own(new (std::nothrow) T(std::forward(args)...)); } -template Our share(Args &&... args) { +template Our share(Args &&...args) { return std::make_shared(std::forward(args)...); } @@ -53,4 +55,4 @@ template using UnfixVoid = typename VoidUnfix::Type; template using ReturnType = typename ReturnTypeHelper::Type; -} // namespace gin \ No newline at end of file +} // namespace gin diff --git a/source/kelgin/error.cpp b/source/kelgin/error.cpp new file mode 100644 index 0000000..e3e0c4a --- /dev/null +++ b/source/kelgin/error.cpp @@ -0,0 +1,61 @@ +#include "error.h" + +namespace gin { +Error::Error() : error_{0} {} + +Error::Error(const std::string_view &msg, int8_t code) + : error_message{msg}, error_{code} {} + +Error::Error(std::string &&msg, int8_t code) + : error_message{std::move(msg)}, error_{code} {} + +Error::Error(Error &&error) + : error_message{std::move(error.error_message)}, error_{std::move( + error.error_)} {} + +const std::string_view Error::message() const { + + return std::visit( + [this](auto &&arg) -> const std::string_view { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + return std::string_view{arg}; + } else if constexpr (std::is_same_v) { + return arg; + } else { + return "Error in class Error. Good luck :)"; + } + }, + error_message); +} + +bool Error::failed() const { return error_ != 0; } + +bool Error::isCritical() const { return error_ < 0; } + +bool Error::isRecoverable() const { return error_ > 0; } + +Error Error::copyError() const { + Error error; + error.error_ = error_; + try { + error.error_message = error_message; + } catch (const std::bad_alloc &) { + error.error_message = + std::string_view{"Error while copying Error string. Out of memory"}; + } + return error; +} + +Error criticalError(const std::string_view &generic) { + return Error{generic, -1}; +} + +Error recoverableError(const std::string_view &generic) { + return Error{generic, 1}; +} + +Error noError() { return Error{}; } + +} // namespace gin diff --git a/source/kelgin/error.h b/source/kelgin/error.h new file mode 100644 index 0000000..2d26d2c --- /dev/null +++ b/source/kelgin/error.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +#include "common.h" + +namespace gin { +/** + * Utility class for generating errors. Has a base distinction between + * critical and recoverable errors. Additional code ids can be provided to the + * constructor if additional distinctions are necessary. + */ +class Error { +private: + std::variant error_message; + int8_t error_; + +public: + Error(); + Error(const std::string_view &msg, int8_t code); + Error(std::string &&msg, int8_t code); + Error(Error &&error); + + GIN_FORBID_COPY(Error); + + Error &operator=(Error &&) = default; + + const std::string_view message() const; + bool failed() const; + + bool isCritical() const; + bool isRecoverable() const; + + Error copyError() const; +}; + +template +Error makeError(const Formatter &formatter, int8_t code, + const std::string_view &generic) { + try { + std::string error_msg = formatter(); + return Error{std::move(error_msg), code}; + } catch (std::bad_alloc &) { + return Error{generic, code}; + } +} + +Error criticalError(const std::string_view &generic); + +template +Error criticalError(const Formatter &formatter, + const std::string_view &generic) { + return makeError(formatter, -1, generic); +} + +Error recoverableError(const std::string_view &generic); + +template +Error recoverableError(const Formatter &formatter, + const std::string_view &generic) { + return makeError(formatter, -1, generic); +} + +Error noError(); + +/** + * Exception alternative. Since I code without exceptions this class is + * essentially a kind of exception replacement. + */ +template class ErrorOr; + +class ErrorOrValue { +public: + virtual ~ErrorOrValue() = default; + + template ErrorOr &as() { + return dynamic_cast &>(*this); + } + + template const ErrorOr &as() const { + return dynamic_cast &>(*this); + } +}; + +template class ErrorOr : public ErrorOrValue { +private: + std::variant, Error> value_or_error; + +public: + ErrorOr() = default; + ErrorOr(const FixVoid &value) : value_or_error{value} {} + + ErrorOr(FixVoid &&value) : value_or_error{std::move(value)} {} + + ErrorOr(const Error &error) : value_or_error{error} {} + ErrorOr(Error &&error) : value_or_error{std::move(error)} {} + + bool isValue() const { return std::holds_alternative(value_or_error); } + + bool isError() const { + return std::holds_alternative(value_or_error); + } + + Error &error() { return std::get(value_or_error); } + + const Error &error() const { return std::get(value_or_error); } + + FixVoid &value() { return std::get>(value_or_error); } + + const FixVoid &value() const { + return std::get>(value_or_error); + } +}; + +} // namespace gin diff --git a/source/io.cpp b/source/kelgin/io.cpp similarity index 100% rename from source/io.cpp rename to source/kelgin/io.cpp diff --git a/source/io.h b/source/kelgin/io.h similarity index 76% rename from source/io.h rename to source/kelgin/io.h index ffd0425..ee3dbff 100644 --- a/source/io.h +++ b/source/kelgin/io.h @@ -66,7 +66,7 @@ public: * Listen on this address */ virtual Own listen() = 0; - virtual Own connect() = 0; + virtual Conveyor> connect() = 0; virtual std::string toString() const = 0; }; @@ -75,8 +75,8 @@ class Network { public: virtual ~Network() = default; - virtual Own parseAddress(const std::string &, - uint16_t port_hint = 0) = 0; + virtual Conveyor> + parseAddress(const std::string &addr, uint16_t port_hint = 0) = 0; }; class AsyncIoProvider { @@ -88,26 +88,11 @@ public: virtual Network &network() = 0; }; -/* -* Future of io context structure ? -* -struct AsyncIoContext { - EventLoop event_loop; - EventPort& event_port; - WaitScope wait_scope; - Own io_provider; - Network& network; -}; -*/ - struct AsyncIoContext { Own io; + EventLoop &event_loop; EventPort &event_port; - WaitScope &wait_scope; }; -/* - * Setup a default Context with an active waitscope - */ -AsyncIoContext setupAsyncIo(); -} // namespace gin \ No newline at end of file +ErrorOr setupAsyncIo(); +} // namespace gin diff --git a/source/io_helpers.cpp b/source/kelgin/io_helpers.cpp similarity index 100% rename from source/io_helpers.cpp rename to source/kelgin/io_helpers.cpp diff --git a/source/io_helpers.h b/source/kelgin/io_helpers.h similarity index 100% rename from source/io_helpers.h rename to source/kelgin/io_helpers.h diff --git a/source/kelgin/json.h b/source/kelgin/json.h new file mode 100644 index 0000000..189a491 --- /dev/null +++ b/source/kelgin/json.h @@ -0,0 +1,1094 @@ +#pragma once + +#include "buffer.h" +#include "message.h" +#include "message_dynamic.h" + +#include "error.h" + +#include +#include +#include +#include +#include + +#include + +namespace gin { +class JsonCodec { +public: + struct Limits { + size_t depth; + size_t elements; + + Limits() : depth{16}, elements{1024} {} + Limits(size_t d, size_t e) : depth{d}, elements{e} {} + }; + +private: + bool isWhitespace(int8_t letter); + + void skipWhitespace(Buffer &buffer); + + Error decodeBool(DynamicMessageBool::Builder message, Buffer &buffer); + + // Not yet clear if double or integer + Error decodeNumber(Own &message, Buffer &buffer); + + Error decodeNull(Buffer &buffer); + + Error decodeValue(Own &message, Buffer &buffer, + const Limits &limits, Limits &counter); + + Error decodeRawString(std::string &raw, Buffer &buffer); + + Error decodeList(DynamicMessageList::Builder builder, Buffer &buffer, + const Limits &limits, Limits &counter); + + Error decodeStruct(DynamicMessageStruct::Builder message, Buffer &buffer, + const Limits &limits, Limits &counter); + + ErrorOr> decodeDynamic(Buffer &buffer, + const Limits &limits); + +public: + template + Error encode(typename T::Reader reader, Buffer &buffer); + + template + Error decode(typename T::Builder builder, Buffer &buffer, + const Limits &limits = Limits{}); +}; + +template struct JsonEncodeImpl; + +template struct JsonEncodeImpl> { + static Error encode(typename MessagePrimitive::Reader data, + Buffer &buffer) { + std::string stringified = std::to_string(data.get()); + Error error = + buffer.push(*reinterpret_cast(stringified.data()), + stringified.size()); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template <> struct JsonEncodeImpl> { + static Error encode(typename MessagePrimitive::Reader data, + Buffer &buffer) { + std::string str = + std::string{"\""} + std::string{data.get()} + std::string{"\""}; + Error error = buffer.push( + *reinterpret_cast(str.data()), str.size()); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template <> struct JsonEncodeImpl> { + static Error encode(typename MessagePrimitive::Reader data, + Buffer &buffer) { + std::string str = data.get() ? "true" : "false"; + Error error = buffer.push( + *reinterpret_cast(str.data()), str.size()); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template struct JsonEncodeImpl> { + template + static typename std::enable_if::type + encodeMembers(typename MessageList::Reader data, Buffer &buffer) { + (void)data; + (void)buffer; + return noError(); + } + template + static typename std::enable_if < + i::type + encodeMembers(typename MessageList::Reader data, Buffer &buffer) { + if (data.template get().isSetExplicitly()) { + { + Error error = + JsonEncodeImpl::type>:: + encode(data.template get(), buffer); + if (error.failed()) { + return error; + } + } + } else { + { + std::string_view str = "null"; + Error error = buffer.push( + *reinterpret_cast(str.data()), str.size()); + if (error.failed()) { + return error; + } + } + } + if constexpr ((i + 1u) < sizeof...(T)) { + Error error = buffer.push(','); + if (error.failed()) { + return error; + } + } + { + Error error = + JsonEncodeImpl>::encodeMembers(data, + buffer); + if (error.failed()) { + return error; + } + } + return noError(); + } + + static Error encode(typename MessageList::Reader data, + Buffer &buffer) { + Error error = buffer.push('['); + if (error.failed()) { + return error; + } + error = + JsonEncodeImpl>::encodeMembers<0>(data, buffer); + if (error.failed()) { + return error; + } + error = buffer.push(']'); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template +struct JsonEncodeImpl...>> { + template + static typename std::enable_if::type + encodeMembers( + typename MessageStruct...>::Reader data, + Buffer &buffer) { + (void)data; + (void)buffer; + return Error{}; + } + template + static typename std::enable_if < + i::type encodeMembers( + typename MessageStruct...>::Reader data, + Buffer &buffer) { + { + Error error = buffer.push('\"'); + if (error.failed()) { + return error; + } + std::string_view view = ParameterPackType::type::view(); + error = buffer.push(*reinterpret_cast(view.data()), + view.size()); + if (error.failed()) { + return error; + } + error = buffer.push('\"'); + if (error.failed()) { + return error; + } + error = buffer.push(':'); + if (error.failed()) { + return error; + } + } + if (data.template get().isSetExplicitly()) { + Error error = + JsonEncodeImpl::type>:: + encode(data.template get(), buffer); + if (error.failed()) { + return error; + } + } else { + std::string_view str = "null"; + Error error = buffer.push( + *reinterpret_cast(str.data()), str.size()); + if (error.failed()) { + return error; + } + } + if constexpr ((i + 1u) < sizeof...(V)) { + Error error = buffer.push(','); + if (error.failed()) { + return error; + } + } + { + Error error = + JsonEncodeImpl...>>:: + encodeMembers(data, buffer); + if (error.failed()) { + return error; + } + } + return noError(); + } + + static Error + encode(typename MessageStruct...>::Reader data, + Buffer &buffer) { + Error error = buffer.push('{'); + if (error.failed()) { + return error; + } + error = JsonEncodeImpl...>>:: + encodeMembers<0>(data, buffer); + if (error.failed()) { + return error; + } + error = buffer.push('}'); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template +struct JsonEncodeImpl...>> { + template + static typename std::enable_if::type encodeMember( + typename MessageUnion...>::Reader data, + Buffer &buffer) { + (void)data; + (void)buffer; + return noError(); + } + template + static typename std::enable_if < + i::type encodeMember( + typename MessageUnion...>::Reader reader, + Buffer &buffer) { + /// @todo only encode if alternative is set, skip in other cases + /// use holds_alternative + + if (reader.template holdsAlternative< + typename ParameterPackType::type>()) { + { + Error error = buffer.push('{'); + if (error.failed()) { + return error; + } + } + { + Error error = buffer.push('\"'); + if (error.failed()) { + return error; + } + std::string_view view = + ParameterPackType::type::view(); + error = + buffer.push(*reinterpret_cast(view.data()), + view.size()); + if (error.failed()) { + return error; + } + error = buffer.push('\"'); + if (error.failed()) { + return error; + } + error = buffer.push(':'); + if (error.failed()) { + return error; + } + } + + Error error = + JsonEncodeImpl::type>:: + encode(reader.template get(), buffer); + if (error.failed()) { + return error; + } + { + Error error = buffer.push('}'); + if (error.failed()) { + return error; + } + } + return noError(); + } + + Error error = + JsonEncodeImpl...>>:: + encodeMember(reader, buffer); + if (error.failed()) { + return error; + } + + return noError(); + } + + static Error + encode(typename MessageUnion...>::Reader reader, + Buffer &buffer) { + return encodeMember<0>(reader, buffer); + } +}; + +/* + * For JSON decoding we need a dynamic layer where we can query information from + */ +template struct JsonDecodeImpl; + +template struct JsonDecodeImpl> { + // static void decode(BufferView view, typename + // MessagePrimitive::Builder){} + static Error decode(typename MessagePrimitive::Builder, + DynamicMessage::DynamicReader) { + + // This is also a valid null implementation :) + return noError(); + } +}; +template <> struct JsonDecodeImpl> { + // static void decode(BufferView view, typename + // MessagePrimitive::Builder){} + static Error decode(typename MessagePrimitive::Builder data, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::Bool) { + return criticalError("Not a boolean"); + } + DynamicMessageBool::Reader b_reader = reader.as(); + data.set(b_reader.get()); + return noError(); + } +}; +template <> struct JsonDecodeImpl> { + // static void decode(BufferView view, typename + // MessagePrimitive::Builder){} + static Error decode(typename MessagePrimitive::Builder data, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::Signed) { + return criticalError("Not an integer"); + } + DynamicMessageSigned::Reader s_reader = + reader.as(); + data.set(s_reader.get()); + return noError(); + } +}; +template <> struct JsonDecodeImpl> { + // static void decode(BufferView view, typename + // MessagePrimitive::Builder){} + static Error decode(typename MessagePrimitive::Builder builder, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::Signed) { + return criticalError("Not an integer"); + } + DynamicMessageSigned::Reader s_reader = + reader.as(); + int64_t val = s_reader.get(); + if (val < 0) { + return criticalError("Not an unsigned integer"); + } + builder.set(static_cast(val)); + return noError(); + } +}; +template <> struct JsonDecodeImpl> { + // static void decode(BufferView view, typename + // MessagePrimitive::Builder){} + static Error decode(typename MessagePrimitive::Builder data, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::Signed) { + return criticalError("Not an integer"); + } + DynamicMessageSigned::Reader s_reader = + reader.as(); + int64_t val = s_reader.get(); + data.set(static_cast(val)); + return noError(); + } +}; + +template <> struct JsonDecodeImpl> { + static Error decode(typename MessagePrimitive::Builder builder, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::String) { + return criticalError("Not a string"); + } + DynamicMessageString::Reader s_reader = + reader.as(); + builder.set(s_reader.get()); + + return noError(); + } +}; + +template struct JsonDecodeImpl> { + template + static typename std::enable_if::type + decodeMembers(typename MessageList::Builder builder, + DynamicMessageList::Reader reader) { + (void)builder; + (void)reader; + return noError(); + } + + template + static typename std::enable_if < + i::type + decodeMembers(typename MessageList::Builder builder, + DynamicMessageList::Reader reader) { + + DynamicMessage::DynamicReader member_reader = reader.get(i); + + { + Error error = + JsonDecodeImpl::type>:: + decode(builder.template init(), member_reader); + + if (error.failed()) { + return error; + } + } + { + Error error = + JsonDecodeImpl>::decodeMembers(builder, + reader); + if (error.failed()) { + return error; + } + } + return noError(); + } + + static Error decode(typename MessageList::Builder builder, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::List) { + return criticalError("Not a list"); + } + + Error error = JsonDecodeImpl>::decodeMembers<0>( + builder, reader.as()); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +template +struct JsonDecodeImpl...>> { + template + static typename std::enable_if::type + decodeMembers(typename MessageStruct...>::Builder, + DynamicMessageStruct::Reader) { + return noError(); + } + template + static typename std::enable_if < + i::type decodeMembers( + typename MessageStruct...>::Builder + builder, + DynamicMessageStruct::Reader reader) { + DynamicMessage::DynamicReader member_reader = + reader.get(ParameterPackType::type::view()); + { + Error error = + JsonDecodeImpl::type>:: + decode(builder.template init(), member_reader); + if (error.failed()) { + return error; + } + } + { + Error error = + JsonDecodeImpl...>>:: + decodeMembers(builder, reader); + if (error.failed()) { + return error; + } + } + return noError(); + } + static Error decode( + typename MessageStruct...>::Builder builder, + DynamicMessage::DynamicReader reader) { + if (reader.type() != DynamicMessage::Type::Struct) { + return criticalError("Not a struct"); + } + Error error = + JsonDecodeImpl...>>:: + decodeMembers<0>(builder, reader.as()); + if (error.failed()) { + return error; + } + return noError(); + } +}; + +bool JsonCodec::isWhitespace(int8_t letter) { + return letter == '\t' || letter == ' ' || letter == '\r' || letter == '\n'; +} + +void JsonCodec::skipWhitespace(Buffer &buffer) { + while (buffer.readCompositeLength() > 0 && isWhitespace(buffer.read())) { + buffer.readAdvance(1); + } +} + +struct JsonCodecLimitGuardHelper { + JsonCodec::Limits &counter; + + JsonCodecLimitGuardHelper(JsonCodec::Limits &l) : counter{l} { + ++counter.depth; + ++counter.elements; + } + + ~JsonCodecLimitGuardHelper() { --counter.depth; } + + static bool inLimit(const JsonCodec::Limits &counter, + const JsonCodec::Limits &top) { + return counter.depth < top.depth && counter.elements < top.elements; + } +}; + +Error JsonCodec::decodeBool(DynamicMessageBool::Builder message, + Buffer &buffer) { + assert((buffer.read() == 'T') || (buffer.read() == 't') || + (buffer.read() == 'F') || (buffer.read() == 'f')); + + bool is_true = buffer.read() == 'T' || buffer.read() == 't'; + buffer.readAdvance(1); + + if (is_true) { + std::array check = {'r', 'u', 'e'}; + for (size_t i = 0; buffer.readCompositeLength() > 0 && i < 3; ++i) { + if (buffer.read() != check[i]) { + return criticalError("Assumed true value, but it is invalid"); + } + buffer.readAdvance(1); + } + } else { + std::array check = {'a', 'l', 's', 'e'}; + for (size_t i = 0; buffer.readCompositeLength() > 0 && i < 4; ++i) { + if (buffer.read() != check[i]) { + return criticalError("Assumed false value, but it is invalid"); + } + buffer.readAdvance(1); + } + } + + message.set(is_true); + + return noError(); +} + +// Not yet clear if double or integer +Error JsonCodec::decodeNumber(Own &message, Buffer &buffer) { + assert((buffer.read() >= '0' && buffer.read() <= '9') || + buffer.read() == '+' || buffer.read() == '-'); + size_t offset = 0; + + if (buffer.read() == '-') { + ++offset; + } else if (buffer.read() == '+') { + return criticalError("Not a valid number with +"); + } + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + bool integer = true; + if (buffer.read(offset) >= '1' && buffer.read(offset) <= '9') { + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + + while (1) { + if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + continue; + } + break; + } + } else if (buffer.read(offset) == '0') { + ++offset; + } else { + return criticalError("Not a JSON number"); + } + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + if (buffer.read(offset) == '.') { + integer = false; + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + + size_t partial_start = offset; + + while (1) { + if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + continue; + } + break; + } + + if (offset == partial_start) { + return criticalError("No numbers after '.'"); + } + } + if (buffer.read(offset) == 'e' || buffer.read(offset) == 'E') { + integer = false; + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + + if (buffer.read(offset) == '+' || buffer.read(offset) == '-') { + ++offset; + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + } + + size_t exp_start = offset; + + while (1) { + if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') { + ++offset; + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + continue; + } + break; + } + if (offset == exp_start) { + return criticalError("No numbers after exponent token"); + } + } + + if (offset >= buffer.readCompositeLength()) { + return recoverableError("Buffer too short"); + } + + std::string_view number_view{reinterpret_cast(&buffer.read()), + offset}; + if (integer) { + int64_t result; + auto fc_result = + std::from_chars(number_view.data(), + number_view.data() + number_view.size(), result); + if (fc_result.ec != std::errc{}) { + return criticalError("Not an integer"); + } + + // + auto int_msg = std::make_unique(); + DynamicMessageSigned::Builder builder{*int_msg}; + builder.set(result); + message = std::move(int_msg); + } else { + std::string double_hack{number_view}; + double result; + // This is hacky because technically c++17 allows noexcept from_chars + // doubles, but clang++ and g++ don't implement it since that is + // apparently hard. + try { + result = std::stod(double_hack); + } catch (const std::exception &) { + return criticalError("Not a double"); + } + + /* + auto fc_result = + std::from_chars(number_view.data(), + number_view.data() + number_view.size(), result); + if (fc_result.ec != std::errc{}) { + return criticalError("Not a double"); + } + */ + + // + auto dbl_msg = std::make_unique(); + DynamicMessageDouble::Builder builder{*dbl_msg}; + builder.set(result); + message = std::move(dbl_msg); + } + + buffer.readAdvance(offset); + skipWhitespace(buffer); + return noError(); +} + +Error JsonCodec::decodeNull(Buffer &buffer) { + assert(buffer.read() == 'N' || buffer.read() == 'n'); + + buffer.readAdvance(1); + + std::array check = {'u', 'l', 'l'}; + for (size_t i = 0; buffer.readCompositeLength() > 0 && i < 3; ++i) { + if (buffer.read() != check[i]) { + return criticalError("Assumed null value, but it is invalid"); + } + buffer.readAdvance(1); + } + + return noError(); +} + +Error JsonCodec::decodeValue(Own &message, Buffer &buffer, + const Limits &limits, Limits &counter) { + skipWhitespace(buffer); + + JsonCodecLimitGuardHelper ctr_helper{counter}; + + if (!JsonCodecLimitGuardHelper::inLimit(counter, limits)) { + return criticalError("Not in limit"); + } + + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + + switch (buffer.read()) { + case '"': { + std::string str; + Error error = decodeRawString(str, buffer); + if (error.failed()) { + return error; + } + Own msg_string = + std::make_unique(); + DynamicMessageString::Builder builder{*msg_string}; + builder.set(std::move(str)); + message = std::move(msg_string); + } break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': { + Error error = decodeNumber(message, buffer); + if (error.failed()) { + return error; + } + } break; + case 't': + case 'T': + case 'f': + case 'F': { + Own msg_bool = + std::make_unique(); + decodeBool(DynamicMessageBool::Builder{*msg_bool}, buffer); + message = std::move(msg_bool); + } break; + case '{': { + Own msg_struct = + std::make_unique(); + Error error = decodeStruct(DynamicMessageStruct::Builder{*msg_struct}, + buffer, limits, counter); + if (error.failed()) { + return error; + } + message = std::move(msg_struct); + } break; + case '[': { + Own msg_list = + std::make_unique(); + decodeList(DynamicMessageList::Builder{*msg_list}, buffer, limits, + counter); + message = std::move(msg_list); + } break; + case 'n': + case 'N': { + Own msg_null = + std::make_unique(); + decodeNull(buffer); + message = std::move(msg_null); + } break; + default: { + return criticalError("Cannot identify next JSON value"); + } + } + + skipWhitespace(buffer); + return noError(); +} + +Error JsonCodec::decodeRawString(std::string &raw, Buffer &buffer) { + assert(buffer.read() == '"'); + + buffer.readAdvance(1); + std::stringstream iss; + bool string_done = false; + while (!string_done) { + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + switch (buffer.read()) { + case '\\': + buffer.readAdvance(1); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + switch (buffer.read()) { + case '\\': + case '/': + case '"': + iss << buffer.read(); + break; + case 'b': + iss << '\b'; + break; + case 'f': + iss << '\f'; + break; + case 'n': + iss << '\n'; + break; + case 'r': + iss << '\r'; + break; + case 't': + iss << '\t'; + break; + case 'u': { + buffer.readAdvance(1); + if (buffer.readCompositeLength() < 4) { + return recoverableError("Broken unicode or short buffer"); + } + /// @todo correct unicode handling + iss << '?'; // dummy line + iss << '?'; + iss << '?'; + iss << '?'; + // There is always a skip at the end so here we skip 3 + // instead of 4 bytes + buffer.readAdvance(3); + } break; + } + break; + case '"': + string_done = true; + break; + default: + iss << buffer.read(); + break; + } + buffer.readAdvance(1); + } + raw = iss.str(); + return noError(); +} + +Error JsonCodec::decodeList(DynamicMessageList::Builder builder, Buffer &buffer, + const Limits &limits, Limits &counter) { + assert(buffer.read() == '['); + buffer.readAdvance(1); + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + + while (buffer.read() != ']') { + + Own message = nullptr; + { + Error error = decodeValue(message, buffer, limits, counter); + if (error.failed()) { + return error; + } + } + builder.push(std::move(message)); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + + switch (buffer.read()) { + case ']': + break; + case ',': + buffer.readAdvance(1); + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + break; + default: + return criticalError("Not a JSON Object"); + } + } + buffer.readAdvance(1); + return noError(); +} + +Error JsonCodec::decodeStruct(DynamicMessageStruct::Builder message, + Buffer &buffer, const Limits &limits, + Limits &counter) { + assert(buffer.read() == '{'); + buffer.readAdvance(1); + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + + while (buffer.read() != '}') { + if (buffer.read() == '"') { + std::string key_string; + { + Error error = decodeRawString(key_string, buffer); + if (error.failed()) { + return error; + } + } + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + if (buffer.read() != ':') { + return criticalError("Expecting a ':' token"); + } + buffer.readAdvance(1); + Own msg = nullptr; + { + Error error = decodeValue(msg, buffer, limits, counter); + if (error.failed()) { + return error; + } + } + message.init(key_string, std::move(msg)); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + + switch (buffer.read()) { + case '}': + break; + case ',': + buffer.readAdvance(1); + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + break; + default: + return criticalError("Not a JSON Object"); + } + } else { + return criticalError("Not a JSON Object"); + } + } + buffer.readAdvance(1); + return noError(); +} + +ErrorOr> JsonCodec::decodeDynamic(Buffer &buffer, + const Limits &limits) { + Limits counter{0, 0}; + + skipWhitespace(buffer); + if (buffer.readCompositeLength() == 0) { + return recoverableError("Buffer too short"); + } + if (buffer.read() == '{') { + + Own message = + std::make_unique(); + Error error = decodeStruct(DynamicMessageStruct::Builder{*message}, + buffer, limits, counter); + if (error.failed()) { + return error; + } + skipWhitespace(buffer); + + return Own{std::move(message)}; + } else if (buffer.read() == '[') { + + Own message = + std::make_unique(); + Error error = decodeList(*message, buffer, limits, counter); + if (error.failed()) { + return error; + } + skipWhitespace(buffer); + + return Own{std::move(message)}; + } else { + return criticalError("Not a JSON Object"); + } +} + +template +Error JsonCodec::encode(typename T::Reader reader, Buffer &buffer) { + BufferView view{buffer}; + Error error = JsonEncodeImpl::encode(reader, view); + if (error.failed()) { + return error; + } + + buffer.writeAdvance(view.writeOffset()); + + return error; +} + +template +Error JsonCodec::decode(typename T::Builder builder, Buffer &buffer, + const Limits &limits) { + + BufferView view{buffer}; + + ErrorOr> error_or_message = decodeDynamic(view, limits); + if (error_or_message.isError()) { + return std::move(error_or_message.error()); + } + + Own message = std::move(error_or_message.value()); + if (!message) { + return criticalError("No message object created"); + } + if (message->type() == DynamicMessage::Type::Null) { + return criticalError("Can't decode to json"); + } + + DynamicMessage::DynamicReader reader{*message}; + Error static_error = JsonDecodeImpl::decode(builder, reader); + + if (static_error.failed()) { + return static_error; + } + + buffer.readAdvance(view.readOffset()); + + return static_error; +} + +} // namespace gin diff --git a/source/log.cpp b/source/kelgin/log.cpp similarity index 100% rename from source/log.cpp rename to source/kelgin/log.cpp diff --git a/source/log.h b/source/kelgin/log.h similarity index 100% rename from source/log.h rename to source/kelgin/log.h diff --git a/source/message.h b/source/kelgin/message.h similarity index 89% rename from source/message.h rename to source/kelgin/message.h index f4b20e7..dbf9ca0 100644 --- a/source/message.h +++ b/source/kelgin/message.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "common.h" @@ -67,6 +68,7 @@ public: Builder asBuilder() { return Builder{message}; } }; }; + template <> class MessagePrimitive : public Message { private: std::string value; @@ -159,9 +161,56 @@ public: bool isSetExplicitly() const { return message.set_explicitly; } - Builder asBuilder() { return Reader{message}; } + Builder asBuilder() { return Builder{message}; } }; }; + +/// @todo how to do initialization? +template class MessageArray : public Message { +private: + using array_type = std::vector; + array_type elements; + friend class Builder; + friend class Reader; + +public: + class Reader; + class Builder { + private: + MessageArray &message; + + public: + Builder(MessageArray &message) : message{message} { + message.set_explicitly = true; + } + + constexpr typename T::Builder init(size_t i) { + T &msg_ref = message.elements.at(i); + return typename T::Builder{msg_ref}; + } + + Reader asReader() { return Reader{message}; } + }; + + class Reader { + private: + MessageArray &message; + + public: + Reader(MessageArray &message) : message{message} {} + + constexpr typename T::Reader get(size_t i) { + return message.elements.at(i); + } + + size_t size() const { return elements.size(); } + + bool isSetExplicitly() const { return message.set_explicitly; } + + Builder asBuilder() { return Builder{message}; } + }; +}; + template struct MessageStructMember; template @@ -279,6 +328,8 @@ public: constexpr size_t size() { return std::tuple_size::value; } + bool isSetExplicitly() const { return message.set_explicitly; } + Builder asBuilder() { return Builder{message}; } }; }; @@ -376,6 +427,8 @@ public: size_t index() const { return message.values.index(); } + bool isSetExplicitly() const { return message.set_explicitly; } + constexpr size_t size() { return std::variant_size::value; } Builder asBuilder() { return Builder{message}; } diff --git a/source/message_dynamic.cpp b/source/kelgin/message_dynamic.cpp similarity index 100% rename from source/message_dynamic.cpp rename to source/kelgin/message_dynamic.cpp diff --git a/source/message_dynamic.h b/source/kelgin/message_dynamic.h similarity index 97% rename from source/message_dynamic.h rename to source/kelgin/message_dynamic.h index f8f49b2..a0c6065 100644 --- a/source/message_dynamic.h +++ b/source/kelgin/message_dynamic.h @@ -239,6 +239,12 @@ public: return builder; } + DynamicMessage::DynamicBuilder push(Own &&msg) { + DynamicMessage::DynamicBuilder builder{*msg}; + message.messages.push_back(std::move(msg)); + return builder; + } + Reader asReader() const { return Reader{message}; } }; diff --git a/source/proto_kel.h b/source/kelgin/proto_kel.h similarity index 82% rename from source/proto_kel.h rename to source/kelgin/proto_kel.h index 45a9157..e940a02 100644 --- a/source/proto_kel.h +++ b/source/kelgin/proto_kel.h @@ -15,6 +15,41 @@ namespace gin { using msg_union_id_t = uint32_t; using msg_packet_length_t = uint64_t; +class ProtoKelCodec { +private: + struct ReadContext { + Buffer &buffer; + size_t offset = 0; + }; + + template friend struct ProtoKelEncodeImpl; + + template friend struct ProtoKelDecodeImpl; + +public: + struct Limits { + msg_packet_length_t packet_size; + + Limits() : packet_size{4096} {} + Limits(msg_packet_length_t ps) : packet_size{ps} {} + }; + + struct Version { + size_t major; + size_t minor; + size_t security; + }; + + const Version version() const { return Version{0, 0, 0}; } + + template + Error encode(typename T::Reader reader, Buffer &buffer); + + template + Error decode(typename T::Builder builder, Buffer &buffer, + const Limits &limits = Limits{}); +}; + template struct ProtoKelEncodeImpl; template struct ProtoKelEncodeImpl> { @@ -34,10 +69,13 @@ template <> struct ProtoKelEncodeImpl> { Buffer &buffer) { std::string_view view = data.get(); size_t size = view.size(); - if ((sizeof(size) + size) > buffer.writeCompositeLength()) { - return recoverableError("Buffer too small"); + + Error error = buffer.writeRequireLength(sizeof(size) + size); + if (error.failed()) { + return error; } - Error error = StreamValue::encode(size, buffer); + + error = StreamValue::encode(size, buffer); if (error.failed()) { return error; } @@ -81,7 +119,7 @@ template struct ProtoKelEncodeImpl> { template static typename std::enable_if::type - sizeMembers(typename MessageList::Reader data) { + sizeMembers(typename MessageList::Reader) { return 0; } @@ -357,67 +395,70 @@ struct ProtoKelDecodeImpl...>> { } }; -class ProtoKelCodec { -public: - struct Version { - size_t major; - size_t minor; - size_t security; - }; +template +Error ProtoKelCodec::encode(typename T::Reader reader, Buffer &buffer) { + BufferView view{buffer}; - const Version version() const { return Version{0, 0, 0}; } + msg_packet_length_t packet_length = ProtoKelEncodeImpl::size(reader); + // Check the size of the packet for the first + // message length description - template - Error encode(typename T::Reader reader, Buffer &buffer) { - msg_packet_length_t packet_length = ProtoKelEncodeImpl::size(reader); - // Check the size of the packet for the first - // message length description - - if (buffer.writeCompositeLength() < - (packet_length + sizeof(msg_packet_length_t))) { - return recoverableError("Buffer too small"); - } - { - Error error = - StreamValue::encode(packet_length, buffer); - if (error.failed()) { - return error; - } - } - { - Error error = ProtoKelEncodeImpl::encode(reader, buffer); - if (error.failed()) { - return error; - } - } - - return noError(); - }; - - template - Error decode(typename T::Builder builder, Buffer &buffer) { - msg_packet_length_t packet_length = 0; - { - Error error = - StreamValue::decode(packet_length, buffer); - if (error.failed()) { - return error; - } - } - { - Error error = ProtoKelDecodeImpl::decode(builder, buffer); - if (error.failed()) { - return error; - } - } - { - if (ProtoKelEncodeImpl::size(builder.asReader()) != - packet_length) { - return criticalError("Bad packet format"); - } - } - return noError(); + Error error = + view.writeRequireLength(packet_length + sizeof(msg_packet_length_t)); + if (error.failed()) { + return error; } -}; + + { + Error error = + StreamValue::encode(packet_length, view); + if (error.failed()) { + return error; + } + } + { + Error error = ProtoKelEncodeImpl::encode(reader, view); + if (error.failed()) { + return error; + } + } + + buffer.writeAdvance(view.writeOffset()); + return noError(); +} + +template +Error ProtoKelCodec::decode(typename T::Builder builder, Buffer &buffer, + const Limits &limits) { + BufferView view{buffer}; + + msg_packet_length_t packet_length = 0; + { + Error error = + StreamValue::decode(packet_length, view); + if (error.failed()) { + return error; + } + } + + if (packet_length > limits.packet_size) { + return criticalError("Packet size too big"); + } + + { + Error error = ProtoKelDecodeImpl::decode(builder, view); + if (error.failed()) { + return error; + } + } + { + if (ProtoKelEncodeImpl::size(builder.asReader()) != packet_length) { + return criticalError("Bad packet format"); + } + } + + buffer.readAdvance(view.readOffset()); + return noError(); +} } // namespace gin \ No newline at end of file diff --git a/source/stream_endian.h b/source/kelgin/stream_endian.h similarity index 77% rename from source/stream_endian.h rename to source/kelgin/stream_endian.h index c39090b..9137fd7 100644 --- a/source/stream_endian.h +++ b/source/kelgin/stream_endian.h @@ -7,17 +7,23 @@ #include namespace gin { +/** + * Helper class to encode/decode any primtive type into/from litte endian. + * The shift class does this by shifting bytes. This type of procedure is + * platform independent. So it does not matter if the memory layout is + * little endian or big endian + */ template class ShiftStreamValue; template class ShiftStreamValue { public: inline static Error decode(T &val, Buffer &buffer) { - uint8_t& raw = reinterpret_cast(val); + uint8_t &raw = reinterpret_cast(val); return buffer.pop(raw, sizeof(T)); } inline static Error encode(const T &val, Buffer &buffer) { - uint8_t& raw = reinterpret_cast(val); + const uint8_t &raw = reinterpret_cast(val); return buffer.push(raw, sizeof(T)); } @@ -30,26 +36,24 @@ public: if (buffer.readCompositeLength() < sizeof(T)) { return recoverableError("Buffer too small"); } - - uint16_t& raw = reinterpret_cast(val); - uint8_t buf[sizeof(T)] = 0; - Error error = buffer.pop(buf, sizeof(T)); - if(error.failed()){ - return error; - } + uint16_t raw = 0; for (size_t i = 0; i < sizeof(T); ++i) { - raw |= buf[i] << (i * 8); + raw |= buffer.read(i) << (i * 8); } + memcpy(&val, &raw, sizeof(T)); + buffer.readAdvance(sizeof(T)); return noError(); } inline static Error encode(const T &val, Buffer &buffer) { - if (buffer.writeCompositeLength() < sizeof(T)) { - return recoverableError("Buffer too small"); + Error error = buffer.writeRequireLength(sizeof(T)); + if (error.failed()) { + return error; } + uint16_t raw; memcpy(&raw, &val, sizeof(T)); @@ -83,9 +87,11 @@ public: } inline static Error encode(const T &val, Buffer &buffer) { - if (buffer.writeCompositeLength() < sizeof(T)) { - return recoverableError("Buffer too small"); + Error error = buffer.writeRequireLength(sizeof(T)); + if (error.failed()) { + return error; } + uint32_t raw; memcpy(&raw, &val, sizeof(T)); @@ -119,9 +125,11 @@ public: } inline static Error encode(const T &val, Buffer &buffer) { - if (buffer.writeCompositeLength() < sizeof(T)) { - return recoverableError("Buffer too small"); + Error error = buffer.writeRequireLength(sizeof(T)); + if (error.failed()) { + return error; } + uint64_t raw; memcpy(&raw, &val, sizeof(T)); diff --git a/source/string_literal.h b/source/kelgin/string_literal.h similarity index 70% rename from source/string_literal.h rename to source/kelgin/string_literal.h index fcb2c2b..b19cb54 100644 --- a/source/string_literal.h +++ b/source/kelgin/string_literal.h @@ -4,6 +4,11 @@ #include namespace gin { +/** + * Helper object which creates a templated string from the provided string + * literal. It guarantees compile time uniqueness and thus allows using strings + * in template parameters. + */ template class StringLiteral { public: static constexpr std::array data = {Chars..., diff --git a/source/timer.h b/source/kelgin/timer.h similarity index 100% rename from source/timer.h rename to source/kelgin/timer.h diff --git a/source/tls.cpp b/source/kelgin/tls.cpp similarity index 100% rename from source/tls.cpp rename to source/kelgin/tls.cpp diff --git a/source/tls.h b/source/kelgin/tls.h similarity index 61% rename from source/tls.h rename to source/kelgin/tls.h index b947888..85cf06f 100644 --- a/source/tls.h +++ b/source/kelgin/tls.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace gin { class TlsContext { @@ -17,15 +18,13 @@ private: public: TlsContext(); ~TlsContext(); - - }; -class TlsNetwork : public Network { +class TlsNetwork final : public Network { private: public: - Own parseAddress(const std::string &, - uint16_t port_hint = 0) override; + Conveyor> parseAddress(const std::string &, + uint16_t port_hint = 0) override; }; } // namespace gin diff --git a/test/async.cpp b/test/async.cpp index 3faf985..427bba4 100644 --- a/test/async.cpp +++ b/test/async.cpp @@ -1,6 +1,6 @@ #include "suite/suite.h" -#include "source/async.h" +#include "source/kelgin/async.h" namespace { GIN_TEST("Async Immediate"){ @@ -19,7 +19,7 @@ GIN_TEST("Async Immediate"){ ErrorOr error_or_number = is_number.take(); - GIN_EXPECT(!error_or_number.isError(), "Return is an error: " + error_or_number.error().message()); + GIN_EXPECT(!error_or_number.isError(), error_or_number.error().message()); GIN_EXPECT(error_or_number.isValue(), "Return is not a value"); GIN_EXPECT(error_or_number.value(), "Value is not 5"); } @@ -36,7 +36,7 @@ GIN_TEST("Async Adapt"){ ErrorOr foo = feeder_conveyor.conveyor.take(); - GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message()); + GIN_EXPECT(!foo.isError(), foo.error().message()); GIN_EXPECT(foo.isValue(), "Return is not a value"); GIN_EXPECT(foo.value() == 5, "Values not 5, but " + std::to_string(foo.value())); } @@ -54,7 +54,7 @@ GIN_TEST("Async Adapt Multiple"){ ErrorOr foo = feeder_conveyor.conveyor.take(); - GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message()); + GIN_EXPECT(!foo.isError(), foo.error().message()); GIN_EXPECT(foo.isValue(), "Return is not a value"); GIN_EXPECT(foo.value() == 5, "Values not 5, but " + std::to_string(foo.value())); @@ -62,7 +62,7 @@ GIN_TEST("Async Adapt Multiple"){ ErrorOr bar = feeder_conveyor.conveyor.take(); - GIN_EXPECT(!bar.isError(), "Return is an error: " + bar.error().message()); + GIN_EXPECT(!foo.isError(), bar.error().message()); GIN_EXPECT(bar.isValue(), "Return is not a value"); GIN_EXPECT(bar.value() == 10, "Values not 10, but " + std::to_string(bar.value())); @@ -72,11 +72,11 @@ GIN_TEST("Async Adapt Multiple"){ ErrorOr a = feeder_conveyor.conveyor.take(); ErrorOr b = feeder_conveyor.conveyor.take(); - GIN_EXPECT(!a.isError(), "Return is an error: " + a.error().message()); + GIN_EXPECT(!foo.isError(), a.error().message()); GIN_EXPECT(a.isValue(), "Return is not a value"); GIN_EXPECT(a.value() == 2, "Values not 2, but " + std::to_string(a.value())); - GIN_EXPECT(!b.isError(), "Return is an error: " + b.error().message()); + GIN_EXPECT(!foo.isError(), b.error().message()); GIN_EXPECT(b.isValue(), "Return is not a value"); GIN_EXPECT(b.value() == 4234, "Values not 4234, but " + std::to_string(b.value())); } @@ -97,7 +97,7 @@ GIN_TEST("Async Conversion"){ ErrorOr foo = string_conveyor.take(); - GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message()); + GIN_EXPECT(!foo.isError(), foo.error().message()); GIN_EXPECT(foo.isValue(), "Return is not a value"); GIN_EXPECT(foo.value() == std::to_string(10), "Values is not 10, but " + foo.value()); } @@ -122,7 +122,7 @@ GIN_TEST("Async Conversion Multistep"){ ErrorOr foo = conveyor.take(); - GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message()); + GIN_EXPECT(!foo.isError(), foo.error().message()); GIN_EXPECT(foo.isValue(), "Return is not a value"); GIN_EXPECT(foo.value(), "Values is not true"); } @@ -165,20 +165,37 @@ GIN_TEST("Async Scheduling"){ ErrorOr foo_10 = string_conveyor.take(); - GIN_EXPECT(!foo_10.isError(), "Return is an error: " + foo_10.error().message()); + GIN_EXPECT(!foo_10.isError(), foo_10.error().message()); GIN_EXPECT(foo_10.isValue(), "Return is not a value"); GIN_EXPECT(foo_10.value() == (std::string{"pre"} + std::to_string(11) + std::string{"post"}), "Values is not pre11post, but " + foo_10.value()); ErrorOr foo_20 = string_conveyor.take(); - GIN_EXPECT(!foo_20.isError(), "Return is an error: " + foo_20.error().message()); + GIN_EXPECT(!foo_20.isError(), foo_20.error().message()); GIN_EXPECT(foo_20.isValue(), "Return is not a value"); GIN_EXPECT(foo_20.value() == (std::string{"pre"} + std::to_string(22) + std::string{"post"}), "Values is not pre22post, but " + foo_20.value()); ErrorOr foo_30 = string_conveyor.take(); - GIN_EXPECT(!foo_30.isError(), "Return is an error: " + foo_30.error().message()); + GIN_EXPECT(!foo_30.isError(), foo_30.error().message()); GIN_EXPECT(foo_30.isValue(), "Return is not a value"); GIN_EXPECT(foo_30.value() == (std::string{"pre"} + std::to_string(33) + std::string{"post"}), "Values is not pre33post, but " + foo_30.value()); } + +GIN_TEST("Async detach"){ + using namespace gin; + + EventLoop event_loop; + WaitScope wait_scope{event_loop}; + + int num = 0; + + Conveyor{10}.then([&num](int bar){ + num = bar; + }).detach(); + + wait_scope.poll(); + + GIN_EXPECT(num == 10, std::string{"Bad value: Expected 10, but got "} + std::to_string(num)); +} } \ No newline at end of file diff --git a/test/data/json.h b/test/data/json.h new file mode 100644 index 0000000..998c811 --- /dev/null +++ b/test/data/json.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "message.h" + +namespace gin { +const std::string_view json_org_example = +R"({ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +})"; +} \ No newline at end of file diff --git a/test/json.cpp b/test/json.cpp index ffb49ae..adccd5b 100644 --- a/test/json.cpp +++ b/test/json.cpp @@ -4,8 +4,10 @@ #include #include "buffer.h" -#include "source/message.h" -#include "source/json.h" +#include "source/kelgin/message.h" +#include "source/kelgin/json.h" + +#include "./data/json.h" using gin::MessageList; using gin::MessageStruct; @@ -40,7 +42,8 @@ GIN_TEST("JSON List Encoding"){ typedef MessageStruct< MessageStructMember, decltype("test_uint"_t)>, MessageStructMember, decltype("test_string"_t)>, - MessageStructMember, decltype("test_name"_t)> + MessageStructMember, decltype("test_name"_t)>, + MessageStructMember, decltype("test_bool"_t)> > TestStruct; GIN_TEST("JSON Struct Encoding"){ @@ -57,11 +60,13 @@ GIN_TEST("JSON Struct Encoding"){ auto string_name = root.init(); string_name.set("test_name"_t.view()); + root.init().set(false); + JsonCodec codec; RingBuffer temp_buffer; codec.encode(root.asReader(), temp_buffer); - std::string expected_result{"{\"test_uint\":23,\"test_string\":\"foo\",\"test_name\":\"test_name\"}"}; + std::string expected_result{"{\"test_uint\":23,\"test_string\":\"foo\",\"test_name\":\"test_name\",\"test_bool\":false}"}; std::string tmp_string = temp_buffer.toString(); GIN_EXPECT(tmp_string == expected_result, std::string{"Bad encoding:\n"} + tmp_string); @@ -86,7 +91,7 @@ GIN_TEST("JSON Union Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); std::string expected_result{"{\"test_uint\":23}"}; @@ -105,7 +110,7 @@ GIN_TEST("JSON Union Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); std::string expected_result{"{\"test_string\":\"foo\"}"}; @@ -119,7 +124,8 @@ GIN_TEST("JSON Struct Decoding"){ { "test_string" :"banana" , "test_uint" : 5, - "test_name" : "keldu" + "test_name" : "keldu", + "test_bool" : true })"; auto builder = heapMessageBuilder(); @@ -135,6 +141,28 @@ GIN_TEST("JSON Struct Decoding"){ GIN_EXPECT( reader.get().get() == "banana", "Test String has wrong value" ); GIN_EXPECT( reader.get().get() == 5, "Test Unsigned has wrong value" ); GIN_EXPECT( reader.get().get() == "keldu", "Test Name has wrong value" ); + GIN_EXPECT( reader.get().get() == true, "Test Bool has wrong value" ); +} + +GIN_TEST("JSON List Decoding"){ + std::string json_string = R"( + [ + 12, + "free" + ])"; + + auto builder = heapMessageBuilder(); + TestList::Builder root = builder.initRoot(); + + JsonCodec codec; + RingBuffer temp_buffer; + temp_buffer.push(*reinterpret_cast(json_string.data()), json_string.size()); + Error error = codec.decode(root, temp_buffer); + GIN_EXPECT( !error.failed(), error.message() ); + + auto reader = root.asReader(); + GIN_EXPECT( reader.get<0>().get() == 12, "Test Unsigned has wrong value" ); + GIN_EXPECT( reader.get<1>().get() == "free", "Test String has wrong value" ); } typedef MessageStruct< @@ -149,7 +177,8 @@ GIN_TEST("JSON Struct Decoding Two layer"){ "test_struct" :{ "test_string" : "banana", "test_uint": 40, - "test_name":"HaDiKo" + "test_name":"HaDiKo", + "test_bool" :false }, "test_uint": 5, "test_name" : "keldu" @@ -171,7 +200,90 @@ GIN_TEST("JSON Struct Decoding Two layer"){ GIN_EXPECT( inner_reader.get().get() == "banana", "Test String has wrong value" ); GIN_EXPECT( inner_reader.get().get() == 40, "Test Unsigned has wrong value" ); GIN_EXPECT( inner_reader.get().get() == "HaDiKo", "Test Name has wrong value" ); + GIN_EXPECT( inner_reader.get().get() == false, "Test Bool has wrong value" ); GIN_EXPECT( reader.get().get() == 5, "Test Unsigned has wrong value" ); GIN_EXPECT( reader.get().get() == "keldu", "Test Name has wrong value" ); } -} \ No newline at end of file + +typedef MessageStruct< + MessageStructMember< + MessageStruct< + MessageStructMember< MessagePrimitive, decltype("title"_t)>, + MessageStructMember< + MessageStruct< + MessageStructMember,decltype("title"_t)>, + MessageStructMember< + MessageStruct< + MessageStructMember< + MessageStruct< + MessageStructMember,decltype("ID"_t)>, + MessageStructMember,decltype("SortAs"_t)>, + MessageStructMember,decltype("GlossTerm"_t)>, + MessageStructMember,decltype("Acronym"_t)>, + MessageStructMember,decltype("Abbrev"_t)>, + MessageStructMember< + MessageStruct< + MessageStructMember, decltype("para"_t)>, + MessageStructMember< + MessageList< + MessagePrimitive, + MessagePrimitive + > + , decltype("GlossSeeAlso"_t)> + > + , decltype("GlossDef"_t)>, + MessageStructMember, decltype("GlossSee"_t)> + > + , decltype("GlossEntry"_t)> + > + , decltype("GlossList"_t)> + > + , decltype("GlossDiv"_t)> + > + , decltype("glossary"_t)> +> TestJsonOrgExample; + +GIN_TEST ("JSON.org Decoding Example"){ + auto builder = heapMessageBuilder(); + TestJsonOrgExample::Builder root = builder.initRoot(); + + JsonCodec codec; + RingBuffer temp_buffer; + temp_buffer.push(*reinterpret_cast(gin::json_org_example.data()), gin::json_org_example.size()); + + Error error = codec.decode(root, temp_buffer); + GIN_EXPECT(!error.failed(), error.message()); + + auto reader = root.asReader(); + + auto glossary_reader = reader.get(); + + GIN_EXPECT(glossary_reader.get().get() == "example glossary", "Bad glossary title"); + + auto gloss_div_reader = glossary_reader.get(); + + GIN_EXPECT(gloss_div_reader.get().get() == "S", "bad gloss div value" ); + + auto gloss_list_reader = gloss_div_reader.get(); + + auto gloss_entry_reader = gloss_list_reader.get(); + + GIN_EXPECT(gloss_entry_reader.get().get() == "SGML", "bad ID value" ); + GIN_EXPECT(gloss_entry_reader.get().get() == "SGML", "bad SortAs value" ); + GIN_EXPECT(gloss_entry_reader.get().get() == "Standard Generalized Markup Language", "bad GlossTerm value" ); + GIN_EXPECT(gloss_entry_reader.get().get() == "SGML", "bad Acronym value" ); + GIN_EXPECT(gloss_entry_reader.get().get() == "ISO 8879:1986", "bad Abbrev value" ); + GIN_EXPECT(gloss_entry_reader.get().get() == "markup", "bad GlossSee value" ); + + auto gloss_def_reader = gloss_entry_reader.get(); + + GIN_EXPECT(gloss_def_reader.get().get() == "A meta-markup language, used to create markup languages such as DocBook.", "para value wrong"); + + auto gloss_see_also_reader = gloss_def_reader.get(); + + GIN_EXPECT(gloss_see_also_reader.get<0>().get() == "GML", "List 0 value wrong"); + GIN_EXPECT(gloss_see_also_reader.get<1>().get() == "XML", "List 1 value wrong"); + + // (void) gloss_div_reader; +} +} diff --git a/test/message.cpp b/test/message.cpp index 93136ce..7cd6042 100644 --- a/test/message.cpp +++ b/test/message.cpp @@ -3,7 +3,7 @@ #include #include -#include "source/message.h" +#include "source/kelgin/message.h" using gin::MessageList; using gin::MessageStruct; using gin::MessageStructMember; diff --git a/test/proto_kel.cpp b/test/proto_kel.cpp index aac2c34..cd0a029 100644 --- a/test/proto_kel.cpp +++ b/test/proto_kel.cpp @@ -1,6 +1,6 @@ #include "suite/suite.h" -#include "source/proto_kel.h" +#include "source/kelgin/proto_kel.h" #include @@ -33,7 +33,7 @@ GIN_TEST("Primitive Encoding"){ Error error = ProtoKelEncodeImpl::encode(root.asReader(), temp_buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(temp_buffer.readCompositeLength() == sizeof(value), "Bad Size: " + std::to_string(temp_buffer.readCompositeLength())); GIN_EXPECT(temp_buffer[0] == 5 && temp_buffer[1] == 0 && temp_buffer[2] == 0 && temp_buffer[3] == 0, "Wrong encoded values"); } @@ -54,7 +54,7 @@ GIN_TEST("List Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(buffer.readCompositeLength() == 14, "Bad Size: " + std::to_string(buffer.readCompositeLength())); GIN_EXPECT("06 00 00 00\n00 00 00 00\nbf 94 20 00\n5f ab" == buffer.toHex(), "Not equal encoding\n"+buffer.toHex()); } @@ -80,7 +80,7 @@ GIN_TEST("Struct Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(buffer.readCompositeLength() == 40, "Bad Size: " + std::to_string(buffer.readCompositeLength())); GIN_EXPECT("20 00 00 00\n00 00 00 00\n17 00 00 00\n03 00 00 00\n00 00 00 00\n66 6f 6f 09\n00 00 00 00\n00 00 00 74\n65 73 74 5f\n6e 61 6d 65" == buffer.toHex(), "Not equal encoding:\n"+buffer.toHex()); @@ -100,7 +100,7 @@ GIN_TEST("Union Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(buffer.readCompositeLength() == 16, "Bad Size: " + std::to_string(buffer.readCompositeLength())); GIN_EXPECT("08 00 00 00\n00 00 00 00\n00 00 00 00\n17 00 00 00" == buffer.toHex(), "Not equal encoding:\n"+buffer.toHex()); @@ -117,7 +117,7 @@ GIN_TEST("Union Encoding"){ Error error = codec.encode(root.asReader(), buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(buffer.readCompositeLength() == 23, "Bad Size: " + std::to_string(buffer.readCompositeLength())); GIN_EXPECT("0f 00 00 00\n00 00 00 00\n01 00 00 00\n03 00 00 00\n00 00 00 00\n66 6f 6f" == buffer.toHex(), "Not equal encoding:\n"+buffer.toHex()); @@ -137,7 +137,7 @@ GIN_TEST("List Decoding"){ auto root = builder.initRoot(); Error error = codec.decode(root, buffer); - GIN_EXPECT(!error.failed(), std::string{"Error: "} + error.message()); + GIN_EXPECT(!error.failed(), error.message()); auto reader = root.asReader(); @@ -166,7 +166,7 @@ GIN_TEST("Struct Decoding"){ auto test_uint = reader.get(); auto test_name = reader.get(); - GIN_EXPECT(!error.failed(), std::string{"Error: "} + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(foo_string.get() == "foo" && test_uint.get() == 23 && test_name.get() == "test_name", "Values not correctly decoded"); } @@ -185,7 +185,7 @@ GIN_TEST("Union Decoding"){ Error error = codec.decode(root, buffer); - GIN_EXPECT(!error.failed(), "Error: " + error.message()); + GIN_EXPECT(!error.failed(), error.message()); GIN_EXPECT(reader.holdsAlternative(), "Wrong union value"); auto str_rd = reader.get(); GIN_EXPECT(str_rd.get() == "foo", "Wrong value: " + std::string{str_rd.get()}); diff --git a/test/suite/suite.h b/test/suite/suite.h index db2d846..73412a5 100644 --- a/test/suite/suite.h +++ b/test/suite/suite.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "common.h" @@ -35,7 +36,8 @@ public: }GIN_UNIQUE_NAME(testCase); \ void GIN_UNIQUE_NAME(TestCase)::run() -#define GIN_EXPECT(expr, msg) \ +#define GIN_EXPECT(expr, msg_split) \ if( ! (expr) ){ \ - throw std::runtime_error{msg}; \ + auto msg = msg_split; \ + throw std::runtime_error{std::string{msg}};\ }