Skip to content

Commit 156ef7c

Browse files
lexus2kCopilot
andcommitted
Implement missing HDLC ABM protocol features
Add DM (Disconnect Mode) frame support: - Send DM response when I/S-frames arrive in disconnected state instead of auto-reconnecting with SABM (per ISO 13239) - Handle incoming DM frames by transitioning to disconnected state Complete FRMR (Frame Reject) handler: - On FRMR reception, disconnect and initiate link reset via SABM Implement RSET (Reset) handler: - Reset sequence numbers (V(S), V(R)) on RSET reception - Respond with UA frame as required by spec Add RNR (Receive Not Ready) S-frame recognition: - Confirm frames up to N(R) but do not trigger further sends - Enables peer busy flow control signaling Update proto logger to recognize DM and RNR frame subtypes. Update and expand unit tests: - Rename ABM_SendSABMOnIFrameIfDisconnected to ABM_SendDMOnIFrameIfDisconnected - Add ABM_SendDMOnSFrameIfDisconnected - Add ABM_FRMRReceiveTriggersReconnect - Add ABM_RSETResetsSequenceNumbers - Add ABM_RNRPausesTransmission - Add ABM_DMReceiveDisconnects Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 81f4358 commit 156ef7c

6 files changed

Lines changed: 146 additions & 18 deletions

File tree

src/proto/fd/tiny_fd.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,15 @@ static void on_frame_read(void *user_data, uint8_t *data, int len)
147147
{
148148
__on_u_frame_read(handle, peer, data, len);
149149
}
150-
// If I-frame is received when connection is not established, we should ignore it
151-
// And let's attempt to initiate new connection
150+
// If I-frame is received when connection is not established, send DM per HDLC ABM spec
152151
else if ( handle->peers[peer].state != TINY_FD_STATE_CONNECTED && handle->peers[peer].state != TINY_FD_STATE_DISCONNECTING )
153152
{
154-
// Should send DM in case we receive here S- or I-frames.
155-
// If connection is not established, we should ignore all frames except U-frames
156-
LOG(TINY_LOG_CRIT, "[%p] Connection is not established, connecting\n", handle);
153+
LOG(TINY_LOG_CRIT, "[%p] Connection is not established, sending DM\n", handle);
157154
tiny_frame_header_t frame = {
158-
.address = __peer_to_address_field( handle, peer ) | HDLC_CR_BIT,
159-
.control = (handle->mode == TINY_FD_MODE_NRM ? HDLC_U_FRAME_TYPE_SNRM : HDLC_U_FRAME_TYPE_SABM) | HDLC_U_FRAME_BITS,
155+
.address = __peer_to_address_field( handle, peer ),
156+
.control = HDLC_U_FRAME_TYPE_DM | HDLC_U_FRAME_BITS,
160157
};
161158
__put_u_s_frame_to_tx_queue(handle, TINY_FD_QUEUE_U_FRAME, &frame, 2);
162-
handle->peers[peer].state = TINY_FD_STATE_CONNECTING;
163159
}
164160
else if ( (control & HDLC_I_FRAME_MASK) == HDLC_I_FRAME_BITS )
165161
{

src/proto/fd/tiny_fd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,11 @@ extern "C"
107107
typedef enum
108108
{
109109
TINY_FD_FRAME_SUBTYPE_RR = 0x00, ///< S-frame subtype RR
110+
TINY_FD_FRAME_SUBTYPE_RNR = 0x04, ///< S-frame subtype RNR
110111
TINY_FD_FRAME_SUBTYPE_REJ = 0x08, ///< S-frame subtype REJ
111112

112113
TINY_FD_FRAME_SUBTYPE_UA = 0x60, ///< U-frame subtype UA
114+
TINY_FD_FRAME_SUBTYPE_DM = 0x0C, ///< U-frame subtype DM
113115
TINY_FD_FRAME_SUBTYPE_FRMR = 0x84, ///< U-frame subtype FRMR
114116
TINY_FD_FRAME_SUBTYPE_RSET = 0x8C, ///< U-frame subtype RSET
115117
TINY_FD_FRAME_SUBTYPE_SABM = 0x2C, ///< U-frame subtype SABM

src/proto/fd/tiny_fd_defines_int.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ enum
8989
#define HDLC_S_FRAME_MASK 0x03
9090
#define HDLC_S_FRAME_TYPE_REJ 0x08
9191
#define HDLC_S_FRAME_TYPE_RR 0x00
92+
#define HDLC_S_FRAME_TYPE_RNR 0x04
9293
#define HDLC_S_FRAME_TYPE_MASK 0x0C
9394

9495
#define HDLC_U_FRAME_BITS 0x03
9596
#define HDLC_U_FRAME_MASK 0x03
9697
// 2 lower bits of the command id's are zero, because they are covered by U_FRAME_BITS
9798
#define HDLC_U_FRAME_TYPE_UA 0x60
99+
#define HDLC_U_FRAME_TYPE_DM 0x0C
98100
#define HDLC_U_FRAME_TYPE_FRMR 0x84
99101
#define HDLC_U_FRAME_TYPE_RSET 0x8C
100102
#define HDLC_U_FRAME_TYPE_SABM 0x2C

src/proto/fd/tiny_fd_on_rx_int.h

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ static int __on_s_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
123123
uint8_t nr = control >> 5;
124124
int result = TINY_ERR_FAILED;
125125
LOG(TINY_LOG_INFO, "[%p] Receiving S-Frame N(R)=%02X, type=%s with address [%02X]\n", handle, nr,
126-
((control >> 2) & 0x03) == 0x00 ? "RR" : "REJ", ((uint8_t *)data)[0]);
126+
((control >> 2) & 0x03) == 0x00 ? "RR" : ((control >> 2) & 0x03) == 0x01 ? "RNR" : "REJ", ((uint8_t *)data)[0]);
127127
if ( (control & HDLC_S_FRAME_TYPE_MASK) == HDLC_S_FRAME_TYPE_REJ )
128128
{
129129
// Confirm all previously sent frames up to received N(R)
@@ -148,6 +148,12 @@ static int __on_s_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
148148
}
149149
}
150150
}
151+
else if ( (control & HDLC_S_FRAME_TYPE_MASK) == HDLC_S_FRAME_TYPE_RNR )
152+
{
153+
// Confirm all previously sent frames up to received N(R), but peer is busy — do not send more
154+
__confirm_sent_frames(handle, peer, nr);
155+
LOG(TINY_LOG_WRN, "[%p] Peer signalled RNR (busy), pausing I-frame transmission\n", handle);
156+
}
151157
return result;
152158
}
153159

