Skip to content

Commit 58e69b8

Browse files
committed
Updated howto3 README.
1 parent 612cee0 commit 58e69b8

File tree

1 file changed

+110
-44
lines changed

1 file changed

+110
-44
lines changed

howtos/howto3/README.md

Lines changed: 110 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,81 +13,147 @@ be placed before any byte inside the rest of the message frame / payload, value
1313
of the special characters (`STX`, `ETX`, or `DLE`). It ensures that a valid payload byte won't be
1414
confused with the end of the current or beginning of the next message.
1515

16-
In order to handle such case it is highly recommended to pre-process the input and strip
17-
all the special characters before passing it for processing to the
18-
[COMMS Library](https://github.com/commschamp/comms).
19-
The same goes for post-processing the output and inject special characters before sending raw bytes
20-
over the I/O link.
21-
22-
The [schema](dsl/schema.xml) of this tutorial doesn't mention any prefix/suffix fields:
16+
In order to handle such case there is a need to split a protocol into two sub-protocols
17+
(covered in the [tutorial27](../../tutorials/tutorial27)). Let's call them `prot1` and `prot2`.
18+
The outer frame uses `<sync>` layers as both prefix and suffix(
19+
features introduced in **v8.0** of the [CommsDSL](https://github.com/commschamp/CommsDSL-Specification)).
2320
```xml
24-
<frame name="Frame">
25-
<id name="Id" field="MsgId" />
26-
<payload name="Data" />
27-
</frame>
21+
<ns name="prot1">
22+
...
23+
<frame name="Prot1Frame">
24+
<sync name="Prefix" seekField="true" escField="prot1.EscField">
25+
<field>
26+
<int name="SyncField" type="uint8" defaultValue="0x02" />
27+
</field>
28+
</sync>
29+
<id name="Id">
30+
<int name="IdField" type="uint8" defaultValue="0" pseudo="true" />
31+
</id>
32+
<payload name="Data" />
33+
<sync name="Suffix" seekField="true" from="Id" escField="prot1.EscField">
34+
<field>
35+
<int name="SyncField" type="uint8" defaultValue="0x03" />
36+
</field>
37+
</sync>
38+
</frame>
39+
</ns>
2840
```
41+
Note the usage of the **seekField**, **escField**, and **from** (in the suffix) properties. Usage
42+
of the **seekField="true"** in the `Prefix` layer allows silent consuming all the garbabe preceding the escape field.
2943

30-
The pre- and post-processing functionality is common for both **client** and **server** and is implemented
31-
inside [src/CommonSessionBase.cpp](src/CommonSessionBase.cpp).
44+
Another thing to note is having only a single message without actually serializing the message ID value (thanks to setting **pseudo** property).
45+
```xml
46+
<int name="IdField" type="uint8" defaultValue="0" pseudo="true" />
47+
```
48+
Such frame is effectively:
49+
```
50+
PREFIX | DATA | SUFFIX
51+
```
52+
See also [howto4](../howto4) for more details on single message protocol without message ID in framing.
3253

33-
When new input arrives, it's been pre-processed to remove all the special characters before passing
34-
the identified message to the [COMMS Library](https://github.com/commschamp/comms)
35-
for processing.
54+
The reception of the input data is simple dispatching it to the frame for handling:
3655
```cpp
3756
std::size_t ClientSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen)
3857
{
39-
...
40-
std::size_t consumed = 0;
41-
while (true) {
42-
MsgBuf msgBuf;
43-
auto consumedTmp = preProcessInput(buf + consumed, bufLen - consumed, msgBuf);
58+
std::cout << "Processing input: " << std::hex;
59+
std::copy_n(buf, bufLen, std::ostream_iterator<unsigned>(std::cout, " "));
60+
std::cout << std::dec << std::endl;
61+
62+
return comms::processAllWithDispatch(buf, bufLen, m_prot1Frame, *this);
63+
}
64+
```
4465
45-
if (consumedTmp == 0) {
46-
// Empty buffer or incomplete message remaining
47-
break;
48-
}
66+
When such message is received the raw data needs to be post-process to eliminate all the escape characters and
67+
then process it with the second protocol frame.
68+
```cpp
69+
void ClientSession::handle(Prot1PseudoMsg& msg)
70+
{
71+
std::cout << "Received message \"" << msg.doName() << '\n';
4972
50-
consumed += consumedTmp;
73+
MsgBuf prot2ProcessBuf;
74+
dropEscapes(msg.field_data().value().data(), msg.field_data().value().size(), prot2ProcessBuf);
5175
52-
if (!msgBuf.empty()) {
53-
comms::processAllWithDispatch(&msgBuf[0], msgBuf.size(), m_frame, *this);
54-
}
55-
}
76+
std::cout << "post-processed input: " << std::hex;
77+
std::copy(prot2ProcessBuf.begin(), prot2ProcessBuf.end(), std::ostream_iterator<unsigned>(std::cout, " "));
78+
std::cout << std::dec << std::endl;
5679
57-
return consumed;
80+
comms::processAllWithDispatch(prot2ProcessBuf.data(), prot2ProcessBuf.size(), m_prot2Frame, *this);
5881
}
5982
```
6083

61-
Please note that the input is processed in a loop to make sure that all the reported bytes are
62-
processes, while the call to `comms::processAllWithDispatch()` is performed per identified post-processed
63-
**single** message buffer.
84+
The second frame is defined as `ID | PAYLOAD`:
85+
```xml
86+
<frame name="Prot2Frame">
87+
<id name="Id" field="prot2.MsgId" />
88+
<payload name="Data" />
89+
</frame>
90+
```
91+
92+
The output functionality is performed in reverse. First `prot2` framing is used to serialize message,
93+
then post-processing to introduce escape characters is performed, and only finally the prefix and suffix
94+
framing is added using `prot1` framing.
95+
```cpp
96+
void ClientSession::writeMessage(const Prot2Interface& msg, MsgBuf& output)
97+
{
98+
MsgBuf outputTmp;
99+
100+
// Serialize message into the buffer (including framing)
101+
// The serialization uses polymorphic write functionality.
102+
auto writeIter = std::back_inserter(outputTmp);
103+
104+
// The frame will use polymorphic message ID retrieval to
105+
// prot1fix message payload with message ID
106+
auto es = m_prot2Frame.write(msg, writeIter, outputTmp.max_size());
107+
108+
...
109+
110+
// Introduce special characters
111+
addEscapes(outputTmp);
112+
113+
Prot1PseudoMsg pseudoMsg;
114+
comms::util::assign(pseudoMsg.field_data().value(), outputTmp.begin(), outputTmp.end());
115+
116+
auto finalWriteIter = std::back_inserter(output);
117+
auto sizeBeforeWrite = output.size();
118+
es = m_prot1Frame.write(pseudoMsg, finalWriteIter, output.max_size());
119+
...
120+
}
121+
```
64122
65123
In order to test such scenario the [client](src/ClientSession.cpp) concatenates buffers of `Msg2` and
66124
`Msg3` before sending them:
67125
```cpp
68126
void ClientSession::sendMsg2Msg3()
69127
{
128+
MsgBuf output;
70129
...
71130
writeMessage(msg2, output);
72131
...
73-
writeMessage(msg3, outputTmp);
74-
75-
// Concatenate buffers
76-
output.insert(output.end(), outputTmp.begin(), outputTmp.end());
132+
writeMessage(msg3, output);
77133
78-
// Send serialized message
134+
// Send serialized messages
79135
sendOutput(&output[0], output.size());
80136
}
81137
```
82138

83139
The [server](src/ServerSession.cpp) side receives this single buffer and successfully differentiates
84140
between the messages:
85141
```
86-
Processing input: 2 10 2 0 1 10 2 10 3 4 5 10 10 11 12 3 2 10 3 ab cd 3
142+
Sending raw data: 2 1 68 65 6c 6c 6f 3
143+
Processing input: 2 1 68 65 6c 6c 6f 3
144+
Received message "Prot1PseudoMsg
145+
post-processed input: 1 68 65 6c 6c 6f
146+
Received message "Message 1" with ID=1
147+
F1 = hello
148+
Sending raw data: 2 10 2 0 1 10 2 10 3 4 5 10 10 11 12 3 2 10 3 ab cd 3
149+
Processing input: 2 10 2 0 1 10 2 10 3 4 5 10 10 11 12 3
150+
Received message "Prot1PseudoMsg
151+
post-processed input: 2 0 1 2 3 4 5 10 11 12
87152
Received message "Message 2" with ID=2
88-
Sending message "Message 2" with ID=2
89-
Sending raw data: 2 10 2 0 1 10 2 10 3 4 5 10 10 11 12 3
153+
F1 = 0 1 2 3 4 5 10 11 12
154+
Processing input: 2 10 3 ab cd 3
155+
Received message "Prot1PseudoMsg
156+
post-processed input: 3 ab cd
90157
Received message "Message 3" with ID=3
91-
Sending message "Message 3" with ID=3
92-
Sending raw data: 2 10 3 ab cd 3
158+
F1 = 43981
93159
```

0 commit comments

Comments
 (0)