@@ -13,81 +13,147 @@ be placed before any byte inside the rest of the message frame / payload, value
1313of the special characters (` STX ` , ` ETX ` , or ` DLE ` ). It ensures that a valid payload byte won't be
1414confused 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
3756std::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
65123In order to test such scenario the [client](src/ClientSession.cpp) concatenates buffers of `Msg2` and
66124`Msg3` before sending them:
67125```cpp
68126void 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
83139The [ server] ( src/ServerSession.cpp ) side receives this single buffer and successfully differentiates
84140between 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
87152Received 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
90157Received 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