Skip to content

Commit

Permalink
kocherga_serial: update header format
Browse files Browse the repository at this point in the history
  • Loading branch information
coderkalyan committed Mar 30, 2024
1 parent 69e2131 commit 5fc7c84
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 157 deletions.
83 changes: 81 additions & 2 deletions kocherga/kocherga.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,87 @@ auto getRandomByte() -> std::uint8_t;

// --------------------------------------------------------------------------------------------------------------------

namespace detail
{

static constexpr uint8_t BitsPerByte = 8U;

/// Size-optimized implementation of CRC16-CCITT
class CRC16
{
public:
static constexpr std::size_t Size = 2;

void update(const std::uint8_t b) noexcept
{
value_ ^= static_cast<std::uint16_t>(b << BitsPerByte);
for (auto i = 0U; i < BitsPerByte; i++)
{
value_ = (value_ << 1U) ^ (((value_ & Top) != 0U) ? Poly : 0U); // NOLINT
}
}

[[nodiscard]] auto get() const noexcept { return value_; }

[[nodiscard]] auto getBytes() const noexcept -> std::array<std::uint8_t, Size>
{
const auto x = get();
return {
static_cast<std::uint8_t>(x >> (BitsPerByte * 0U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 1U)),
};
}

[[nodiscard]] auto isResidueCorrect() const noexcept { return value_ == Residue; }

private:
static constexpr std::uint16_t Initial = 0xFFFFU;
static constexpr std::uint16_t Poly = 0x1021U;
static constexpr std::uint16_t Top = 0x8000U;
static constexpr std::uint16_t Residue = 0x0000U;

std::uint16_t value_ = Initial;
};

/// Size-optimized implementation of CRC32-C (Castagnoli).
class CRC32C
{
public:
static constexpr std::size_t Size = 4;

void update(const std::uint8_t b) noexcept
{
value_ ^= static_cast<std::uint32_t>(b);
for (auto i = 0U; i < BitsPerByte; i++)
{
value_ = ((value_ & 1U) != 0) ? ((value_ >> 1U) ^ ReflectedPoly) : (value_ >> 1U); // NOLINT
}
}

[[nodiscard]] auto get() const noexcept { return value_ ^ Xor; }

[[nodiscard]] auto getBytes() const noexcept -> std::array<std::uint8_t, Size>
{
const auto x = get();
return {
static_cast<std::uint8_t>(x >> (BitsPerByte * 0U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 1U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 2U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 3U)),
};
}

[[nodiscard]] auto isResidueCorrect() const noexcept { return value_ == Residue; }

private:
static constexpr std::uint32_t Xor = 0xFFFF'FFFFUL;
static constexpr std::uint32_t ReflectedPoly = 0x82F6'3B78UL;
static constexpr std::uint32_t Residue = 0xB798'B438UL;

std::uint32_t value_ = Xor;
};
} // namespace detail