@@ -183,13 +189,31 @@ static int __on_u_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
183189
}
184190
else if ( type == HDLC_U_FRAME_TYPE_RSET )
185191
{
186-
// resets N(R) = 0 in secondary, resets N(S) = 0 in primary
187-
// expected answer UA
192+
// RSET resets sequence numbers and expects UA response
193+
LOG(TINY_LOG_WRN, "[%p] RSET received, resetting sequence numbers\n", handle);
194+
__reset_i_queue_control(&handle->peers[peer].i_queue_control);
195+
handle->peers[peer].sent_nr = 0;
196+
handle->peers[peer].sent_reject = 0;
197+
tiny_frame_header_t frame = {
198+
.address = __peer_to_address_field( handle, peer ),
199+
.control = HDLC_U_FRAME_TYPE_UA | HDLC_U_FRAME_BITS,
200+
};
201+
__put_u_s_frame_to_tx_queue(handle, TINY_FD_QUEUE_U_FRAME, &frame, 2);
188202
}
189203
else if ( type == HDLC_U_FRAME_TYPE_FRMR )
190204
{
191-
// response of secondary in case of protocol errors: invalid control field, invalid N(R),
192-
// information field too long or not expected in this frame
205+
// FRMR indicates protocol error on remote side — initiate link reset via SABM/SNRM
206+
LOG(TINY_LOG_ERR, "[%p] FRMR received, initiating link reset\n", handle);
207+
if ( handle->peers[peer].state != TINY_FD_STATE_DISCONNECTED )
208+
{
209+
__switch_to_disconnected_state(handle, peer);
210+
}
211+
tiny_frame_header_t sabm_frame = {
212+
.address = __peer_to_address_field( handle, peer ) | HDLC_CR_BIT,
213+
.control = (handle->mode == TINY_FD_MODE_NRM ? HDLC_U_FRAME_TYPE_SNRM : HDLC_U_FRAME_TYPE_SABM) | HDLC_U_FRAME_BITS,
214+
};
215+
__put_u_s_frame_to_tx_queue(handle, TINY_FD_QUEUE_U_FRAME, &sabm_frame, 2);
216+
handle->peers[peer].state = TINY_FD_STATE_CONNECTING;
193217
}
194218
else if ( type == HDLC_U_FRAME_TYPE_UA )
195219
{
@@ -203,6 +227,15 @@ static int __on_u_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
203227
__switch_to_disconnected_state(handle, peer);
204228
}
205229
}
230+
else if ( type == HDLC_U_FRAME_TYPE_DM )
231+
{
232+
// DM indicates the remote side is in disconnected mode
233+
LOG(TINY_LOG_WRN, "[%p] DM received, peer is in disconnected mode\n", handle);
234+
if ( handle->peers[peer].state != TINY_FD_STATE_DISCONNECTED )
235+
{
236+
__switch_to_disconnected_state(handle, peer);
237+
}
238+
}
206239
else
207240
{
208241
LOG(TINY_LOG_WRN, "[%p] Unknown hdlc U-frame received\n", handle);

src/proto/fd/tiny_fd_proto_logger.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,15 @@ static const char *__get_frame_subtype_str(uint8_t control)
116116
switch (control & HDLC_S_FRAME_TYPE_MASK)
117117
{
118118
case HDLC_S_FRAME_TYPE_RR: return " RR";
119+
case HDLC_S_FRAME_TYPE_RNR: return " RNR";
119120
case HDLC_S_FRAME_TYPE_REJ: return " REJ";
120121
default: return " UNK";
121122
}
122123
}
123124
switch (control & HDLC_U_FRAME_TYPE_MASK)
124125
{
125126
case HDLC_U_FRAME_TYPE_UA: return " UA";
127+
case HDLC_U_FRAME_TYPE_DM: return " DM";
126128
case HDLC_U_FRAME_TYPE_FRMR: return "FRMR";
127129
case HDLC_U_FRAME_TYPE_RSET: return "RSET";
128130
case HDLC_U_FRAME_TYPE_SABM: return "SABM";

unittest/tiny_fd_abm_tests.cpp

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,17 @@ TEST(TINY_FD_ABM, ABM_RecieveOutOfOrderIFrames)
238238
CHECK_EQUAL(0x7E, outBuffer[7]); // Flag
239239
}
240240

