Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
All registered in `test/test_runner.cpp`.

### Changed
- `TinyLink` telemetry now uses the saturating `Stats::increment(TelemetryEvent)` API for every
counter, so engine counters never wrap. `sendAck()` now returns `false` (writing nothing) until
an application frame has been accepted; the acknowledged sequence survives callback delivery,
`read()` and `flush()` and is invalidated by construction, `reset()`, `begin()`, a peer handshake
request, and an unsupported-version handshake.
- `TinyLink<T, Adapter>` now requires payload type `T` to be trivially copyable, enforced with a
`static_assert` on every toolchain including AVR (compiler intrinsic where `<type_traits>` is
unavailable). Trivially copyable is only a minimum object-safety requirement; raw object memory
Expand Down
61 changes: 43 additions & 18 deletions src/TinyLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ class TinyLink {
uint8_t _nextTransmitSequence;
uint8_t _pendingTransmitSequence;
uint8_t _lastReceivedSequence;
bool _hasReceivedSequence;
unsigned long _lastHandshakeSent;
unsigned long _lastTransmit;
unsigned long _lastReceiveByte;
Expand All @@ -155,6 +156,8 @@ class TinyLink {
bool _hasNew;
bool _outputDirty;

friend struct TinyLinkStatsAccess;

TinyLink(const TinyLink&) = delete;
TinyLink& operator=(const TinyLink&) = delete;

Expand All @@ -180,7 +183,7 @@ class TinyLink {
plain, sizeof(plain));

if (packed.status != packet::PacketStatus::Success) {
++_stats.packetEncodeFailures;
_stats.increment(TelemetryEvent::PacketEncodeFailed);
return false;
}

Expand All @@ -189,7 +192,7 @@ class TinyLink {
plain, packed.length, encoded, sizeof(encoded));

if (cobs.status != codec::CobsStatus::Success) {
++_stats.cobsEncodeFailures;
_stats.increment(TelemetryEvent::CobsEncodeFailed);
return false;
}

Expand Down Expand Up @@ -230,7 +233,7 @@ class TinyLink {
if (encode_handshake_message(
message, payload, sizeof(payload)) !=
HandshakeMessageCodecStatus::Success) {
++_stats.invalidHandshakes;
_stats.increment(TelemetryEvent::InvalidHandshake);
return false;
}

Expand Down Expand Up @@ -261,12 +264,12 @@ class TinyLink {

if (result.status != packet::PacketStatus::Success) {
if (result.status == packet::PacketStatus::ChecksumMismatch) {
++_stats.checksumMismatches;
_stats.increment(TelemetryEvent::ChecksumMismatch);
} else if (result.status == packet::PacketStatus::LengthMismatch ||
result.status == packet::PacketStatus::FrameTooShort) {
++_stats.packetLengthMismatches;
_stats.increment(TelemetryEvent::PacketLengthMismatch);
} else {
++_stats.payloadSizeMismatches;
_stats.increment(TelemetryEvent::PayloadSizeMismatch);
}
return;
}
Expand Down Expand Up @@ -297,13 +300,19 @@ class TinyLink {
decode_handshake_message(payload, payloadLength, message);

if (status != HandshakeMessageCodecStatus::Success) {
++_stats.invalidHandshakes;
_stats.increment(TelemetryEvent::InvalidHandshake);
if (status == HandshakeMessageCodecStatus::UnsupportedVersion) {
_connectionState = ConnectionState::Incompatible;
_hasReceivedSequence = false;
}
return;
}

if (message.kind == HandshakeKind::Request) {
// A peer restarting its handshake makes the old frame stale.
_hasReceivedSequence = false;
}

if (_onHandshakeReceived) {
_onHandshakeReceived(message);
}
Expand All @@ -329,13 +338,13 @@ class TinyLink {
decode_ack_message(payload, payloadLength, result);

if (status != AckMessageCodecStatus::Success) {
++_stats.invalidAckResults;
_stats.increment(TelemetryEvent::InvalidAckResult);
return;
}

if (_transmitState != TransmitState::AwaitingAck ||
sequence != _pendingTransmitSequence) {
++_stats.unexpectedSequences;
_stats.increment(TelemetryEvent::UnexpectedSequence);
return;
}

Expand All @@ -353,7 +362,7 @@ class TinyLink {

if (decode_log_message(payload, payloadLength, message) !=
LogMessageCodecStatus::Success) {
++_stats.payloadSizeMismatches;
_stats.increment(TelemetryEvent::PayloadSizeMismatch);
return;
}

Expand All @@ -368,19 +377,20 @@ class TinyLink {
size_t payloadLength)
{
if (!is_connected(_connectionState)) {
++_stats.unexpectedMessageTypes;
_stats.increment(TelemetryEvent::UnexpectedMessageType);
return;
}

if (payloadLength != sizeof(T)) {
++_stats.payloadSizeMismatches;
_stats.increment(TelemetryEvent::PayloadSizeMismatch);
return;
}

memcpy(&_mailbox, payload, sizeof(T));
_lastReceivedSequence = sequence;
_hasReceivedSequence = true;
_hasNew = true;
++_stats.packetsReceived;
_stats.increment(TelemetryEvent::PacketReceived);

if (_onDataReceived) {
_onDataReceived(_mailbox);
Expand All @@ -406,7 +416,7 @@ class TinyLink {
_receiveState = ReceiveState::WaitingForFrame;

if (decoded.status != codec::CobsStatus::Success) {
++_stats.cobsDecodeFailures;
_stats.increment(TelemetryEvent::CobsDecodeFailed);
return;
}

Expand Down Expand Up @@ -434,7 +444,7 @@ class TinyLink {
}

if (_encodedReceiveLength >= sizeof(_encodedReceive)) {
++_stats.receiveOverflows;
_stats.increment(TelemetryEvent::ReceiveOverflow);
_encodedReceiveLength = 0;
_receiveState = ReceiveState::DiscardingFrame;
return;
Expand All @@ -460,6 +470,7 @@ class TinyLink {
_nextTransmitSequence(0),
_pendingTransmitSequence(0),
_lastReceivedSequence(0),
_hasReceivedSequence(false),
_lastHandshakeSent(0),
_lastTransmit(0),
_lastReceiveByte(0),
Expand Down Expand Up @@ -500,6 +511,7 @@ class TinyLink {
{
_handshakeEnabled = true;
_connectionState = ConnectionState::Connecting;
_hasReceivedSequence = false;
return send_handshake(HandshakeKind::Request);
}

Expand All @@ -515,6 +527,7 @@ class TinyLink {
_nextTransmitSequence = 0;
_pendingTransmitSequence = 0;
_lastReceivedSequence = 0;
_hasReceivedSequence = false;
}

/** @brief Process timers and all currently available transport bytes. */
Expand All @@ -534,14 +547,14 @@ class TinyLink {

if (_receiveState == ReceiveState::ReceivingFrame &&
now - _lastReceiveByte >= _timeout) {
++_stats.interByteTimeouts;
_stats.increment(TelemetryEvent::InterByteTimeout);
_encodedReceiveLength = 0;
_receiveState = ReceiveState::WaitingForFrame;
}

if (_transmitState == TransmitState::AwaitingAck &&
now - _lastTransmit >= _timeout) {
++_stats.ackTimeouts;
_stats.increment(TelemetryEvent::AckTimeout);
_transmitState = TransmitState::Ready;
}

Expand Down Expand Up @@ -615,9 +628,21 @@ class TinyLink {
return true;
}

/** @brief Acknowledge the most recently accepted application frame. */
/**
* @brief Acknowledge the most recently accepted application frame.
*
* Returns false without writing until an application frame has been
* accepted. The acknowledged sequence stays valid across callback
* delivery, read() and flush(). It is invalidated by construction,
* reset(), begin(), a peer handshake request (peer restart) and an
* unsupported handshake version (Incompatible).
*/
bool sendAck(AckResult result = AckResult::Ok)
{
if (!_hasReceivedSequence) {
return false;
}

uint8_t payload[ACK_MESSAGE_WIRE_SIZE] = {};
if (encode_ack_message(result, payload, sizeof(payload)) !=
AckMessageCodecStatus::Success) {
Expand Down
Loading
Loading