Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binary RPC REPE #577

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/glaze/binary/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ namespace glz
}
}
};

template <always_null_t T>
struct from_binary<T>
{
template <auto Opts>
GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&& ctx, auto&& it, auto&&) noexcept
{
if (*it != 0) [[unlikely]] {
ctx.error = error_code::syntax_error;
return;
}
++it;
}
};

template <glaze_value_t T>
struct from_binary<T>
Expand Down
139 changes: 139 additions & 0 deletions include/glaze/binary/rpc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Glaze Library
// For the license information refer to glaze.hpp

#pragma once

#include "glaze/binary/write.hpp"

namespace glz::repe
{
constexpr uint8_t delimiter = 0b00000'110;

struct header final
{
uint8_t version = 0b00000'110; // the REPE version
bool error = false; // whether an error has occurred
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the error be something else than bool?
How does this member relate to the error struct below

bool notification = false; // whether this RPC is a notification (no response returned)
std::string method = ""; // the RPC method to call
std::variant<std::monostate, uint64_t, std::string> id{}; // an identifier for this RPC call
std::vector<std::byte> custom; // custom data

struct glaze {
using T = header;
static constexpr auto value = glz::array(&T::version, //
&T::error, //
&T::notification, //
&T::method, //
&T::id, //
&T::custom //
);
};
};

template <class Data>
struct error final
{
uint32_t code = 0;
std::string message{};
Data& data;

struct glaze {
using T = error;
static constexpr auto value = glz::array(&T::code, //
&T::message, //
&T::data //
);
};
};

template <>
struct error<void> final
{
uint32_t code = 0;
std::string message{};

struct glaze {
using T = error;
static constexpr auto value = glz::array(&T::code, //
&T::message //
);
};
};

// response/request
template <class Body>
struct message final {
repe::header header{};
Body& body;
};

template <>
struct message<void> final {
repe::header header{};
};

template <class T>
struct call final
{

};
}

namespace glz::detail
{
template <class T>
struct to_binary<repe::message<T>> final
{
template <auto Opts, class... Args>
GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, Args&&... args) noexcept
{
write<binary>::op<Opts>(value.header, ctx, args...);
dump_type(repe::delimiter, args...);
if constexpr (requires { T::body; }) {
write<binary>::op<Opts>(value.body, ctx, args...);
dump_type(repe::delimiter, args...);
}
else {
dump_type(uint8_t(0), args...); // null body
dump_type(repe::delimiter, args...);
}
}
};

template <class T>
struct from_binary<repe::message<T>> final
{
template <auto Opts, class... Args>
GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& it, auto&& end) noexcept
{
read<binary>::op<Opts>(value.header, ctx, it, end);
if (bool(ctx.error)) [[unlikely]]
return;

const auto tag = uint8_t(*it);
if (tag != repe::delimiter) {
ctx.error = error_code::syntax_error;
return;
}
++it;

if constexpr (requires { T::body; }) {
read<binary>::op<Opts>(value.body, ctx, it, end);
if (bool(ctx.error)) [[unlikely]]
return;
}
else {
if (uint8_t(*it) != 0) {
ctx.error = error_code::syntax_error;
return;
}
}

if (uint8_t(*it) != repe::delimiter) {
ctx.error = error_code::syntax_error;
return;
}
++it;
}
};
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
endif()

add_subdirectory(api_test)
add_subdirectory(binary_rpc)
add_subdirectory(binary_test)
add_subdirectory(compare_test)
add_subdirectory(csv_test)
Expand Down
9 changes: 9 additions & 0 deletions tests/binary_rpc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
project(binary_rpc)

add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp)

target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common)

add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})

target_code_coverage(${PROJECT_NAME} AUTO ALL)
48 changes: 48 additions & 0 deletions tests/binary_rpc/binary_rpc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Glaze Library
// For the license information refer to glaze.hpp

#ifndef BOOST_UT_DISABLE_MODULE
#define BOOST_UT_DISABLE_MODULE
#endif

#include "boost/ut.hpp"
#include "glaze/binary/read.hpp"
#include "glaze/binary/write.hpp"
#include "glaze/binary/rpc.hpp"

using namespace boost::ut;

struct my_request_data
{
uint32_t integer{};
std::string string{};

struct glaze {
using T = my_request_data;
static constexpr auto value = glz::object(&T::integer, &T::string);
};
};

suite repe_write_read = [] {
"repe_write_read"_test = [] {
// This syntax works, but we can wrap this into a cleaner interface. Let this be the low level syntax.
my_request_data params{55, "hello"};
glz::repe::message<my_request_data> msg{glz::repe::header{.method = "func"}, params};

std::string buffer;
glz::write_binary(msg, buffer);

params = {};
msg.header = {};
expect(!glz::read_binary(msg, buffer));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can read_binary return an error with partial as an error code? In terms of networking, so we would know to async_receive more data into the buffer before trying again.

Secondly would it be beneficial to be able to only read the header?

glz::repe::header header{};
glz::read_binary(header, buffer);


expect(msg.header.method == "func");
expect(params.integer == 55);
expect(params.string == "hello");
};
};

int main()
{
return boost::ut::cfg<>.run({.report_errors = true});
}
Loading