241-
TEST(TINY_FD_ABM, ABM_SendSABMOnIFrameIfDisconnected)
241+
TEST(TINY_FD_ABM, ABM_SendDMOnIFrameIfDisconnected)
242242
{
243-
// If we are disconnected, we should send SABM frame on I-frame
243+
// Per HDLC ABM spec, if we are disconnected and receive I-frame, send DM (Disconnect Mode)
244244
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x00\x11\x7E", 5); // I-frame in order
245245
CHECK_EQUAL(TINY_SUCCESS, read_result);
246246
auto len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
247247
CHECK_EQUAL(4, len);
248-
// Check SABM frame
248+
// DM control = (0x0C | 0x03) | P_BIT = 0x0F | 0x10 = 0x1F
249249
CHECK_EQUAL(0x7E, outBuffer[0]); // Flag
250-
CHECK_EQUAL(0x03, outBuffer[1]); // Address field - CR bit must be cleared
251-
CHECK_EQUAL(0x3F, outBuffer[2]); // SABM packet
250+
CHECK_EQUAL(0x01, outBuffer[1]); // Address field - CR bit cleared (response)
251+
CHECK_EQUAL(0x1F, outBuffer[2]); // DM packet with P/F bit
252252
CHECK_EQUAL(0x7E, outBuffer[3]); // Flag
253253
}
254254

