|
1 | 1 | # Tutorial 27
|
2 |
| -Sub-protocols |
| 2 | +Sub-protocols (using namespaces) |
| 3 | + |
| 4 | +Some protocols can be divided to two or more sub-protocols with usually different framing and different messages. |
| 5 | +It is recommended to use different namespaces (using `<ns>` XML node) in the schema definition (see [schema](dsl/schema.xml)). |
| 6 | +``` |
| 7 | +<ns name="sub1"> |
| 8 | + ... |
| 9 | +</ns> |
| 10 | +
|
| 11 | +<ns name="sub2"> |
| 12 | + ... |
| 13 | +</ns> |
| 14 | +``` |
| 15 | +When using namespaces in the schema definition, the [CommsDSL Specification](https://commschamp.github.io/commsdsl_spec) requires |
| 16 | +full path when referencing fields or other components even if the referenced element resides in the same namespace: |
| 17 | +``` |
| 18 | +<message name="Msg1" id="sub1.MsgId.M0" order="0" displayName="^Msg1Name" /> |
| 19 | +... |
| 20 | +<message name="Msg4" id="sub2.MsgId.M4" displayName="^Msg4Name" /> |
| 21 | +``` |
| 22 | +Please pay attention that the protocol definition classes are functions have found their way to the |
| 23 | +[include/tutorial27/sub1](include/tutorial27/sub1) and the [include/tutorial27/sub2](include/tutorial27/sub2) folders |
| 24 | +respectively. The options definitions though are common and are defined in the parent |
| 25 | +[include/tutorial27/options](include/tutorial27/options) folder and namespace. |
| 26 | + |
| 27 | +Due to the fact that the schema doesn't define any `<interface>` it is generated automatically and resides |
| 28 | +in the parent namespace (see [include/tutorial27/Message.h](include/tutorial27/Message.h)). |
| 29 | + |
| 30 | +In this particular tutorial two sub protocols are defined. The first one is the same as defined in the |
| 31 | +[previous tutorial](../tutorial26), i.e. stateful one of where certain order of messages sequence is expected. |
| 32 | +Once all the messages from the first sub-protocol are received the flow is expected to switch to the second |
| 33 | +sub protocol where the messages are differentiated by proper ID field in the framing and can be received in any order. |
| 34 | + |
| 35 | +Let's take a look how the generated classes and other definitions are used, [server](src/ServerSession.h) for example: |
| 36 | +```cpp |
| 37 | +// Definition of all the used message classes |
| 38 | +TUTORIAL27_SUB1_ALIASES_FOR_ALL_MESSAGES(,,Message,ServerProtocolOptions); |
| 39 | +TUTORIAL27_SUB2_ALIASES_FOR_ALL_MESSAGES(,,Message,ServerProtocolOptions); |
| 40 | +``` |
| 41 | +The macros above are defined in the [include/tutorial27/sub1/input/AllMessages.h](include/tutorial27/sub1/input/AllMessages.h) |
| 42 | +and [include/tutorial27/sub2/input/AllMessages.h](include/tutorial27/sub2/input/AllMessages.h) respectively. Please not |
| 43 | +the presence of the `SUB1` and `SUB2` after the `TUTORIAL27` prefix, which don't exist when there is only one global namespace. |
| 44 | +
|
| 45 | +The frames are also defined referencing different namespaces: |
| 46 | +```cpp |
| 47 | +using Sub1Frame = |
| 48 | + tutorial27::sub1::frame::Frame< |
| 49 | + Message, |
| 50 | + tutorial27::sub1::input::ServerInputMessages<Message, ServerProtocolOptions>, |
| 51 | + ServerProtocolOptions |
| 52 | + >; |
| 53 | +using Sub2Frame = |
| 54 | + tutorial27::sub2::frame::Frame< |
| 55 | + Message, |
| 56 | + tutorial27::sub2::input::ServerInputMessages<Message, ServerProtocolOptions>, |
| 57 | + ServerProtocolOptions |
| 58 | + >; |
| 59 | +``` |
| 60 | + |
| 61 | +Both [server](src/ServerSession.cpp) and [client](src/ClientSession.cpp) are implemented more or less in the same way. |
| 62 | +They both track the stage of their communication and choose what framing to use to decode and dispatch the message. |
| 63 | +```cpp |
| 64 | +std::size_t ServerSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen) |
| 65 | +{ |
| 66 | + std::cout << "Processing input: " << std::hex; |
| 67 | + std::copy_n(buf, bufLen, std::ostream_iterator<unsigned>(std::cout, " ")); |
| 68 | + std::cout << std::dec << std::endl; |
| 69 | + |
| 70 | + using ProcFunc = std::size_t (ServerSession::*)(const std::uint8_t* buf, std::size_t bufLen); |
| 71 | + static const ProcFunc ProcMap[] = { |
| 72 | + /* ProtStage_sub1 */ &ServerSession::processInputSub1, |
| 73 | + /* ProtStage_sub2 */ &ServerSession::processInputSub2, |
| 74 | + }; |
| 75 | + static const std::size_t ProcMapSize = std::extent<decltype(ProcMap)>::value; |
| 76 | + static_assert(ProcMapSize == ProtStage_numOfValues, "Invalid map"); |
| 77 | + |
| 78 | + auto idx = static_cast<unsigned>(m_protStage); |
| 79 | + assert(idx < ProcMapSize); |
| 80 | + |
| 81 | + auto func = ProcMap[idx]; |
| 82 | + return (this->*func)(buf, bufLen); |
| 83 | +} |
| 84 | +``` |
| 85 | +The same goes for sending the message: |
| 86 | +```cpp |
| 87 | +void ServerSession::sendMessage(const Message& msg) |
| 88 | +{ |
| 89 | + std::cout << "Sending message \"" << msg.name() << "\"" << std::endl; |
| 90 | +
|
| 91 | + std::vector<std::uint8_t> output; |
| 92 | +
|
| 93 | + // Serialize message into the buffer (including framing) |
| 94 | + // The serialization uses polymorphic write functionality. |
| 95 | + auto writeIter = std::back_inserter(output); |
| 96 | +
|
| 97 | + using WriteFunc = comms::ErrorStatus (ServerSession::*)(const Message& msg, WriteIter& iter, std::size_t bufLen); |
| 98 | + static const WriteFunc WriteMap[] = { |
| 99 | + /* ProtStage_sub1 */ &ServerSession::writeMessageSub1, |
| 100 | + /* ProtStage_sub2 */ &ServerSession::writeMessageSub2, |
| 101 | + }; |
| 102 | + static const std::size_t WriteMapSize = std::extent<decltype(WriteMap)>::value; |
| 103 | + static_assert(WriteMapSize == ProtStage_numOfValues, "Invalid map"); |
| 104 | +
|
| 105 | + auto idx = static_cast<unsigned>(m_protStage); |
| 106 | + assert(idx < WriteMapSize); |
| 107 | +
|
| 108 | + auto func = WriteMap[idx]; |
| 109 | + auto es = (this->*func)(msg, writeIter, output.max_size()); |
| 110 | + if (es != comms::ErrorStatus::Success) { |
| 111 | + assert(!"Write operation failed unexpectedly"); |
| 112 | + return; |
| 113 | + } |
| 114 | +
|
| 115 | + // Send (re)serialized message back |
| 116 | + sendOutput(&output[0], output.size()); |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +**SIDE NOTE**: The namespaces can also be used to sub-divide the messages and/or fields to some sub-categories, not necessarily |
| 123 | +for the sub-protocols division. |
| 124 | + |
| 125 | +--- |
3 | 126 |
|
4 | 127 | ## Summary
|
5 | 128 |
|
| 129 | +- It is recommended to use different namespaces (`<ns>` XML node) to divide the protocol to sub-protocols. |
| 130 | +- The namespaces in schema are defined in the relevant sub-namespace and folder in the generated code. |
| 131 | + |
6 | 132 | [Read Previous Tutorial](../tutorial26) <-----------------------
|
0 commit comments