Skip to content

Commit 1b297aa

Browse files
lexus2kCopilot
andcommitted
Implement SREJ (Selective Reject) S-frame support
SREJ allows a receiver to request retransmission of a single specific frame, unlike REJ which retransmits all frames from N(R) onward. This is more efficient when only one frame is lost in a window. Implementation: - Added HDLC_S_FRAME_TYPE_SREJ (0x0C) constant - Added TINY_FD_FRAME_SUBTYPE_SREJ to public enum - RX handler: on SREJ receipt, confirms frames up to N(R) and sets srej_req_mask bit for the requested frame number - TX path: checks srej_req_mask before normal next_ns dispatch, retransmits only the specific frame without advancing next_ns - Uses uint8_t bitmask (perfect for modulo-8 sequence numbers) - Mask cleared on connect, disconnect, and RSET - Updated logger to display SREJ subtype Tests: - ABM_SREJRetransmitsSingleFrame: basic SREJ retransmit - ABM_SREJRetransmitsOnlyRequestedFrame: verifies only one frame sent - ABM_SREJForSecondFrame: SREJ with N(R)=1 and implicit ack - ABM_REJRetransmitsAllFromNR: contrast test showing REJ sends all - All 72 tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 86d2ed1 commit 1b297aa

7 files changed

Lines changed: 151 additions & 1 deletion

File tree