@@ -419,4 +419,97 @@ TEST(TINY_FD_ABM, ABM_CheckReceiveReadyWithCommandBitCleared)
419419
CHECK_EQUAL(TINY_SUCCESS, read_result);
420420
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
421421
CHECK_EQUAL(0, len); // We should have sent RR frame
422+
}
423+
424+
TEST(TINY_FD_ABM, ABM_SendDMOnSFrameIfDisconnected)
425+
{
426+
// Per HDLC ABM spec, if we are disconnected and receive S-frame, send DM
427+
// RR frame with N(R)=0: control = 0x01
428+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x01\x7E", 4); // RR S-frame
429+
CHECK_EQUAL(TINY_SUCCESS, read_result);
430+
auto len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
431+
CHECK_EQUAL(4, len);
432+
// DM control = (0x0C | 0x03) | P_BIT = 0x0F | 0x10 = 0x1F
433+
CHECK_EQUAL(0x7E, outBuffer[0]); // Flag
434+
CHECK_EQUAL(0x01, outBuffer[1]); // Address field - CR bit cleared (response)
435+
CHECK_EQUAL(0x1F, outBuffer[2]); // DM packet with P/F bit
436+
CHECK_EQUAL(0x7E, outBuffer[3]); // Flag
437+
}
438+
439+
TEST(TINY_FD_ABM, ABM_FRMRReceiveTriggersReconnect)
440+
{
441+
// FRMR should trigger disconnection and SABM reconnection attempt
442+
establishConnection();
443+
CHECK(connected);
444+
// Send FRMR frame: control = HDLC_U_FRAME_TYPE_FRMR | HDLC_U_FRAME_BITS = 0x84 | 0x03 = 0x87
445+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x01\x87\x00\x00\x7E", 6); // FRMR with 2 info bytes
446+
CHECK_EQUAL(TINY_SUCCESS, read_result);
447+
CHECK(!connected); // Should have disconnected
448+
// Should get SABM reconnection attempt
449+
auto len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
450+
CHECK_EQUAL(4, len);
451+
CHECK_EQUAL(0x7E, outBuffer[0]); // Flag
452+
CHECK_EQUAL(0x03, outBuffer[1]); // Address field - CR bit set (command)
453+
CHECK_EQUAL(0x3F, outBuffer[2]); // SABM packet (0x2C | 0x13 with P bit = 0x2F | 0x10 = 0x3F)
454+
CHECK_EQUAL(0x7E, outBuffer[3]); // Flag
455+
}
456+
457+
TEST(TINY_FD_ABM, ABM_RSETResetsSequenceNumbers)
458+
{
459+
establishConnection();
460+
// Send some I-frames to advance sequence numbers
461+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x00\x11\x7E", 5); // I-frame N(S)=0
462+
CHECK_EQUAL(TINY_SUCCESS, read_result);
463+
// Drain RR response
464+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
465+
CHECK(len > 0);
466+
// Now send RSET: control = HDLC_U_FRAME_TYPE_RSET | HDLC_U_FRAME_BITS = 0x8C | 0x03 = 0x8F
467+
read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x8F\x7E", 4); // RSET frame
468+
CHECK_EQUAL(TINY_SUCCESS, read_result);
469+
// Should respond with UA
470+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
471+
CHECK_EQUAL(4, len);
472+
CHECK_EQUAL(0x7E, outBuffer[0]); // Flag
473+
CHECK_EQUAL(0x01, outBuffer[1]); // Address field - CR bit cleared (response)
474+
CHECK_EQUAL(0x73, outBuffer[2]); // UA packet
475+
CHECK_EQUAL(0x7E, outBuffer[3]); // Flag
476+
// After RSET, send I-frame N(S)=0 again — should succeed since V(R) was reset to 0
477+
read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x00\x22\x7E", 5); // I-frame N(S)=0
478+
CHECK_EQUAL(TINY_SUCCESS, read_result);
479+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
480+
CHECK(len > 0);
481+
// Should get RR with N(R)=1 confirming the frame was accepted
482+
CHECK_EQUAL(0x7E, outBuffer[0]); // Flag
483+
CHECK_EQUAL(0x01, outBuffer[1]); // Address field
484+
CHECK_EQUAL(0x31, outBuffer[2]); // RR with N(R)=1
485+
CHECK_EQUAL(0x7E, outBuffer[3]); // Flag
486+
}
487+
488+
TEST(TINY_FD_ABM, ABM_RNRPausesTransmission)
489+
{
490+
establishConnection();
491+
// Send I-frame first to advance sequence
492+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x00\x11\x7E", 5); // I-frame
493+
CHECK_EQUAL(TINY_SUCCESS, read_result);
494+
// Drain RR
495+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
496+
CHECK(len > 0);
497+
// Send RNR S-frame: control = HDLC_S_FRAME_BITS | HDLC_S_FRAME_TYPE_RNR | (N(R)<<5)
498+
// = 0x01 | 0x04 | (1<<5) = 0x25
499+
read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x25\x7E", 4); // RNR with N(R)=1
500+
CHECK_EQUAL(TINY_SUCCESS, read_result);
501+
// RNR should be accepted without error (confirms N(R)=1)
502+
// No response frame should be generated (RNR doesn't have CR bit set as command here)
503+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
504+
CHECK_EQUAL(0, len);
505+
}
506+
507+
TEST(TINY_FD_ABM, ABM_DMReceiveDisconnects)
508+
{
509+
establishConnection();
510+
CHECK(connected);
511+
// Receive DM from peer: control = HDLC_U_FRAME_TYPE_DM | HDLC_U_FRAME_BITS = 0x0C | 0x03 = 0x0F
512+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x01\x0F\x7E", 4); // DM frame
513+
CHECK_EQUAL(TINY_SUCCESS, read_result);
514+
CHECK(!connected); // Should have disconnected
422515
}

0 commit comments

Comments
 (0)