Skip to content

Commit 2d5d246

Browse files
committed
Added tutorial27 README.
1 parent c404cd6 commit 2d5d246

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ $> nmake install
181181
- [tutorial24](../../tree/master/tutorials/tutorial24) - Extra validity verification of message payload.
182182
- [tutorial25](../../tree/master/tutorials/tutorial25) - Dealing with big protocols.
183183
- [tutorial26](../../tree/master/tutorials/tutorial26) - Stateful messages sequence.
184+
- [tutorial27](../../tree/master/tutorials/tutorial27) - Sub-protocols (using namespaces).
184185

185186

186187
# How-Tos

tutorials/tutorial27/README.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,132 @@
11
# 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+
---
3126

4127
## Summary
5128

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+
6132
[Read Previous Tutorial](../tutorial26) &lt;-----------------------

0 commit comments

Comments
 (0)