COMMS
Template library intended to help with implementation of communication protocols.
Loading...
Searching...
No Matches
SyncSuffixLayer.h
Go to the documentation of this file.
1//
2// Copyright 2025 - 2026 (C). Alex Robenko. All rights reserved.
3//
4// SPDX-License-Identifier: MPL-2.0
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9
12
13#pragma once
14
15#include "comms/cast.h"
17#include "comms/details/tag.h"
19#include "comms/frame/details/SyncSuffixLayerBase.h"
20#include "comms/frame/details/SyncSuffixLayerOptionsParser.h"
22
23#include <algorithm>
24#include <cstddef>
25#include <iterator>
26#include <type_traits>
27#include <utility>
28
29COMMS_MSVC_WARNING_PUSH
30COMMS_MSVC_WARNING_DISABLE(4189) // Disable erroneous initialized but not referenced variable warning
31
32namespace comms
33{
34
35namespace frame
36{
62template <typename TField, typename TNextLayer, typename... TOptions>
63class SyncSuffixLayer : public comms::frame::details::SyncSuffixLayerBase<TField, TNextLayer, TOptions...>
64{
65 using BaseImpl = comms::frame::details::SyncSuffixLayerBase<TField, TNextLayer, TOptions...>;
66 using ParsedOptionsInternal = details::SyncSuffixLayerOptionsParser<TOptions...>;
67
68public:
70 using Field = typename BaseImpl::Field;
71
74 using EscField = typename ParsedOptionsInternal::EscField;
75
77 SyncSuffixLayer() = default;
78
81
84
86 ~SyncSuffixLayer() noexcept = default;
87
89 SyncSuffixLayer& operator=(const SyncSuffixLayer&) = default;
90
92 SyncSuffixLayer& operator=(SyncSuffixLayer&&) = default;
93
96 static constexpr bool hasExtendingClass()
97 {
98 return ParsedOptionsInternal::HasExtendingClass;
99 }
100
103 static constexpr bool hasVerifyBeforeRead()
104 {
105 return ParsedOptionsInternal::HasVerifyBeforeRead;
106 }
107
110 static constexpr bool hasSeekField()
111 {
112 return ParsedOptionsInternal::HasSeekField;
113 }
114
144 template <typename TMsg, typename TIter, typename TNextLayerReader, typename... TExtraValues>
146 Field& field,
147 TMsg& msg,
148 TIter& iter,
149 std::size_t size,
150 TNextLayerReader&& nextLayerReader,
151 TExtraValues... extraValues)
152 {
153 using IterType = typename std::decay<decltype(iter)>::type;
154 static_assert(std::is_same<typename std::iterator_traits<IterType>::iterator_category, std::random_access_iterator_tag>::value,
155 "The read operation is expected to use random access iterator");
156
157 using SeekTag =
158 typename comms::util::LazyShallowConditional<
159 ParsedOptionsInternal::HasSeekField
160 >::template Type<
161 SeekFieldTag,
162 InstantOpTag
163 >;
164
165 return
166 readInternal(
167 field,
168 msg,
169 iter,
170 size,
171 std::forward<TNextLayerReader>(nextLayerReader),
172 SeekTag(),
173 extraValues...);
174 }
175
192 template <typename TMsg, typename TIter, typename TNextLayerWriter>
194 Field& field,
195 const TMsg& msg,
196 TIter& iter,
197 std::size_t size,
198 TNextLayerWriter&& nextLayerWriter) const
199 {
200 using IterType = typename std::decay<decltype(iter)>::type;
201 using Tag = typename std::iterator_traits<IterType>::iterator_category;
202
203 return writeInternal(field, msg, iter, size, std::forward<TNextLayerWriter>(nextLayerWriter), Tag());
204 }
205
206protected:
213 static bool verifyFieldValue(const Field& field)
214 {
215 return field == Field();
216 }
217
224 template <typename TEscField>
225 static bool verifyEscFieldValue(const TEscField& field)
226 {
227 return field == TEscField();
228 }
229
237 static void prepareFieldForWrite(Field& field)
238 {
239 static_cast<void>(field);
240 }
241
242private:
243 template <typename... TParams>
244 using SeekFieldTag = comms::details::tag::Tag1<>;
245
246 template <typename... TParams>
247 using InstantOpTag = comms::details::tag::Tag2<>;
248
249 template <typename... TParams>
250 using VerifyBeforeReadTag = comms::details::tag::Tag3<>;
251
252 template <typename... TParams>
253 using VerifyAfterReadTag = comms::details::tag::Tag4<>;
254
255 template <typename... TParams>
256 using EscapeSupportedTag = comms::details::tag::Tag5<>;
257
258 template <typename... TParams>
259 using NoEscapeTag = comms::details::tag::Tag6<>;
260
261 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
262 ErrorStatus verifyRead(
263 Field& field,
264 TMsg& msg,
265 TIter& iter,
266 std::size_t size,
267 TReader&& nextLayerReader,
268 TExtraValues... extraValues)
269 {
270 auto fromIter = iter;
271 auto* msgPtr = BaseImpl::toMsgPtr(msg);
272 auto fieldLen = Field::minLength();
273
274 static_assert(Field::minLength() == Field::maxLength(), "Cannot verify variable length suffix before reading rest of the message frame");
275 auto& thisObj = BaseImpl::thisLayer();
276 if (msgPtr != nullptr) {
277 fieldLen = thisObj.doFieldLength(*msgPtr);
278 }
279 auto toIter = fromIter + (size - fieldLen);
280
281 auto fieldEs = thisObj.doReadField(msgPtr, field, toIter, fieldLen);
282 if (fieldEs != ErrorStatus::Success) {
283 return fieldEs;
284 }
285
286 bool verified = thisObj.verifyFieldValue(field);
287 if (!verified) {
289 }
290
291 auto es = nextLayerReader.read(msg, iter, size - fieldLen, extraValues...);
292 if (es == ErrorStatus::Success) {
293 iter = toIter;
294 }
295
296 return es;
297 }
298
299 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
300 ErrorStatus readVerify(
301 Field& field,
302 TMsg& msg,
303 TIter& iter,
304 std::size_t size,
305 TReader&& nextLayerReader,
306 TExtraValues... extraValues)
307 {
308 auto fromIter = iter;
309
310 auto es = nextLayerReader.read(msg, iter, size, extraValues...);
311 if ((es == ErrorStatus::NotEnoughData) ||
312 (es == ErrorStatus::ProtocolError)) {
313 return es;
314 }
315
316 auto len = static_cast<std::size_t>(std::distance(fromIter, iter));
317 COMMS_ASSERT(len <= size);
318 auto remSize = size - len;
319 auto* msgPtr = BaseImpl::toMsgPtr(msg);
320 auto& thisObj = BaseImpl::thisLayer();
321 auto fieldEs = thisObj.doReadField(msgPtr, field, iter, remSize);
322 if (fieldEs == ErrorStatus::NotEnoughData) {
323 BaseImpl::updateMissingSize(field, remSize, extraValues...);
324 }
325
326 if (fieldEs != ErrorStatus::Success) {
327 BaseImpl::resetMsg(msg);
328 return fieldEs;
329 }
330
331 bool verified = thisObj.verifyFieldValue(field);
332 if (!verified) {
334 }
335
336 return es;
337 }
338
339 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
340 ErrorStatus seekFieldRead(
341 Field& field,
342 TMsg& msg,
343 TIter& iter,
344 std::size_t size,
345 TReader&& nextLayerReader,
346 TExtraValues... extraValues)
347 {
348 auto& thisObj = BaseImpl::thisLayer();
349 auto* msgPtr = BaseImpl::toMsgPtr(msg);
350
351 auto fromIter = iter;
352 std::size_t consumed = 0U;
353 while (consumed < size) {
354 auto iterTmp = iter;
355 auto remSize = size - consumed;
356
357 auto fieldEs = thisObj.doReadField(msgPtr, field, iterTmp, remSize);
358 if (fieldEs == ErrorStatus::NotEnoughData) {
359 BaseImpl::updateMissingSize(field, remSize, extraValues...);
360 BaseImpl::resetMsg(msg);
361 return fieldEs;
362 }
363
364 if ((fieldEs == ErrorStatus::Success) &&
365 (!fieldEscapedInternal(fromIter, iter)) &&
366 (thisObj.verifyFieldValue(field))) {
367 // Set iter to point after field
368 iter = iterTmp;
369 break;
370 }
371
372 ++iter;
373 ++consumed;
374 }
375
376 if (size <= consumed) {
377 // Field hasn't been recognized
378 return ErrorStatus::NotEnoughData;
379 }
380
381 // iter is already past the sync field
382 return nextLayerReader.read(msg, fromIter, consumed, extraValues...);
383 }
384
385 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
386 ErrorStatus readInternal(
387 Field& field,
388 TMsg& msg,
389 TIter& iter,
390 std::size_t size,
391 TReader&& nextLayerReader,
392 SeekFieldTag<>,
393 TExtraValues... extraValues)
394 {
395 return
396 seekFieldRead(
397 field,
398 msg,
399 iter,
400 size,
401 std::forward<TReader>(nextLayerReader),
402 extraValues...);
403 }
404
405 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
406 ErrorStatus readInternal(
407 Field& field,
408 TMsg& msg,
409 TIter& iter,
410 std::size_t size,
411 TReader&& nextLayerReader,
412 InstantOpTag<>,
413 TExtraValues... extraValues)
414 {
415 using VerifyTag =
416 typename comms::util::LazyShallowConditional<
417 ParsedOptionsInternal::HasVerifyBeforeRead
418 >::template Type<
419 VerifyBeforeReadTag,
420 VerifyAfterReadTag
421 >;
422
423 return
424 readInternal(
425 field,
426 msg,
427 iter,
428 size,
429 std::forward<TReader>(nextLayerReader),
430 VerifyTag(),
431 extraValues...);
432 }
433
434 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
435 ErrorStatus readInternal(
436 Field& field,
437 TMsg& msg,
438 TIter& iter,
439 std::size_t size,
440 TReader&& nextLayerReader,
441 VerifyBeforeReadTag<>,
442 TExtraValues... extraValues)
443 {
444 return
445 verifyRead(
446 field,
447 msg,
448 iter,
449 size,
450 std::forward<TReader>(nextLayerReader),
451 extraValues...);
452 }
453
454 template <typename TMsg, typename TIter, typename TReader, typename... TExtraValues>
455 ErrorStatus readInternal(
456 Field& field,
457 TMsg& msg,
458 TIter& iter,
459 std::size_t size,
460 TReader&& nextLayerReader,
461 VerifyAfterReadTag<>,
462 TExtraValues... extraValues)
463 {
464 return
465 readVerify(
466 field,
467 msg,
468 iter,
469 size,
470 std::forward<TReader>(nextLayerReader),
471 extraValues...);
472 }
473
474 template <typename TMsg, typename TIter, typename TWriter>
475 ErrorStatus writeInternalRandomAccess(
476 Field& field,
477 const TMsg& msg,
478 TIter& iter,
479 std::size_t size,
480 TWriter&& nextLayerWriter) const
481 {
482 auto fromIter = iter;
483 auto es = nextLayerWriter.write(msg, iter, size);
484 if ((es != comms::ErrorStatus::Success) &&
486 return es;
487 }
488
489 COMMS_ASSERT(fromIter <= iter);
490 auto len = static_cast<std::size_t>(std::distance(fromIter, iter));
491 auto remSize = size - len;
492 auto& thisObj = BaseImpl::thisLayer();
493
494 thisObj.prepareFieldForWrite(field);
495 auto esTmp = thisObj.doWriteField(&msg, field, iter, remSize);
496 if (esTmp != comms::ErrorStatus::Success) {
497 return esTmp;
498 }
499
500 return es;
501 }
502
503 template <typename TMsg, typename TIter, typename TWriter>
504 ErrorStatus writeInternalOutput(
505 Field& field,
506 const TMsg& msg,
507 TIter& iter,
508 std::size_t size,
509 TWriter&& nextLayerWriter) const
510 {
511 auto& thisObj = BaseImpl::thisLayer();
512 auto fieldLen = thisObj.doFieldLength(msg);
513 auto es = nextLayerWriter.write(msg, iter, size - fieldLen);
514 if ((es != comms::ErrorStatus::Success) &&
516 return es;
517 }
518
519 auto esTmp = thisObj.doWriteField(&msg, field, iter, fieldLen);
520 static_cast<void>(esTmp);
523 }
524
525 template <typename TMsg, typename TIter, typename TWriter>
526 ErrorStatus writeInternal(
527 Field& field,
528 const TMsg& msg,
529 TIter& iter,
530 std::size_t size,
531 TWriter&& nextLayerWriter,
532 std::random_access_iterator_tag) const
533 {
534 return writeInternalRandomAccess(field, msg, iter, size, std::forward<TWriter>(nextLayerWriter));
535 }
536
537 template <typename TMsg, typename TIter, typename TWriter>
538 ErrorStatus writeInternal(
539 Field& field,
540 const TMsg& msg,
541 TIter& iter,
542 std::size_t size,
543 TWriter&& nextLayerWriter,
544 std::output_iterator_tag) const
545 {
546 return writeInternalOutput(field, msg, iter, size, std::forward<TWriter>(nextLayerWriter));
547 }
548
549 template <typename TMsg, typename TIter>
550 ErrorStatus fieldUpdateInternal(const TMsg* msgPtr, TIter from, TIter to, std::size_t size, Field& field) const
551 {
552 COMMS_ASSERT(from <= to);
553 auto len = static_cast<std::size_t>(std::distance(from, to));
554 auto& thisObj = BaseImpl::thisLayer();
555 if (msgPtr != nullptr) {
556 COMMS_ASSERT(len == (size - thisObj.doFieldLength(*msgPtr)));
557 }
558 else {
559 COMMS_ASSERT(len == (size - Field::maxLength()));
560 }
561 auto remSize = size - len;
562
563 bool checksumValid = false;
564 auto checksum =
565 thisObj.calculateSyncSuffix(
566 msgPtr,
567 from,
568 len,
569 checksumValid);
570
571 if (!checksumValid) {
573 }
574
575 thisObj.prepareFieldForWrite(checksum, msgPtr, field);
576 return thisObj.doWriteField(msgPtr, field, to, remSize);
577 }
578
579 template <typename TIter>
580 bool fieldEscapedInternal(TIter from, TIter to, NoEscapeTag<>)
581 {
582 static_cast<void>(from);
583 static_cast<void>(to);
584 return false;
585 }
586
587 template <typename TIter>
588 bool fieldEscapedInternal(TIter from, TIter to, EscapeSupportedTag<>)
589 {
590 unsigned escCount = 0U;
591 auto& thisObj = BaseImpl::thisLayer();
592
593 while (from != to) {
594 auto dist = static_cast<std::size_t>(std::distance(from, to));
595 dist = std::min(dist, EscField::maxLength());
596 if (dist < EscField::minLength()) {
597 break;
598 }
599
600 auto maxLenIter = to;
601 std::advance(maxLenIter, -static_cast<int>(dist));
602
603 auto iter = to;
604 std::advance(iter, -static_cast<int>(EscField::minLength()));
605 auto prevCount = escCount;
606 while (true) {
607 auto iterTmp = iter;
608 EscField escField;
609 auto len = static_cast<std::size_t>(std::distance(iterTmp, to));
610 auto es = escField.read(iterTmp, len);
611 if ((es == comms::ErrorStatus::Success) &&
612 (iterTmp == to) &&
613 (thisObj.verifyEscFieldValue(escField))) {
614 ++escCount;
615 break;
616 }
617
618 if (iter == maxLenIter) {
619 break;
620 }
621
622 std::advance(iter, -1);
623 }
624
625 if (prevCount == escCount) {
626 // Failed to detect escField
627 break;
628 }
629
630 // esc field was detected, trying again
631 to = iter;
632 }
633
634 // Escaped only if odd number of esc field is detected
635 return (escCount & 0x1) != 0U;
636 }
637
638 template <typename TIter>
639 bool fieldEscapedInternal(TIter from, TIter to)
640 {
641 using EscTag =
642 typename comms::util::LazyShallowConditional<
643 std::is_same<EscField, void>::value
644 >::template Type<
645 NoEscapeTag,
646 EscapeSupportedTag
647 >;
648
649 return fieldEscapedInternal(from, to, EscTag());
650 }
651
652 static_assert(!(hasVerifyBeforeRead() && hasSeekField()),
653 "Usage of both comms::option::def::FrameLayerVerifyBeforeRead and comms::option::def::FrameLayerSeekField at the same time are not allowed");
654};
655
656namespace details
657{
658template <typename T>
659struct SyncSuffixLayerCheckHelper
660{
661 static const bool Value = false;
662};
663
664template <typename TField, typename TNextLayer, typename... TOptions>
665struct SyncSuffixLayerCheckHelper<SyncSuffixLayer<TField, TNextLayer, TOptions...> >
666{
667 static const bool Value = true;
668};
669
670} // namespace details
671
675template <typename T>
676constexpr bool isSyncSuffixLayer()
677{
678 return details::SyncSuffixLayerCheckHelper<T>::Value;
679}
680
681} // namespace frame
682
683} // namespace comms
684
685COMMS_MSVC_WARNING_POP
#define COMMS_ASSERT(expr)
Generic assert macro.
Definition Assert.h:170
Contains various compiler related definitions.
Contains definition of comms::field::IntValue.
Contains definition of various casts.
Base class to all the field classes.
Definition Field.h:36
Frame layer that uses "sync" field as a suffix (after message payload) to all the preceding data writ...
Definition SyncSuffixLayer.h:64
static bool verifyFieldValue(const Field &field)
Verify the validity of the field.
Definition SyncSuffixLayer.h:213
ErrorStatus doWrite(Field &field, const TMsg &msg, TIter &iter, std::size_t size, TNextLayerWriter &&nextLayerWriter) const
Customized write functionality, invoked by write().
Definition SyncSuffixLayer.h:193
SyncSuffixLayer(SyncSuffixLayer &&)=default
Move constructor.
SyncSuffixLayer(const SyncSuffixLayer &)=default
Copy constructor.
static constexpr bool hasVerifyBeforeRead()
Compile time inquiry of whether comms::option::def::FrameLayerVerifyBeforeRead options has been used.
Definition SyncSuffixLayer.h:103
ErrorStatus doRead(Field &field, TMsg &msg, TIter &iter, std::size_t size, TNextLayerReader &&nextLayerReader, TExtraValues... extraValues)
Customized read functionality, invoked by read().
Definition SyncSuffixLayer.h:145
SyncSuffixLayer()=default
Default constructor.
static bool verifyEscFieldValue(const TEscField &field)
Verify the validity of the escapte field provided via comms::option::def::FrameLayerSeekField option.
Definition SyncSuffixLayer.h:225
static constexpr bool hasSeekField()
Compile time inquiry of whether comms::option::def::FrameLayerSeekField options has been used.
Definition SyncSuffixLayer.h:110
typename ParsedOptionsInternal::EscField EscField
Type of the escape field provided via comms::option::def::FrameLayerSeekField option.
Definition SyncSuffixLayer.h:74
~SyncSuffixLayer() noexcept=default
Destructor.
typename BaseImpl::Field Field
Type of the field object used to read/write checksum value.
Definition SyncSuffixLayer.h:70
constexpr bool isSyncSuffixLayer()
Compile time check of whether the provided type is a variant of SyncSuffixLayer.
Definition SyncSuffixLayer.h:676
static void prepareFieldForWrite(Field &field)
Prepare field for writing.
Definition SyncSuffixLayer.h:237
Main namespace for all classes / functions of COMMS library.
ErrorStatus
Error statuses reported by the Communication module.
Definition ErrorStatus.h:19
@ Success
Used to indicate successful outcome of the operation.
Replacement to some types from standard type_traits.