1 /*
2 * SRT - Secure, Reliable, Transport
3 * Copyright (c) 2018 Haivision Systems Inc.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 */
10
11 /*****************************************************************************
12 Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
13 All rights reserved.
14
15 Redistribution and use in source and binary forms, with or without
16 modification, are permitted provided that the following conditions are
17 met:
18
19 * Redistributions of source code must retain the above
20 copyright notice, this list of conditions and the
21 following disclaimer.
22
23 * Redistributions in binary form must reproduce the
24 above copyright notice, this list of conditions
25 and the following disclaimer in the documentation
26 and/or other materials provided with the distribution.
27
28 * Neither the name of the University of Illinois
29 nor the names of its contributors may be used to
30 endorse or promote products derived from this
31 software without specific prior written permission.
32
33 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
34 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
35 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
36 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
38 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
39 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
40 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
41 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
42 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 *****************************************************************************/
45
46 /*****************************************************************************
47 written by
48 Yunhong Gu, last updated 02/12/2011
49 modified by
50 Haivision Systems Inc.
51 *****************************************************************************/
52
53
54 //////////////////////////////////////////////////////////////////////////////
55 // 0 1 2 3
56 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
57 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
58 // | Packet Header |
59 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60 // | |
61 // ~ Data / Control Information Field ~
62 // | |
63 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
64 //
65 // 0 1 2 3
66 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
67 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
68 // |0| Sequence Number |
69 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
70 // |ff |o|kf |r| Message Number |
71 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72 // | Time Stamp |
73 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74 // | Destination Socket ID |
75 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76 //
77 // bit 0:
78 // 0: Data Packet
79 // 1: Control Packet
80 // bit ff:
81 // 11: solo message packet
82 // 10: first packet of a message
83 // 01: last packet of a message
84 // bit o:
85 // 0: in order delivery not required
86 // 1: in order delivery required
87 // bit kf: HaiCrypt Key Flags
88 // 00: not encrypted
89 // 01: encrypted with even key
90 // 10: encrypted with odd key
91 // bit r: retransmission flag (set to 1 if this packet was sent again)
92 //
93 // 0 1 2 3
94 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
95 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96 // |1| Type | Reserved |
97 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
98 // | Additional Info |
99 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
100 // | Time Stamp |
101 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
102 // | Destination Socket ID |
103 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104 //
105 // bit 1-15: Message type -- see @a UDTMessageType
106 // 0: Protocol Connection Handshake (UMSG_HANDSHAKE}
107 // Add. Info: Undefined
108 // Control Info: Handshake information (see @a CHandShake)
109 // 1: Keep-alive (UMSG_KEEPALIVE)
110 // Add. Info: Undefined
111 // Control Info: None
112 // 2: Acknowledgement (UMSG_ACK)
113 // Add. Info: The ACK sequence number
114 // Control Info: The sequence number to which (but not include) all the previous packets have beed received
115 // Optional: RTT
116 // RTT Variance
117 // available receiver buffer size (in bytes)
118 // advertised flow window size (number of packets)
119 // estimated bandwidth (number of packets per second)
120 // 3: Negative Acknowledgement (UMSG_LOSSREPORT)
121 // Add. Info: Undefined
122 // Control Info: Loss list (see loss list coding below)
123 // 4: Congestion/Delay Warning (UMSG_CGWARNING)
124 // Add. Info: Undefined
125 // Control Info: None
126 // 5: Shutdown (UMSG_SHUTDOWN)
127 // Add. Info: Undefined
128 // Control Info: None
129 // 6: Acknowledgement of Acknowledement (UMSG_ACKACK)
130 // Add. Info: The ACK sequence number
131 // Control Info: None
132 // 7: Message Drop Request (UMSG_DROPREQ)
133 // Add. Info: Message ID
134 // Control Info: first sequence number of the message
135 // last seqeunce number of the message
136 // 8: Error Signal from the Peer Side (UMSG_PEERERROR)
137 // Add. Info: Error code
138 // Control Info: None
139 // 0x7FFF: Explained by bits 16 - 31 (UMSG_EXT)
140 //
141 // bit 16 - 31:
142 // This space is used for future expansion or user defined control packets.
143 //
144 // 0 1 2 3
145 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
146 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
147 // |1| Sequence Number a (first) |
148 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
149 // |0| Sequence Number b (last) |
150 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
151 // |0| Sequence Number (single) |
152 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
153 //
154 // Loss List Field Coding:
155 // For any consectutive lost seqeunce numbers that the differnece between
156 // the last and first is more than 1, only record the first (a) and the
157 // the last (b) sequence numbers in the loss list field, and modify the
158 // the first bit of a to 1.
159 // For any single loss or consectutive loss less than 2 packets, use
160 // the original sequence numbers in the field.
161
162 #include "platform_sys.h"
163
164 #include <cstring>
165 #include "packet.h"
166 #include "handshake.h"
167 #include "logging.h"
168 #include "handshake.h"
169
170 namespace srt_logging
171 {
172 extern Logger inlog;
173 }
174 using namespace srt_logging;
175
176 // Set up the aliases in the constructure
CPacket()177 srt::CPacket::CPacket():
178 m_extra_pad(),
179 m_data_owned(false),
180 m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])),
181 m_iMsgNo((int32_t&)(m_nHeader[SRT_PH_MSGNO])),
182 m_iTimeStamp((int32_t&)(m_nHeader[SRT_PH_TIMESTAMP])),
183 m_iID((int32_t&)(m_nHeader[SRT_PH_ID])),
184 m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef()))
185 {
186 m_nHeader.clear();
187
188 // The part at PV_HEADER will be always set to a builtin buffer
189 // containing SRT header.
190 m_PacketVector[PV_HEADER].set(m_nHeader.raw(), HDR_SIZE);
191
192 // The part at PV_DATA is zero-initialized. It should be
193 // set (through m_pcData and setLength()) to some externally
194 // provided buffer before calling CChannel::sendto().
195 m_PacketVector[PV_DATA].set(NULL, 0);
196 }
197
getData()198 char* srt::CPacket::getData()
199 {
200 return (char*)m_PacketVector[PV_DATA].dataRef();
201 }
202
allocate(size_t alloc_buffer_size)203 void srt::CPacket::allocate(size_t alloc_buffer_size)
204 {
205 if (m_data_owned)
206 {
207 if (getLength() == alloc_buffer_size)
208 return; // already allocated
209
210 // Would be nice to reallocate; for now just allocate again.
211 delete [] m_pcData;
212 }
213 m_PacketVector[PV_DATA].set(new char[alloc_buffer_size], alloc_buffer_size);
214 m_data_owned = true;
215 }
216
deallocate()217 void srt::CPacket::deallocate()
218 {
219 if (m_data_owned)
220 delete [] (char*)m_PacketVector[PV_DATA].data();
221 m_PacketVector[PV_DATA].set(NULL, 0);
222 }
223
release()224 char* srt::CPacket::release()
225 {
226 // When not owned, release returns NULL.
227 char* buffer = NULL;
228 if (m_data_owned)
229 {
230 buffer = getData();
231 m_data_owned = false;
232 }
233
234 deallocate(); // won't delete because m_data_owned == false
235 return buffer;
236 }
237
~CPacket()238 srt::CPacket::~CPacket()
239 {
240 // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer.
241 // Delete the internal buffer only if it was declared as owned.
242 if (m_data_owned)
243 delete[](char*)m_PacketVector[PV_DATA].data();
244 }
245
246
getLength() const247 size_t srt::CPacket::getLength() const
248 {
249 return m_PacketVector[PV_DATA].size();
250 }
251
setLength(size_t len)252 void srt::CPacket::setLength(size_t len)
253 {
254 m_PacketVector[PV_DATA].setLength(len);
255 }
256
pack(UDTMessageType pkttype,const int32_t * lparam,void * rparam,size_t size)257 void srt::CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size)
258 {
259 // Set (bit-0 = 1) and (bit-1~15 = type)
260 setControl(pkttype);
261 HLOGC(inlog.Debug, log << "pack: type=" << MessageTypeStr(pkttype)
262 << " ARG=" << (lparam ? Sprint(*lparam) : std::string("NULL"))
263 << " [ " << (rparam ? Sprint(*(int32_t*)rparam) : std::string()) << " ]");
264
265 // Set additional information and control information field
266 switch (pkttype)
267 {
268 case UMSG_ACK: //0010 - Acknowledgement (ACK)
269 // ACK packet seq. no.
270 if (NULL != lparam)
271 m_nHeader[SRT_PH_MSGNO] = *lparam;
272
273 // data ACK seq. no.
274 // optional: RTT (microsends), RTT variance (microseconds) advertised flow window size (packets), and estimated link capacity (packets per second)
275 m_PacketVector[PV_DATA].set(rparam, size);
276
277 break;
278
279 case UMSG_ACKACK: //0110 - Acknowledgement of Acknowledgement (ACK-2)
280 // ACK packet seq. no.
281 m_nHeader[SRT_PH_MSGNO] = *lparam;
282
283 // control info field should be none
284 // but "writev" does not allow this
285 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
286
287 break;
288
289 case UMSG_LOSSREPORT: //0011 - Loss Report (NAK)
290 // loss list
291 m_PacketVector[PV_DATA].set(rparam, size);
292
293 break;
294
295 case UMSG_CGWARNING: //0100 - Congestion Warning
296 // control info field should be none
297 // but "writev" does not allow this
298 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
299
300 break;
301
302 case UMSG_KEEPALIVE: //0001 - Keep-alive
303 if (lparam)
304 {
305 // XXX EXPERIMENTAL. Pass the 32-bit integer here.
306 m_nHeader[SRT_PH_MSGNO] = *lparam;
307 }
308 // control info field should be none
309 // but "writev" does not allow this
310 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
311
312 break;
313
314 case UMSG_HANDSHAKE: //0000 - Handshake
315 // control info filed is handshake info
316 m_PacketVector[PV_DATA].set(rparam, size);
317
318 break;
319
320 case UMSG_SHUTDOWN: //0101 - Shutdown
321 // control info field should be none
322 // but "writev" does not allow this
323 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
324
325 break;
326
327 case UMSG_DROPREQ: //0111 - Message Drop Request
328 // msg id
329 m_nHeader[SRT_PH_MSGNO] = *lparam;
330
331 //first seq no, last seq no
332 m_PacketVector[PV_DATA].set(rparam, size);
333
334 break;
335
336 case UMSG_PEERERROR: //1000 - Error Signal from the Peer Side
337 // Error type
338 m_nHeader[SRT_PH_MSGNO] = *lparam;
339
340 // control info field should be none
341 // but "writev" does not allow this
342 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
343
344 break;
345
346 case UMSG_EXT: //0x7FFF - Reserved for user defined control packets
347 // for extended control packet
348 // "lparam" contains the extended type information for bit 16 - 31
349 // "rparam" is the control information
350 m_nHeader[SRT_PH_SEQNO] |= *lparam;
351
352 if (NULL != rparam)
353 {
354 m_PacketVector[PV_DATA].set(rparam, size);
355 }
356 else
357 {
358 m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4);
359 }
360
361 break;
362
363 default:
364 break;
365 }
366 }
367
toNL()368 void srt::CPacket::toNL()
369 {
370 // XXX USE HtoNLA!
371 if (isControl())
372 {
373 for (ptrdiff_t i = 0, n = getLength() / 4; i < n; ++i)
374 *((uint32_t*)m_pcData + i) = htonl(*((uint32_t*)m_pcData + i));
375 }
376
377 // convert packet header into network order
378 uint32_t* p = m_nHeader;
379 for (int j = 0; j < 4; ++j)
380 {
381 *p = htonl(*p);
382 ++p;
383 }
384 }
385
toHL()386 void srt::CPacket::toHL()
387 {
388 // convert back into local host order
389 uint32_t* p = m_nHeader;
390 for (int k = 0; k < 4; ++k)
391 {
392 *p = ntohl(*p);
393 ++p;
394 }
395
396 if (isControl())
397 {
398 for (ptrdiff_t l = 0, n = getLength() / 4; l < n; ++l)
399 *((uint32_t*) m_pcData + l) = ntohl(*((uint32_t*) m_pcData + l));
400 }
401 }
402
403
getPacketVector()404 IOVector* srt::CPacket::getPacketVector()
405 {
406 return m_PacketVector;
407 }
408
getType() const409 UDTMessageType srt::CPacket::getType() const
410 {
411 return UDTMessageType(SEQNO_MSGTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]));
412 }
413
getExtendedType() const414 int srt::CPacket::getExtendedType() const
415 {
416 return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]);
417 }
418
getAckSeqNo() const419 int32_t srt::CPacket::getAckSeqNo() const
420 {
421 // read additional information field
422 // This field is used only in UMSG_ACK and UMSG_ACKACK,
423 // so 'getAckSeqNo' symbolically defines the only use of it
424 // in case of CONTROL PACKET.
425 return m_nHeader[SRT_PH_MSGNO];
426 }
427
getControlFlags() const428 uint16_t srt::CPacket::getControlFlags() const
429 {
430 // This returns exactly the "extended type" value,
431 // which is not used at all in case when the standard
432 // type message is interpreted. This can be used to pass
433 // additional special flags.
434 return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]);
435 }
436
getMsgBoundary() const437 PacketBoundary srt::CPacket::getMsgBoundary() const
438 {
439 return PacketBoundary(MSGNO_PACKET_BOUNDARY::unwrap(m_nHeader[SRT_PH_MSGNO]));
440 }
441
getMsgOrderFlag() const442 bool srt::CPacket::getMsgOrderFlag() const
443 {
444 return 0!= MSGNO_PACKET_INORDER::unwrap(m_nHeader[SRT_PH_MSGNO]);
445 }
446
getMsgSeq(bool has_rexmit) const447 int32_t srt::CPacket::getMsgSeq(bool has_rexmit) const
448 {
449 if ( has_rexmit )
450 {
451 return MSGNO_SEQ::unwrap(m_nHeader[SRT_PH_MSGNO]);
452 }
453 else
454 {
455 return MSGNO_SEQ_OLD::unwrap(m_nHeader[SRT_PH_MSGNO]);
456 }
457 }
458
getRexmitFlag() const459 bool srt::CPacket::getRexmitFlag() const
460 {
461 // return false; //
462 return 0 != MSGNO_REXMIT::unwrap(m_nHeader[SRT_PH_MSGNO]);
463 }
464
getMsgCryptoFlags() const465 EncryptionKeySpec srt::CPacket::getMsgCryptoFlags() const
466 {
467 return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[SRT_PH_MSGNO]));
468 }
469
470 // This is required as the encryption/decryption happens in place.
471 // This is required to clear off the flags after decryption or set
472 // crypto flags after encrypting a packet.
setMsgCryptoFlags(EncryptionKeySpec spec)473 void srt::CPacket::setMsgCryptoFlags(EncryptionKeySpec spec)
474 {
475 int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask;
476 m_nHeader[SRT_PH_MSGNO] = clr_msgno | EncryptionKeyBits(spec);
477 }
478
getMsgTimeStamp() const479 uint32_t srt::CPacket::getMsgTimeStamp() const
480 {
481 // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests
482 return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK;
483 }
484
clone() const485 srt::CPacket* srt::CPacket::clone() const
486 {
487 CPacket* pkt = new CPacket;
488 memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE);
489 pkt->m_pcData = new char[m_PacketVector[PV_DATA].size()];
490 memcpy((pkt->m_pcData), m_pcData, m_PacketVector[PV_DATA].size());
491 pkt->m_PacketVector[PV_DATA].setLength(m_PacketVector[PV_DATA].size());
492
493 return pkt;
494 }
495
496 namespace srt {
497
498 // Useful for debugging
PacketMessageFlagStr(uint32_t msgno_field)499 std::string PacketMessageFlagStr(uint32_t msgno_field)
500 {
501 using namespace std;
502
503 stringstream out;
504
505 static const char* const boundary [] = { "PB_SUBSEQUENT", "PB_LAST", "PB_FIRST", "PB_SOLO" };
506 static const char* const order [] = { "ORD_RELAXED", "ORD_REQUIRED" };
507 static const char* const crypto [] = { "EK_NOENC", "EK_EVEN", "EK_ODD", "EK*ERROR" };
508 static const char* const rexmit [] = { "SN_ORIGINAL", "SN_REXMIT" };
509
510 out << boundary[MSGNO_PACKET_BOUNDARY::unwrap(msgno_field)] << " ";
511 out << order[MSGNO_PACKET_INORDER::unwrap(msgno_field)] << " ";
512 out << crypto[MSGNO_ENCKEYSPEC::unwrap(msgno_field)] << " ";
513 out << rexmit[MSGNO_REXMIT::unwrap(msgno_field)];
514
515 return out.str();
516 }
517
SprintSpecialWord(std::ostream & os,int32_t val)518 inline void SprintSpecialWord(std::ostream& os, int32_t val)
519 {
520 if (val & LOSSDATA_SEQNO_RANGE_FIRST)
521 os << "<" << (val & (~LOSSDATA_SEQNO_RANGE_FIRST)) << ">";
522 else
523 os << val;
524 }
525
526 } // namespace srt
527
528 #if ENABLE_LOGGING
Info()529 std::string srt::CPacket::Info()
530 {
531 std::ostringstream os;
532 os << "TARGET=@" << m_iID << " ";
533
534 if (isControl())
535 {
536 os << "CONTROL: size=" << getLength() << " type=" << MessageTypeStr(getType(), getExtendedType());
537
538 if (getType() == UMSG_HANDSHAKE)
539 {
540 os << " HS: ";
541 // For handshake we already have a parsing method
542 CHandShake hs;
543 hs.load_from(m_pcData, getLength());
544 os << hs.show();
545 }
546 else
547 {
548 // This is a value that some messages use for some purposes.
549 // The "ack seq no" is one of the purposes, used by UMSG_ACK and UMSG_ACKACK.
550 // This is simply the SRT_PH_MSGNO field used as a message number in data packets.
551 os << " ARG: 0x";
552 os << std::hex << getAckSeqNo() << " ";
553 os << std::dec << getAckSeqNo();
554
555 // It would be nice to see the extended packet data, but this
556 // requires strictly a message-dependent interpreter. So let's simply
557 // display all numbers in the array with the following restrictions:
558 // - all data contained in the buffer are considered 32-bit integer
559 // - sign flag will be cleared before displaying, with additional mark
560 size_t wordlen = getLength()/4; // drop any remainder if present
561 int32_t* array = (int32_t*)m_pcData;
562 os << " [ ";
563 for (size_t i = 0; i < wordlen; ++i)
564 {
565 SprintSpecialWord(os, array[i]);
566 os << " ";
567 }
568 os << "]";
569 }
570 }
571 else
572 {
573 // It's hard to extract the information about peer's supported rexmit flag.
574 // This is only a log, nothing crucial, so we can risk displaying incorrect message number.
575 // Declaring that the peer supports rexmit flag cuts off the highest bit from
576 // the displayed number.
577 os << "DATA: size=" << getLength()
578 << " " << BufferStamp(m_pcData, getLength())
579 << " #" << getMsgSeq(true) << " %" << getSeqNo()
580 << " " << MessageFlagStr();
581 }
582
583 return os.str();
584 }
585 #endif
586