CppWAMP
C++11 client library for the WAMP protocol
wampmessage.hpp
1 /*------------------------------------------------------------------------------
2  Copyright Butterfly Energy Systems 2014-2015, 2022.
3  Distributed under the Boost Software License, Version 1.0.
4  http://www.boost.org/LICENSE_1_0.txt
5 ------------------------------------------------------------------------------*/
6 
7 #ifndef CPPWAMP_INTERNAL_WAMPMESSAGE_HPP
8 #define CPPWAMP_INTERNAL_WAMPMESSAGE_HPP
9 
10 #include <cassert>
11 #include <limits>
12 #include <type_traits>
13 #include <utility>
14 #include "../error.hpp"
15 #include "../variant.hpp"
16 #include "../wampdefs.hpp"
17 #include "messagetraits.hpp"
18 
19 namespace wamp
20 {
21 
22 namespace internal
23 {
24 
25 //------------------------------------------------------------------------------
26 // Derived types must not contain member variables due to intentional static
27 // downcasting (to avoid slicing).
28 //------------------------------------------------------------------------------
29 struct WampMessage
30 {
31  using RequestKey = std::pair<WampMsgType, RequestId>;
32 
33  static WampMessage parse(Array&& fields, std::error_code& ec)
34  {
36  WampMsgType type = parseMsgType(fields);
37  if (type == WampMsgType::none)
38  {
40  }
41  else
42  {
43  auto traits = MessageTraits::lookup(type);
44  if ( fields.size() < traits.minSize ||
45  fields.size() > traits.maxSize )
46  {
48  }
49  else
50  {
51  assert(fields.size() <=
52  std::extent<decltype(traits.fieldTypes)>::value);
53  for (size_t i=0; i<fields.size(); ++i)
54  {
55  if (fields[i].typeId() != traits.fieldTypes[i])
56  {
58  break;
59  }
60  }
61  }
62  }
63  ec = make_error_code(errc);
64  return WampMessage(type, std::move(fields));
65  }
66 
67  WampMessage() : type_(WampMsgType::none) {}
68 
69  WampMessage(WampMsgType type, Array&& messageFields)
70  : type_(type), fields_(std::move(messageFields))
71  {
72  if (fields_.empty())
73  fields_.push_back(static_cast<Int>(type));
74  else
75  fields_.at(0) = static_cast<Int>(type);
76  }
77 
78  void setType(WampMsgType t)
79  {
80  type_ = t;
81  fields_.at(0) = Int(t);
82  }
83 
84  void setRequestId(RequestId reqId)
85  {
86  auto idPos = traits().idPosition;
87  if (idPos != 0)
88  fields_.at(idPos) = reqId;
89  }
90 
91  WampMsgType type() const {return type_;}
92 
93  const MessageTraits& traits() const {return MessageTraits::lookup(type_);}
94 
95  const char* name() const
96  {
97  // Returns nullptr if invalid message type ID field
98  auto t = parseMsgType(fields_);
99  return MessageTraits::lookup(t).name;
100  }
101 
102  const char* nameOr(const char* fallback) const
103  {
104  auto n = name();
105  return n == nullptr ? fallback : n;
106  }
107 
108  size_t size() const {return fields_.size();}
109 
110  const Array& fields() const {return fields_;}
111 
112  Variant& at(size_t index) {return fields_.at(index);}
113 
114  const Variant& at(size_t index) const {return fields_.at(index);}
115 
116  template <typename T>
117  T& as(size_t index) {return fields_.at(index).as<T>();}
118 
119  template <typename T>
120  const T& as(size_t index) const {return fields_.at(index).as<T>();}
121 
122  template <typename T>
123  T to(size_t index) const {return fields_.at(index).to<T>();}
124 
125  bool hasRequestId() const {return traits().idPosition != 0;}
126 
127  RequestId requestId() const
128  {
129  RequestId id = 0;
130  if (type_ != WampMsgType::error)
131  {
132  auto idPos = traits().idPosition;
133  if (idPos != 0)
134  id = fields_.at(idPos).to<RequestId>();
135  }
136  else
137  id = fields_.at(2).to<RequestId>();
138  return id;
139  }
140 
141  RequestKey requestKey() const
142  {
143  WampMsgType reqType = (type_ != WampMsgType::error) ? type_ :
144  static_cast<WampMsgType>(fields_.at(1).as<Int>());
145  return std::make_pair(reqType, requestId());
146  }
147 
148  WampMsgType repliesTo() const {return traits().repliesTo;}
149 
150  bool isProgressiveResponse() const
151  {
152  if (type_ != WampMsgType::result || fields_.size() < 3)
153  return false;
154 
155  const auto& optionsField = fields_.at(2);
156  if (!optionsField.is<Object>())
157  return false;
158 
159  const auto& optionsMap = optionsField.as<Object>();
160  auto found = optionsMap.find("progress");
161  if (found == optionsMap.end())
162  return false;
163 
164  return found->second.valueOr<bool>(false);
165  }
166 
167 protected:
168  WampMsgType type_;
169  mutable Array fields_; // Mutable for lazy-loaded empty payloads
170 
171 private:
172  static WampMsgType parseMsgType(const Array& fields)
173  {
174  auto result = WampMsgType::none;
175 
176  if (!fields.empty())
177  {
178  const auto& first = fields.front();
179  if (first.is<Int>())
180  {
181  using T = std::underlying_type<WampMsgType>::type;
182  static constexpr auto max = std::numeric_limits<T>::max();
183  auto n = first.to<Int>();
184  if (n >= 0 && n <= max)
185  {
186  result = static_cast<WampMsgType>(n);
187  if (!MessageTraits::lookup(result).isValid())
188  result = WampMsgType::none;
189  }
190  }
191  }
192  return result;
193  }
194 };
195 
196 //------------------------------------------------------------------------------
197 template <WampMsgType Kind, unsigned I>
198 struct MessageWithOptions : public WampMessage
199 {
200  static constexpr WampMsgType kind = Kind;
201 
202  static constexpr unsigned optionsPos = I;
203 
204  const Object& options() const
205  {
206  return fields_.at(optionsPos).template as<Object>();
207  }
208 
209  Object& options()
210  {
211  return fields_.at(optionsPos).template as<Object>();
212  }
213 
214 protected:
215  explicit MessageWithOptions(Array&& fields)
216  : WampMessage(kind, std::move(fields))
217  {}
218 };
219 
220 //------------------------------------------------------------------------------
221 template <WampMsgType Kind, unsigned I, unsigned J>
222 struct MessageWithPayload : public MessageWithOptions<Kind, I>
223 {
224  static constexpr unsigned argsPos = J;
225  static constexpr unsigned kwargsPos = J + 1;
226 
227  const Array& args() const &
228  {
229  if (this->fields_.size() <= argsPos)
230  this->fields_.emplace_back(Array{});
231  return this->fields_[argsPos].template as<Array>();
232  }
233 
234  Array& args() &
235  {
236  if (this->fields_.size() <= argsPos)
237  this->fields_.emplace_back(Array{});
238  return this->fields_[argsPos].template as<Array>();
239  }
240 
241  const Object& kwargs() const
242  {
243  if (this->fields_.size() <= kwargsPos)
244  {
245  if (this->fields_.size() <= argsPos)
246  this->fields_.emplace_back(Array{});
247  this->fields_.emplace_back(Object{});
248  }
249  return this->fields_[kwargsPos].template as<Object>();
250  }
251 
252  Object& kwargs()
253  {
254  if (this->fields_.size() <= kwargsPos)
255  {
256  if (this->fields_.size() <= argsPos)
257  this->fields_.emplace_back(Array{});
258  this->fields_.emplace_back(Object{});
259  }
260  return this->fields_[kwargsPos].template as<Object>();
261  }
262 
263 protected:
264  explicit MessageWithPayload(Array&& fields)
265  : MessageWithOptions<Kind, I>(std::move(fields))
266  {}
267 };
268 
269 //------------------------------------------------------------------------------
270 struct HelloMessage : public MessageWithOptions<WampMsgType::hello, 2>
271 {
272  explicit HelloMessage(String realmUri)
273  : Base({0, std::move(realmUri), Object{}})
274  {}
275 
276  const String& realmUri() const {return fields_.at(1).as<String>();}
277 
278 private:
279  using Base = MessageWithOptions<WampMsgType::hello, 2>;
280 };
281 
282 //------------------------------------------------------------------------------
283 struct ChallengeMessage : public MessageWithOptions<WampMsgType::challenge, 2>
284 {
285  ChallengeMessage() : Base({0, String{}}) {}
286 
287  const String& authMethod() const {return fields_.at(1).as<String>();}
288 
289 private:
290  using Base = MessageWithOptions<WampMsgType::challenge, 2>;
291 };
292 
293 //------------------------------------------------------------------------------
294 struct AuthenticateMessage
295  : public MessageWithOptions<WampMsgType::authenticate, 2>
296 {
297  explicit AuthenticateMessage(String sig, Object opts = {})
298  : Base({0, std::move(sig), std::move(opts)})
299  {}
300 
301  const String& signature() const {return fields_.at(1).as<String>();}
302 
303 private:
304  using Base = MessageWithOptions<WampMsgType::authenticate, 2>;
305 };
306 
307 //------------------------------------------------------------------------------
308 struct WelcomeMessage : public MessageWithOptions<WampMsgType::welcome, 2>
309 {
310  WelcomeMessage() : Base({0, 0, Object{}}) {}
311 
312  SessionId sessionId() const {return fields_.at(1).to<SessionId>();}
313 
314 private:
315  using Base = MessageWithOptions<WampMsgType::welcome, 2>;
316 };
317 
318 //------------------------------------------------------------------------------
319 struct AbortMessage : public MessageWithOptions<WampMsgType::abort, 1>
320 {
321  AbortMessage(String reason = "", Object opts = {})
322  : Base({0, std::move(opts), std::move(reason)}) {}
323 
324  const String& reasonUri() const {return fields_.at(2).as<String>();}
325 
326 private:
327  using Base = MessageWithOptions<WampMsgType::abort, 1>;
328 };
329 
330 //------------------------------------------------------------------------------
331 struct GoodbyeMessage : public MessageWithOptions<WampMsgType::goodbye, 1>
332 {
333  explicit GoodbyeMessage(String reason = "", Object opts = {})
334  : Base({0, std::move(opts), std::move(reason)})
335  {
336  if (reasonUri().empty())
337  fields_[2] = String("wamp.error.close_realm");
338  }
339 
340  const String& reasonUri() const {return fields_.at(2).as<String>();}
341 
342 private:
343  using Base = MessageWithOptions<WampMsgType::goodbye, 1>;
344 };
345 
346 //------------------------------------------------------------------------------
347 struct ErrorMessage : public MessageWithPayload<WampMsgType::error, 3, 5>
348 {
349  explicit ErrorMessage(String reason = "", Object opts = {})
350  : Base({0, 0, 0, std::move(opts), std::move(reason)})
351  {}
352 
353  void setRequestInfo(WampMsgType reqType, RequestId reqId) const
354  {
355  fields_.at(1) = Int(reqType);
356  fields_.at(2) = reqId;
357  }
358 
359  WampMsgType requestType() const
360  {
361  using N = std::underlying_type<WampMsgType>::type;
362  auto n = fields_.at(1).to<N>();
363  return static_cast<WampMsgType>(n);
364  }
365 
366  RequestId requestId() const {return fields_.at(2).to<RequestId>();}
367 
368  const String& reasonUri() const {return fields_.at(4).as<String>();}
369 
370 private:
371  using Base = MessageWithPayload<WampMsgType::error, 3, 5>;
372 };
373 
374 //------------------------------------------------------------------------------
375 struct PublishMessage : public MessageWithPayload<WampMsgType::publish, 2, 4>
376 {
377  explicit PublishMessage(String topic, Object opts = {})
378  : Base({0, 0, std::move(opts), std::move(topic)})
379  {}
380 
381  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
382 
383  const String& topicUri() const {return fields_.at(3).as<String>();}
384 
385 private:
386  using Base = MessageWithPayload<WampMsgType::publish, 2, 4>;
387 };
388 
389 //------------------------------------------------------------------------------
390 struct PublishedMessage : public WampMessage
391 {
392  static constexpr auto kind = WampMsgType::published;
393 
394  PublishedMessage() : WampMessage(kind, {0, 0, 0}) {}
395 
396  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
397 
398  PublicationId publicationId() const
399  {
400  return fields_.at(2).to<PublicationId>();
401  }
402 };
403 
404 //------------------------------------------------------------------------------
405 struct SubscribeMessage : public MessageWithOptions<WampMsgType::subscribe, 2>
406 {
407  explicit SubscribeMessage(String topic)
408  : Base({0, 0, Object{}, std::move(topic)})
409  {}
410 
411  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
412 
413  const String& topicUri() const {return fields_.at(3).as<String>();}
414 
415 private:
416  using Base = MessageWithOptions<WampMsgType::subscribe, 2>;
417 };
418 
419 //------------------------------------------------------------------------------
420 struct SubscribedMessage : public WampMessage
421 {
422  static constexpr auto kind = WampMsgType::subscribed;
423 
424  SubscribedMessage() : WampMessage(kind, {0, 0, 0}) {}
425 
426  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
427 
428  SubscriptionId subscriptionId() const
429  {
430  return fields_.at(2).to<SubscriptionId>();
431  }
432 };
433 
434 //------------------------------------------------------------------------------
435 struct UnsubscribeMessage : public WampMessage
436 {
437  static constexpr auto kind = WampMsgType::unsubscribe;
438 
439  explicit UnsubscribeMessage(SubscriptionId subId)
440  : WampMessage(kind, {0, 0, subId})
441  {}
442 
443  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
444 
445  SubscriptionId subscriptionId() const
446  {
447  return fields_.at(2).to<SubscriptionId>();
448  }
449 };
450 
451 //------------------------------------------------------------------------------
452 struct UnsubscribedMessage : public WampMessage
453 {
454  static constexpr auto kind = WampMsgType::unsubscribed;
455 
456  UnsubscribedMessage() : WampMessage(kind, {0, 0}) {}
457 
458  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
459 };
460 
461 //------------------------------------------------------------------------------
462 struct EventMessage : public MessageWithPayload<WampMsgType::event, 3, 4>
463 {
464  EventMessage() : Base({0, 0, 0, Object{}}) {}
465 
466  SubscriptionId subscriptionId() const
467  {
468  return fields_.at(1).to<SubscriptionId>();
469  }
470 
471  PublicationId publicationId() const
472  {
473  return fields_.at(2).to<PublicationId>();
474  }
475 
476 private:
477  using Base = MessageWithPayload<WampMsgType::event, 3, 4>;
478 };
479 
480 //------------------------------------------------------------------------------
481 struct CallMessage : public MessageWithPayload<WampMsgType::call, 2, 4>
482 {
483  explicit CallMessage(String uri, Object opts = {})
484  : Base({0, 0, std::move(opts), std::move(uri)})
485  {}
486 
487  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
488 
489  const String& procedureUri() const {return fields_.at(3).as<String>();}
490 
491 private:
492  using Base = MessageWithPayload<WampMsgType::call, 2, 4>;
493 };
494 
495 //------------------------------------------------------------------------------
496 struct RegisterMessage : public MessageWithOptions<WampMsgType::enroll, 2>
497 {
498  explicit RegisterMessage(String uri, Object opts = {})
499  : Base({0, 0, std::move(opts), std::move(uri)})
500  {}
501 
502  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
503 
504  const String& procedureUri() const {return fields_.at(3).as<String>();}
505 
506 private:
507  using Base = MessageWithOptions<WampMsgType::enroll, 2>;
508 };
509 
510 //------------------------------------------------------------------------------
511 struct RegisteredMessage : public WampMessage
512 {
513  static constexpr auto kind = WampMsgType::registered;
514 
515  RegisteredMessage() : WampMessage(kind, {0, 0, 0}) {}
516 
517  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
518 
519  RegistrationId registrationId() const
520  {
521  return fields_.at(2).to<RegistrationId>();
522  }
523 };
524 
525 //------------------------------------------------------------------------------
526 struct UnregisterMessage : public WampMessage
527 {
528  static constexpr auto kind = WampMsgType::unregister;
529 
530  explicit UnregisterMessage(RegistrationId regId)
531  : WampMessage(kind, {0, 0, regId})
532  {}
533 
534  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
535 
536  RegistrationId registrationId() const
537  {
538  return fields_.at(2).to<RegistrationId>();
539  }
540 };
541 
542 //------------------------------------------------------------------------------
543 struct UnregisteredMessage : public WampMessage
544 {
545  static constexpr auto kind = WampMsgType::unregistered;
546 
547  UnregisteredMessage() : WampMessage(kind, {0, 0}) {}
548 
549  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
550 };
551 
552 //------------------------------------------------------------------------------
553 struct InvocationMessage :
554  public MessageWithPayload<WampMsgType::invocation, 3, 4>
555 {
556  InvocationMessage() : Base({0, 0, 0, Object{}}) {}
557 
558  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
559 
560  RegistrationId registrationId() const
561  {
562  return fields_.at(2).to<RegistrationId>();
563  }
564 
565 private:
566  using Base = MessageWithPayload<WampMsgType::invocation, 3, 4>;
567 };
568 
569 //------------------------------------------------------------------------------
570 struct YieldMessage : public MessageWithPayload<WampMsgType::yield, 2, 3>
571 {
572  explicit YieldMessage(RequestId reqId, Object opts = {})
573  : Base({0, reqId, std::move(opts)})
574  {}
575 
576  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
577 
578 private:
579  using Base = MessageWithPayload<WampMsgType::yield, 2, 3>;
580 };
581 
582 //------------------------------------------------------------------------------
583 struct ResultMessage : public MessageWithPayload<WampMsgType::result, 2, 3>
584 {
585  explicit ResultMessage(Object opts = {}) : Base({0, 0, std::move(opts)}) {}
586 
587  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
588 
589  YieldMessage& transformToYield()
590  {
591  setType(WampMsgType::yield);
592  auto& base = static_cast<WampMessage&>(*this);
593  return static_cast<YieldMessage&>(base);
594  }
595 
596 private:
597  using Base = MessageWithPayload<WampMsgType::result, 2, 3>;
598 };
599 
600 //------------------------------------------------------------------------------
601 struct CancelMessage : public MessageWithOptions<WampMsgType::cancel, 2>
602 {
603  explicit CancelMessage(RequestId reqId, Object opts = {})
604  : Base({0, reqId, std::move(opts)})
605  {}
606 
607  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
608 
609 private:
610  using Base = MessageWithOptions<WampMsgType::cancel, 2>;
611 };
612 
613 //------------------------------------------------------------------------------
614 struct InterruptMessage : public MessageWithOptions<WampMsgType::interrupt, 2>
615 {
616  explicit InterruptMessage(RequestId reqId = 0, Object opts = {})
617  : Base({0, reqId, std::move(opts)})
618  {}
619 
620  RequestId requestId() const {return fields_.at(1).to<RequestId>();}
621 
622 private:
623  using Base = MessageWithOptions<WampMsgType::interrupt, 2>;
624 };
625 
626 //------------------------------------------------------------------------------
627 template <typename TDerived>
628 TDerived& message_cast(WampMessage& msg)
629 {
630  assert(msg.type() == TDerived::kind);
631  return static_cast<TDerived&>(msg);
632 }
633 
634 //------------------------------------------------------------------------------
635 template <typename TDerived>
636 const TDerived& message_cast(const WampMessage& msg)
637 {
638  assert(msg.type() == TDerived::kind);
639  return static_cast<const TDerived&>(msg);
640 }
641 
642 } // namespace internal
643 
644 } // namespace wamp
645 
646 #endif // CPPWAMP_INTERNAL_WAMPMESSAGE_HPP
wamp::RequestId
int64_t RequestId
Ephemeral ID associated with a WAMP request.
Definition: wampdefs.hpp:23
wamp::RegistrationId
int64_t RegistrationId
Obtains the value representing a blank RequestId.
Definition: wampdefs.hpp:28
wamp::SessionId
int64_t SessionId
Ephemeral ID associated with a WAMP session.
Definition: wampdefs.hpp:22
wamp::ProtocolErrc
ProtocolErrc
Error code values used with the ProtocolCategory error category.
Definition: error.hpp:308
wamp::Object
std::map< String, Variant > Object
Variant bound type for maps of variants.
Definition: variantdefs.hpp:52
wamp::SubscriptionId
int64_t SubscriptionId
Ephemeral ID associated with an topic subscription.
Definition: wampdefs.hpp:24
wamp::ProtocolErrc::badSchema
@ badSchema
Invalid WAMP message schema.
wamp
Definition: anyhandler.hpp:36
wamp::ProtocolErrc::success
@ success
Operation successful.
wamp::Array
std::vector< Variant > Array
Variant bound type for arrays of variants.
Definition: variantdefs.hpp:51
wamp::Int
std::int64_t Int
Variant bound type for signed integers.
Definition: variantdefs.hpp:47
wamp::PublicationId
int64_t PublicationId
Ephemeral ID associated with an event publication.
Definition: wampdefs.hpp:25
wamp::String
std::string String
Variant bound type for text strings.
Definition: variantdefs.hpp:50