src/proto/fd/tiny_fd.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ static void __switch_to_connected_state(tiny_fd_handle_t handle, uint8_t peer)
7575
__reset_i_queue_control(&handle->peers[peer].i_queue_control);
7676
handle->peers[peer].sent_nr = 0;
7777
handle->peers[peer].sent_reject = 0;
78+
handle->peers[peer].srej_req_mask = 0;
7879
tiny_fd_queue_reset_for( &handle->frames.i_queue, __peer_to_address_field( handle, peer ) );
7980
// Reset last arrived frame timestamp on connection.
8081
// This is required to avoid disconnection on keep alive timeout at the beginning of connection
@@ -107,6 +108,7 @@ static void __switch_to_disconnected_state(tiny_fd_handle_t handle, uint8_t peer
107108
__reset_i_queue_control(&handle->peers[peer].i_queue_control);
108109
handle->peers[peer].sent_nr = 0;
109110
handle->peers[peer].sent_reject = 0;
111+
handle->peers[peer].srej_req_mask = 0;
110112
tiny_fd_queue_reset_for( &handle->frames.i_queue, __peer_to_address_field( handle, peer ) );
111113
tiny_events_clear(&handle->peers[peer].events, FD_EVENT_CAN_ACCEPT_I_FRAMES);
112114
LOG(TINY_LOG_CRIT, "[%p] Disconnected\n", handle);
@@ -467,6 +469,30 @@ static uint8_t *tiny_fd_get_next_i_frame(tiny_fd_handle_t handle, int *len, uint
467469
// If sending of I-frames is not allowed then just exit
468470
return NULL;
469471
}
472+
// Check for SREJ-requested selective retransmissions first
473+
if ( handle->peers[peer].srej_req_mask != 0 )
474+
{
475+
for ( uint8_t i = 0; i < 8; i++ )
476+
{
477+
if ( handle->peers[peer].srej_req_mask & (1 << i) )
478+
{
479+
ptr = tiny_fd_queue_get_next( &handle->frames.i_queue, TINY_FD_QUEUE_I_FRAME, address, i );
480+
handle->peers[peer].srej_req_mask &= ~(1 << i);
481+
if ( ptr != NULL )
482+
{
483+
data = (uint8_t *)&ptr->header;
484+
*len = ptr->len + sizeof(tiny_frame_header_t);
485+
LOG(TINY_LOG_INFO, "[%p] SREJ retransmit I-Frame N(S)=%02X with address [%02X]\n", handle, i, data[0]);
486+
ptr->header.control &= 0x0F;
487+
ptr->header.control |= ( __i_queue_control_get_next_frame_to_receive(&handle->peers[peer].i_queue_control) << 5);
488+
handle->peers[peer].sent_nr = __i_queue_control_get_next_frame_to_receive(&handle->peers[peer].i_queue_control);
489+
handle->peers[peer].last_sent_i_ts = tiny_millis();
490+
}
491+
break;
492+
}
493+
}
494+
return data;
495+
}
470496
ptr = tiny_fd_queue_get_next( &handle->frames.i_queue, TINY_FD_QUEUE_I_FRAME, address, __i_queue_control_get_next_frame_to_send(&handle->peers[peer].i_queue_control) );
471497
if ( ptr != NULL )
472498
{

src/proto/fd/tiny_fd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ extern "C"
109109
TINY_FD_FRAME_SUBTYPE_RR = 0x00, ///< S-frame subtype RR
110110
TINY_FD_FRAME_SUBTYPE_RNR = 0x04, ///< S-frame subtype RNR
111111
TINY_FD_FRAME_SUBTYPE_REJ = 0x08, ///< S-frame subtype REJ
112+
TINY_FD_FRAME_SUBTYPE_SREJ = 0x0C, ///< S-frame subtype SREJ (Selective Reject)
112113

113114
TINY_FD_FRAME_SUBTYPE_UA = 0x60, ///< U-frame subtype UA
114115
TINY_FD_FRAME_SUBTYPE_DM = 0x0C, ///< U-frame subtype DM

src/proto/fd/tiny_fd_defines_int.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ enum
9090
#define HDLC_S_FRAME_TYPE_REJ 0x08
9191
#define HDLC_S_FRAME_TYPE_RR 0x00
9292
#define HDLC_S_FRAME_TYPE_RNR 0x04
93+
#define HDLC_S_FRAME_TYPE_SREJ 0x0C
9394
#define HDLC_S_FRAME_TYPE_MASK 0x0C
9495

9596
#define HDLC_U_FRAME_BITS 0x03

src/proto/fd/tiny_fd_int.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ extern "C"
8686

8787
uint8_t sent_nr; // frame index last sent back
8888
uint8_t sent_reject; // If reject was already sent
89+
uint8_t srej_req_mask; // Bitmask of frames requested for selective retransmission
8990

9091
i_queue_control_t i_queue_control;
9192

src/proto/fd/tiny_fd_on_rx_int.h

Lines changed: 10 additions & 1 deletion
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" : ((control >> 2) & 0x03) == 0x01 ? "RNR" : "REJ", ((uint8_t *)data)[0]);
126+
((control >> 2) & 0x03) == 0x00 ? "RR" : ((control >> 2) & 0x03) == 0x01 ? "RNR" : ((control >> 2) & 0x03) == 0x02 ? "REJ" : "SREJ", ((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)
@@ -155,6 +155,14 @@ static int __on_s_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
155155
__confirm_sent_frames(handle, peer, nr);
156156
LOG(TINY_LOG_WRN, "[%p] Peer signalled RNR (busy), pausing I-frame transmission\n", handle);
157157
}
158+
else if ( (control & HDLC_S_FRAME_TYPE_MASK) == HDLC_S_FRAME_TYPE_SREJ )
159+
{
160+
// SREJ: confirm frames up to N(R), retransmit only frame N(R)
161+
__confirm_sent_frames(handle, peer, nr);
162+
LOG(TINY_LOG_INFO, "[%p] SREJ received for frame N(R)=%d, scheduling selective retransmit\n", handle, nr);
163+
handle->peers[peer].srej_req_mask |= (1 << nr);
164+
tiny_events_set(&handle->events, FD_EVENT_TX_DATA_AVAILABLE);
165+
}
158166
return result;
159167
}
160168

@@ -195,6 +203,7 @@ static int __on_u_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
195203
__reset_i_queue_control(&handle->peers[peer].i_queue_control);
196204
handle->peers[peer].sent_nr = 0;
197205
handle->peers[peer].sent_reject = 0;
206+
handle->peers[peer].srej_req_mask = 0;
198207
tiny_frame_header_t frame = {
199208
.address = __peer_to_address_field( handle, peer ),
200209
.control = HDLC_U_FRAME_TYPE_UA | HDLC_U_FRAME_BITS,

src/proto/fd/tiny_fd_proto_logger.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ static const char *__get_frame_subtype_str(uint8_t control)
118118
case HDLC_S_FRAME_TYPE_RR: return " RR";
119119
case HDLC_S_FRAME_TYPE_RNR: return " RNR";
120120
case HDLC_S_FRAME_TYPE_REJ: return " REJ";
121+
case HDLC_S_FRAME_TYPE_SREJ: return "SREJ";
121122
default: return " UNK";
122123
}
123124
}

