packio
rpc.h
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_MSGPACK_RPC_H
6 #define PACKIO_MSGPACK_RPC_H
7 
8 #include <msgpack.hpp>
9 
10 #include "../arg.h"
11 #include "../internal/config.h"
12 #include "../internal/log.h"
13 #include "../internal/rpc.h"
14 
15 namespace packio::msgpack {
16 
17 enum class msgpack_rpc_type { request = 0, response = 1, notification = 2 };
18 
19 template <typename... Args>
20 constexpr bool positional_args_v = (!is_arg_v<Args> && ...);
21 
23 class rpc {
24  using buffer_type = ::msgpack::sbuffer;
25 
26 public:
28  using id_type = uint32_t;
29 
31  struct request {
32  call_type type;
33  id_type id;
34  std::string method;
35  ::msgpack::object args;
36 
37  std::unique_ptr<::msgpack::zone> zone;
38  };
39 
41  struct response {
42  id_type id;
43  ::msgpack::object result;
44  ::msgpack::object error;
45 
46  std::unique_ptr<::msgpack::zone> zone;
47  };
48 
49  class incremental_parser {
50  public:
51  incremental_parser()
52  : unpacker_{std::make_unique<::msgpack::unpacker>()}
53  {
54  }
55 
56  std::optional<request> get_request()
57  {
58  try_parse_object();
59  if (!parsed_) {
60  return std::nullopt;
61  }
62  auto object = std::move(*parsed_);
63  parsed_.reset();
64  return parse_request(std::move(object));
65  }
66 
67  std::optional<response> get_response()
68  {
69  try_parse_object();
70  if (!parsed_) {
71  return std::nullopt;
72  }
73  auto object = std::move(*parsed_);
74  parsed_.reset();
75  return parse_response(std::move(object));
76  }
77 
78  char* buffer() const
79  { //
80  return unpacker_->buffer();
81  }
82 
83  std::size_t buffer_capacity() const
84  {
85  return unpacker_->buffer_capacity();
86  }
87 
88  void buffer_consumed(std::size_t bytes)
89  {
90  unpacker_->buffer_consumed(bytes);
91  }
92 
93  void reserve_buffer(std::size_t bytes)
94  {
95  unpacker_->reserve_buffer(bytes);
96  }
97 
98  private:
99  void try_parse_object()
100  {
101  if (parsed_) {
102  return;
103  }
104  ::msgpack::object_handle object;
105  if (unpacker_->next(object)) {
106  parsed_ = std::move(object);
107  }
108  }
109 
110  std::optional<::msgpack::object_handle> parsed_;
111  std::unique_ptr<::msgpack::unpacker> unpacker_;
112  };
113 
114  static std::string format_id(const id_type& id)
115  {
116  return std::to_string(id);
117  }
118 
119  template <typename... Args>
120  static auto serialize_notification(std::string_view method, Args&&... args)
121  -> std::enable_if_t<positional_args_v<Args...>, buffer_type>
122  {
123  buffer_type buffer;
124  ::msgpack::pack(
125  buffer,
126  std::forward_as_tuple(
127  static_cast<int>(msgpack_rpc_type::notification),
128  method,
129  std::forward_as_tuple(std::forward<Args>(args)...)));
130  return buffer;
131  }
132 
133  template <typename... Args>
134  static auto serialize_notification(std::string_view, Args&&...)
135  -> std::enable_if_t<!positional_args_v<Args...>, buffer_type>
136  {
137  static_assert(
138  positional_args_v<Args...>,
139  "msgpack-RPC does not support named arguments");
140  }
141 
142  template <typename... Args>
143  static auto serialize_request(id_type id, std::string_view method, Args&&... args)
144  -> std::enable_if_t<positional_args_v<Args...>, buffer_type>
145  {
146  buffer_type buffer;
147  ::msgpack::pack(
148  buffer,
149  std::forward_as_tuple(
150  static_cast<int>(msgpack_rpc_type::request),
151  id,
152  method,
153  std::forward_as_tuple(std::forward<Args>(args)...)));
154  return buffer;
155  }
156 
157  template <typename... Args>
158  static auto serialize_request(id_type, std::string_view, Args&&...)
159  -> std::enable_if_t<!positional_args_v<Args...>, buffer_type>
160  {
161  static_assert(
162  positional_args_v<Args...>,
163  "msgpack-RPC does not support named arguments");
164  }
165 
166  template <typename... Args>
167  static buffer_type serialize_response(response response)
168  {
169  buffer_type buffer;
170  ::msgpack::pack(
171  buffer,
172  std::forward_as_tuple(
173  static_cast<int>(msgpack_rpc_type::response),
174  response.id,
175  response.error,
176  response.result));
177  return buffer;
178  }
179 
180  static net::const_buffer buffer(const buffer_type& buf)
181  {
182  return net::const_buffer(buf.data(), buf.size());
183  }
184 
185  template <typename T, typename NamesContainer>
186  static std::optional<T> extract_args(
187  const ::msgpack::object& args,
188  const NamesContainer&)
189  {
190  if (args.type != ::msgpack::type::ARRAY) {
191  PACKIO_ERROR("arguments is not an array");
192  return std::nullopt;
193  }
194 
195  if (args.via.array.size != std::tuple_size_v<T>) {
196  // keep this check otherwise msgpack unpacker
197  // may silently drop arguments
198  return std::nullopt;
199  }
200 
201  try {
202  return args.as<T>();
203  }
204  catch (::msgpack::type_error&) {
205  return std::nullopt;
206  }
207  }
208 
209  static response make_response(id_type id)
210  {
211  return make_response(id, ::msgpack::object{});
212  }
213 
214  template <typename T>
215  static response make_response(id_type id, T&& value)
216  {
217  response resp;
218  resp.id = id;
219  resp.zone = std::make_unique<::msgpack::zone>();
220  resp.result = ::msgpack::object(std::forward<T>(value), *resp.zone);
221  return resp;
222  }
223 
224  template <typename T>
225  static response make_error_response(id_type id, T&& value)
226  {
227  response resp;
228  resp.id = id;
229  resp.zone = std::make_unique<::msgpack::zone>();
230  resp.error = ::msgpack::object(std::forward<T>(value), *resp.zone);
231  return resp;
232  }
233 
234 private:
235  static std::optional<response> parse_response(::msgpack::object_handle&& res)
236  {
237  if (res->type != ::msgpack::type::ARRAY) {
238  PACKIO_ERROR("unexpected message type: {}", res->type);
239  return std::nullopt;
240  }
241  if (res->via.array.size != 4) {
242  PACKIO_ERROR("unexpected message size: {}", res->via.array.size);
243  return std::nullopt;
244  }
245  int type = res->via.array.ptr[0].as<int>();
246  if (type != static_cast<int>(msgpack_rpc_type::response)) {
247  PACKIO_ERROR("unexpected type: {}", type);
248  return std::nullopt;
249  }
250 
251  response parsed;
252  parsed.zone = std::move(res.zone());
253  const auto& array = res->via.array.ptr;
254 
255  parsed.id = array[1].as<id_type>();
256  if (array[2].type != ::msgpack::type::NIL) {
257  parsed.error = array[2];
258  }
259  else {
260  parsed.result = array[3];
261  }
262  return parsed;
263  }
264 
265  static std::optional<request> parse_request(::msgpack::object_handle&& req)
266  {
267  if (req->type != ::msgpack::type::ARRAY || req->via.array.size < 3) {
268  PACKIO_ERROR("unexpected message type: {}", req->type);
269  return std::nullopt;
270  }
271 
272  request parsed;
273  parsed.zone = std::move(req.zone());
274  const auto& array = req->via.array.ptr;
275  auto array_size = req->via.array.size;
276  ;
277 
278  try {
279  int idx = 0;
280  msgpack_rpc_type type = static_cast<msgpack_rpc_type>(
281  array[idx++].as<int>());
282 
283  std::size_t expected_size;
284  switch (type) {
285  case msgpack_rpc_type::request:
286  parsed.id = array[idx++].as<id_type>();
287  expected_size = 4;
288  parsed.type = call_type::request;
289  break;
290  case msgpack_rpc_type::notification:
291  expected_size = 3;
292  parsed.type = call_type::notification;
293  break;
294  default:
295  PACKIO_ERROR("unexpected type: {}", type);
296  return std::nullopt;
297  }
298 
299  if (array_size != expected_size) {
300  PACKIO_ERROR("unexpected message size: {}", array_size);
301  return std::nullopt;
302  }
303 
304  parsed.method = array[idx++].as<std::string>();
305  parsed.args = array[idx++];
306 
307  return parsed;
308  }
309  catch (::msgpack::type_error& exc) {
310  PACKIO_ERROR("unexpected message content: {}", exc.what());
311  (void)exc;
312  return std::nullopt;
313  }
314  }
315 };
316 
317 } // packio::msgpack
318 
319 #endif // PACKIO_MSGPACK_RPC_H
packio::msgpack::rpc::request
The object representing a client request.
Definition: rpc.h:31
packio::msgpack
packio::msgpack::rpc::response
The object representing the response to a call.
Definition: rpc.h:41
packio::msgpack::rpc::id_type
uint32_t id_type
Type of the call ID.
Definition: rpc.h:28
packio::msgpack::rpc
The msgpack RPC protocol implementation.
Definition: rpc.h:23
packio::msgpack::rpc::response::zone
std::unique_ptr<::msgpack::zone > zone
Msgpack zone storing error and result.
Definition: rpc.h:46
packio::msgpack::rpc::request::zone
std::unique_ptr<::msgpack::zone > zone
Msgpack zone storing the args.
Definition: rpc.h:37