/// This is used to verify integrity of the application and other data.
/// Note that the firmware CRC verification is a computationally expensive process that needs to be completed
/// in a limited time interval, which should be minimized. This class has been carefully manually optimized to
Expand Down Expand Up @@ -307,8 +388,6 @@ class CRC64
/// Internal use only.
namespace detail
{
static constexpr auto BitsPerByte = 8U;

static constexpr std::chrono::microseconds DefaultTransferIDTimeout{2'000'000}; ///< Default taken from Specification.

/// Detects the application in the ROM, verifies its integrity, and retrieves the information about it.
Expand Down
118 changes: 45 additions & 73 deletions kocherga/kocherga_serial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,23 @@
#pragma once

#include "kocherga.hpp"
#include <cstdio>
#include <variant>

namespace kocherga::serial
{
namespace detail
{
using kocherga::detail::BitsPerByte; // NOSONAR
using kocherga::detail::CRC16; // NOSONAR
using kocherga::detail::CRC32C; // NOSONAR

constexpr std::uint8_t FrameDelimiter = 0x00; ///< Zeros cannot occur inside frames thanks to COBS encoding.

/// Reference values to check the header against.
static constexpr std::uint8_t FrameFormatVersion = 0;
static constexpr std::uint8_t FrameFormatVersion = 1;
static constexpr std::array<std::uint8_t, 4> FrameIndexEOTReference{0, 0, 0, 0x80};

/// Size-optimized implementation of CRC32-C (Castagnoli).
class CRC32C
{
public:
static constexpr std::size_t Size = 4;

void update(const std::uint8_t b) noexcept
{
value_ ^= static_cast<std::uint32_t>(b);
for (auto i = 0U; i < BitsPerByte; i++)
{
value_ = ((value_ & 1U) != 0) ? ((value_ >> 1U) ^ ReflectedPoly) : (value_ >> 1U); // NOLINT
}
}

[[nodiscard]] auto get() const noexcept { return value_ ^ Xor; }

[[nodiscard]] auto getBytes() const noexcept -> std::array<std::uint8_t, Size>
{
const auto x = get();
return {
static_cast<std::uint8_t>(x >> (BitsPerByte * 0U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 1U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 2U)),
static_cast<std::uint8_t>(x >> (BitsPerByte * 3U)),
};
}

[[nodiscard]] auto isResidueCorrect() const noexcept { return value_ == Residue; }

private:
static constexpr std::uint32_t Xor = 0xFFFF'FFFFUL;
static constexpr std::uint32_t ReflectedPoly = 0x82F6'3B78UL;
static constexpr std::uint32_t Residue = 0xB798'B438UL;

std::uint32_t value_ = Xor;
};
static constexpr std::array<std::uint8_t, 2> UserData{0, 0};

/// New instance shall be created per encoded frame.
/// ByteWriter is of type (std::uint8_t) -> bool, returns true on success.
Expand Down Expand Up @@ -205,12 +171,12 @@ struct Transfer
{
struct Metadata
{
static constexpr std::uint8_t DefaultPriority = 6U; // Second to lowest.
static constexpr NodeID AnonymousNodeID = 0xFFFFU;
static constexpr PortID DataSpecServiceFlag = 0x8000U;
static constexpr PortID DataSpecResponseFlag = 0x4000U;
static constexpr std::uint8_t DefaultPriority = 6U; // Second to lowest.
static constexpr NodeID AnonymousNodeID = 0xFFFFU;
static constexpr PortID DataSpecServiceFlag = 0x8000U;
static constexpr PortID DataSpecRequestFlag = 0x4000U;
static constexpr auto DataSpecServiceIDMask =
static_cast<PortID>(~static_cast<PortID>(DataSpecServiceFlag | DataSpecResponseFlag));
static_cast<PortID>(~static_cast<PortID>(DataSpecServiceFlag | DataSpecRequestFlag));

std::uint8_t priority = DefaultPriority;
NodeID source = AnonymousNodeID;
Expand All @@ -220,7 +186,7 @@ struct Transfer

[[nodiscard]] auto isRequest() const noexcept -> std::optional<PortID>
{
if (((data_spec & DataSpecServiceFlag) != 0) && ((data_spec & DataSpecResponseFlag) == 0))
if (((data_spec & DataSpecServiceFlag) != 0) && ((data_spec & DataSpecRequestFlag) != 0))
{
return data_spec & DataSpecServiceIDMask;
}
Expand All @@ -229,7 +195,7 @@ struct Transfer

[[nodiscard]] auto isResponse() const noexcept -> std::optional<PortID>
{
if (((data_spec & DataSpecServiceFlag) != 0) && ((data_spec & DataSpecResponseFlag) != 0))
if (((data_spec & DataSpecServiceFlag) != 0) && ((data_spec & DataSpecRequestFlag) == 0))
{
return data_spec & DataSpecServiceIDMask;
}
Expand All @@ -256,7 +222,7 @@ class StreamParser
const auto dec = decoder_.feed(stream_byte);
if (std::holds_alternative<COBSDecoder::Delimiter>(dec))
{
if (inside_ && (offset_ >= TotalOverheadSize) && crc_.isResidueCorrect() && isMetaValid())
if (inside_ && (offset_ >= TotalOverheadSize) && transferCrc_.isResidueCorrect() && isMetaValid())
{
out = Transfer{
meta_,
Expand All @@ -269,13 +235,14 @@ class StreamParser
}
else if (const std::uint8_t* const decoded_byte = std::get_if<std::uint8_t>(&dec))
{
crc_.update(*decoded_byte);
if (offset_ < HeaderSize)
{
headerCrc_.update(*decoded_byte);
acceptHeader(*decoded_byte);
}
else
{
transferCrc_.update(*decoded_byte);
const auto buf_offset = offset_ - HeaderSize;
if (buf_offset < buf_.size())
{
Expand All @@ -299,10 +266,11 @@ class StreamParser
void reset() noexcept
{
decoder_.reset();
offset_ = 0;
inside_ = false;
crc_ = {};
meta_ = {};
offset_ = 0;
inside_ = false;
headerCrc_ = {};
transferCrc_ = {};
meta_ = {};
}

private:
Expand All @@ -327,7 +295,7 @@ class StreamParser
}
if (offset_ == (HeaderSize - 1U))
{
if (!crc_.isResidueCorrect())
if (!headerCrc_.isResidueCorrect())
{
reset(); // Header CRC error.
}
Expand All @@ -336,7 +304,6 @@ class StreamParser
// also, the amount of dynamic memory that needs to be allocated for the payload would also be determined
// at this moment. The main purpose of the header CRC is to permit such early-stage frame processing.
// This specialized implementation requires none of that.
crc_ = {};
}
}

Expand Down Expand Up @@ -366,21 +333,22 @@ class StreamParser
return meta_.destination == Transfer::Metadata::AnonymousNodeID;
}

static constexpr std::size_t HeaderSize = 32;
static constexpr std::size_t HeaderSize = 24;
static constexpr std::size_t TotalOverheadSize = HeaderSize + CRC32C::Size;
// Header field offsets.
static constexpr std::size_t OffsetVersion = 0;
static constexpr std::size_t OffsetPriority = 1;
static constexpr std::pair<std::size_t, std::size_t> OffsetSource{2, 3};
static constexpr std::pair<std::size_t, std::size_t> OffsetDestination{4, 5};
static constexpr std::pair<std::size_t, std::size_t> OffsetDataSpec{6, 7};
static constexpr std::pair<std::size_t, std::size_t> OffsetTransferID{16, 23};
static constexpr std::pair<std::size_t, std::size_t> OffsetFrameIndexEOT{24, 27};
static constexpr std::pair<std::size_t, std::size_t> OffsetTransferID{8, 15};
static constexpr std::pair<std::size_t, std::size_t> OffsetFrameIndexEOT{16, 19};

COBSDecoder decoder_;
std::size_t offset_ = 0;
bool inside_ = false;
CRC32C crc_;
CRC16 headerCrc_;
CRC32C transferCrc_;
Transfer::Metadata meta_;
std::array<std::uint8_t, MaxPayloadSize + CRC32C::Size> buf_{};
};
Expand All @@ -392,20 +360,17 @@ template <typename Callback>
[[nodiscard]] inline auto transmit(const Callback& send_byte, const Transfer& tr) -> bool
{
COBSEncoder<const Callback&> encoder(send_byte);
CRC32C crc;
const auto out = [&crc, &encoder](const std::uint8_t b) {
crc.update(b);
CRC16 headerCrc;
const auto out = [&headerCrc, &encoder](const std::uint8_t b) {
headerCrc.update(b);
return encoder.push(b);
};
const auto out2 = [&out](const std::uint16_t bb) {
return out(static_cast<std::uint8_t>(bb)) && out(static_cast<std::uint8_t>(bb >> BitsPerByte));
};

bool ok = out(FrameFormatVersion) && out(tr.meta.priority) && //
out2(tr.meta.source) && out2(tr.meta.destination) && out2(tr.meta.data_spec);
for (auto i = 0U; i < sizeof(std::uint64_t); i++)
{
ok = ok && out(0);
}
auto tmp_transfer_id = tr.meta.transfer_id;
for (auto i = 0U; i < sizeof(std::uint64_t); i++)
{
Expand All @@ -416,26 +381,33 @@ template <typename Callback>
{
ok = ok && out(x);
}
for (const auto x : crc.getBytes())
for (const auto x : UserData)
{
ok = ok && out(x);
}
crc = {}; // Now it's the payload CRC.
const auto headerBytes = headerCrc.getBytes();
for (auto it = headerBytes.rbegin(); it != headerBytes.rend(); it++)
{
ok = ok && out(*it);
}

CRC32C transferCrc;
{
const auto* ptr = tr.payload;
for (std::size_t i = 0U; i < tr.payload_len; i++)
{
ok = ok && out(*ptr);
transferCrc.update(*ptr);
ok = ok && encoder.push(*ptr);
++ptr;
if (!ok)
{
break;
}
}
}
for (const auto x : crc.getBytes())
for (const auto x : transferCrc.getBytes())
{
ok = ok && out(x);
ok = ok && encoder.push(x);
}
return ok && encoder.end();
}
Expand Down Expand Up @@ -572,8 +544,7 @@ class SerialNode : public kocherga::INode
meta.source = *local_node_id_;
meta.destination = tr.meta.source;
meta.data_spec = static_cast<PortID>(*req_id) |
static_cast<PortID>(detail::Transfer::Metadata::DataSpecServiceFlag |
detail::Transfer::Metadata::DataSpecResponseFlag);
static_cast<PortID>(detail::Transfer::Metadata::DataSpecServiceFlag);
meta.transfer_id = tr.meta.transfer_id;
for (auto i = 0U; i < service_multiplication_factor_; i++)
{
Expand Down Expand Up @@ -615,7 +586,8 @@ class SerialNode : public kocherga::INode
detail::Transfer::Metadata meta{};
meta.source = *local_node_id_;
meta.destination = server_node_id;
meta.data_spec = static_cast<PortID>(service_id) | detail::Transfer::Metadata::DataSpecServiceFlag;
meta.data_spec = static_cast<PortID>(service_id) | detail::Transfer::Metadata::DataSpecServiceFlag |
detail::Transfer::Metadata::DataSpecRequestFlag;
meta.transfer_id = transfer_id;
bool transmit_ok = false; // Optimistic aggregation: one successful transmission is considered a success.
for (auto i = 0U; i < service_multiplication_factor_; i++)
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/serial/test_misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,31 @@
#include "catch.hpp"
#include <numeric>

TEST_CASE("serial::CRC")
TEST_CASE("serial::CRC3216-CCITT")
{
kocherga::serial::detail::CRC16 crc;
crc.update(static_cast<std::uint8_t>('1'));
crc.update(static_cast<std::uint8_t>('2'));
crc.update(static_cast<std::uint8_t>('3'));
crc.update(static_cast<std::uint8_t>('4'));
crc.update(static_cast<std::uint8_t>('5'));
crc.update(static_cast<std::uint8_t>('6'));
crc.update(static_cast<std::uint8_t>('7'));
crc.update(static_cast<std::uint8_t>('8'));
crc.update(static_cast<std::uint8_t>('9'));

REQUIRE(0x29B1U == crc.get());
REQUIRE(crc.getBytes().at(0) == 0xB1U);
REQUIRE(crc.getBytes().at(1) == 0x29U);

REQUIRE(!crc.isResidueCorrect());
crc.update(0x29U);
crc.update(0xB1U);
REQUIRE(crc.isResidueCorrect());
REQUIRE(0x0000U == crc.get());
}

TEST_CASE("serial::CRC32C")
{
kocherga::serial::detail::CRC32C crc;
crc.update(static_cast<std::uint8_t>('1'));
Expand Down
Loading

0 comments on commit 5fc7c84

Please sign in to comment.