unittest/tiny_fd_abm_tests.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,4 +693,115 @@ TEST(TINY_FD_ABM, ABM_UIFrameReceiveDisconnected)
693693
CHECK(s_ui_received);
694694
CHECK_EQUAL(1, s_ui_len);
695695
CHECK_EQUAL(0x42, s_ui_data[0]);
696+
}
697+
698+
// ==========================================================================
699+
// SREJ (Selective Reject) Tests
700+
// ==========================================================================
701+
702+
TEST(TINY_FD_ABM, ABM_SREJRetransmitsSingleFrame)
703+
{
704+
establishConnection();
705+
// Queue one I-frame
706+
int result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xAA", 1, 1000);
707+
CHECK_EQUAL(TINY_SUCCESS, result);
708+
// Send the I-frame out
709+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
710+
CHECK(len > 0);
711+
uint8_t sent_control = outBuffer[2];
712+
CHECK_EQUAL(0x00, sent_control & 0x01); // I-frame (bit 0 = 0)
713+
CHECK_EQUAL(0x00, (sent_control >> 1) & 0x07); // N(S) = 0
714+
715+
// Peer sends SREJ for frame 0: address=0x03 (command), control=0x0D (SREJ, N(R)=0)
716+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x0D\x7E", 4);
717+
CHECK_EQUAL(TINY_SUCCESS, read_result);
718+
719+
// Should retransmit frame 0
720+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
721+
CHECK(len > 0);
722+
CHECK_EQUAL(0x00, outBuffer[2] & 0x01); // I-frame
723+
CHECK_EQUAL(0x00, (outBuffer[2] >> 1) & 0x07); // N(S) = 0 (retransmitted)
724+
CHECK_EQUAL(0xAA, outBuffer[3]); // Original payload preserved
725+
}
726+
727+
TEST(TINY_FD_ABM, ABM_SREJRetransmitsOnlyRequestedFrame)
728+
{
729+
establishConnection();
730+
// Queue two I-frames
731+
int result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xAA", 1, 1000);
732+
CHECK_EQUAL(TINY_SUCCESS, result);
733+
result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xBB", 1, 1000);
734+
CHECK_EQUAL(TINY_SUCCESS, result);
735+
736+
// Send both I-frames out
737+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
738+
CHECK(len > 0);
739+
int len2 = tiny_fd_get_tx_data(handle, outBuffer.data() + len, outBuffer.size() - len, 100);
740+
CHECK(len2 > 0);
741+
742+
// Peer sends SREJ for frame 0 only: control=0x0D (SREJ, N(R)=0)
743+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x0D\x7E", 4);
744+
CHECK_EQUAL(TINY_SUCCESS, read_result);
745+
746+
// Get retransmitted frame - should be only frame 0
747+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
748+
CHECK(len > 0);
749+
CHECK_EQUAL(0x00, (outBuffer[2] >> 1) & 0x07); // N(S) = 0
750+
751+
// No more frames should be available (frame 1 was NOT retransmitted)
752+
len2 = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
753+
CHECK_EQUAL(0, len2); // Nothing else to send
754+
}
755+
756+
TEST(TINY_FD_ABM, ABM_SREJForSecondFrame)
757+
{
758+
establishConnection();
759+
// Queue two I-frames
760+
int result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xAA", 1, 1000);
761+
CHECK_EQUAL(TINY_SUCCESS, result);
762+
result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xBB", 1, 1000);
763+
CHECK_EQUAL(TINY_SUCCESS, result);
764+
765+
// Send both I-frames out
766+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
767+
len += tiny_fd_get_tx_data(handle, outBuffer.data() + len, outBuffer.size() - len, 100);
768+
769+
// Peer acks frame 0, sends SREJ for frame 1: control=0x2D (SREJ, N(R)=1)
770+
// N(R)=1 means: confirmed frame 0, please retransmit frame 1
771+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x2D\x7E", 4);
772+
CHECK_EQUAL(TINY_SUCCESS, read_result);
773+
774+
// Get retransmitted frame - should be frame 1 with payload 0xBB
775+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
776+
CHECK(len > 0);
777+
CHECK_EQUAL(0x01, (outBuffer[2] >> 1) & 0x07); // N(S) = 1
778+
CHECK_EQUAL(0xBB, outBuffer[3]); // Frame 1 payload
779+
}
780+
781+
TEST(TINY_FD_ABM, ABM_REJRetransmitsAllFromNR)
782+
{
783+
// Compare with REJ behavior: REJ retransmits ALL frames from N(R) onward
784+
establishConnection();
785+
// Queue two I-frames
786+
int result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xAA", 1, 1000);
787+
CHECK_EQUAL(TINY_SUCCESS, result);
788+
result = tiny_fd_send_packet_to(handle, TINY_FD_PRIMARY_ADDR, (const void *)"\xBB", 1, 1000);
789+
CHECK_EQUAL(TINY_SUCCESS, result);
790+
791+
// Send both I-frames out
792+
int len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
793+
len += tiny_fd_get_tx_data(handle, outBuffer.data() + len, outBuffer.size() - len, 100);
794+
795+
// Peer sends REJ for frame 0: control=0x09 (REJ, N(R)=0)
796+
auto read_result = tiny_fd_on_rx_data(handle, (uint8_t *)"\x7E\x03\x09\x7E", 4);
797+
CHECK_EQUAL(TINY_SUCCESS, read_result);
798+
799+
// REJ should retransmit BOTH frames: frame 0 and frame 1
800+
len = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
801+
CHECK(len > 0);
802+
CHECK_EQUAL(0x00, (outBuffer[2] >> 1) & 0x07); // N(S) = 0 (first retransmit)
803+
804+
int len2 = tiny_fd_get_tx_data(handle, outBuffer.data(), outBuffer.size(), 100);
805+
CHECK(len2 > 0);
806+
CHECK_EQUAL(0x01, (outBuffer[2] >> 1) & 0x07); // N(S) = 1 (second retransmit)
696807
}

0 commit comments

Comments
 (0)