Skip to content

Commit 5308eb7

Browse files
lexus2kCopilot
andcommitted
Implement P/F bit enforcement in ABM mode and UI frame support
Phase 2 HDLC ABM compliance improvements: P/F Bit Enforcement: - ABM mode: P bit only set on U-frames and keep-alive RR polls - NRM mode: unchanged (P always set for marker passing) - P-to-F propagation: RR response mirrors P bit from command - Regular I-frames and RR acks no longer carry P bit in ABM UI (Unnumbered Information) Frames: - Added HDLC_U_FRAME_TYPE_UI constant (0x00) - Public API: tiny_fd_send_ui_packet_to(), tiny_fd_send_ui_packet() - RX handler: calls on_read_ui_cb callback when UI frame received - UI frames work in any connection state (connectionless) - Uses s_queue (service queue) for TX Tests: - Updated 6 existing tests for P/F bit expectation changes - Added 7 new tests: P/F enforcement (4), UI send/receive (3) - All 68 tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 156ef7c commit 5308eb7

8 files changed

Lines changed: 313 additions & 15 deletions

File tree

src/proto/fd/tiny_fd.c

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ int tiny_fd_init(tiny_fd_handle_t *handle, tiny_fd_init_t *init)
372372
protocol->user_data = init->pdata;
373373
protocol->on_read_cb = init->on_read_cb;
374374
protocol->on_send_cb = init->on_send_cb;
375+
protocol->on_read_ui_cb = init->on_read_ui_cb;
375376
protocol->on_connect_event_cb = init->on_connect_event_cb;
376377
protocol->log_frame_cb = init->log_frame_cb;
377378
protocol->send_timeout = init->send_timeout;
@@ -523,7 +524,19 @@ static uint8_t *tiny_fd_get_next_frame_to_send(tiny_fd_handle_t handle, int *len
523524
if ( data != NULL )
524525
{
525526
tiny_frame_header_t *header = (tiny_frame_header_t *)data;
526-
header->control |= HDLC_P_BIT;
527+
if ( handle->mode == TINY_FD_MODE_NRM )
528+
{
529+
// NRM: Always set P/F bit for marker passing
530+
header->control |= HDLC_P_BIT;
531+
}
532+
else
533+
{
534+
// ABM: Set P/F only on U-frames (SABM, DISC, UA, DM always use P/F)
535+
if ( (header->control & HDLC_U_FRAME_MASK) == HDLC_U_FRAME_BITS )
536+
{
537+
header->control |= HDLC_P_BIT;
538+
}
539+
}
527540
handle->last_marker_ts = tiny_millis();
528541
handle->peers[peer].last_sent_frame_ts = tiny_millis();
529542
__tiny_fd_log_frame(handle, TINY_FD_FRAME_DIRECTION_OUT, data, *len);
@@ -572,7 +585,7 @@ static void tiny_fd_connected_check_idle_timeout(tiny_fd_handle_t handle, uint8_
572585
// Nothing to send, all frames are confirmed, just send keep alive
573586
tiny_frame_header_t frame = {
574587
.address = __peer_to_address_field( handle, peer ),
575-
.control = HDLC_S_FRAME_BITS | HDLC_S_FRAME_TYPE_RR |
588+
.control = HDLC_S_FRAME_BITS | HDLC_S_FRAME_TYPE_RR | HDLC_P_BIT |
576589
(__i_queue_control_get_next_frame_to_receive(&handle->peers[peer].i_queue_control) << 5),
577590
};
578591
handle->peers[peer].ka_confirmed = 0;
@@ -823,6 +836,51 @@ int tiny_fd_send_packet(tiny_fd_handle_t handle, const void *data, int len, uint
823836

824837
///////////////////////////////////////////////////////////////////////////////
825838

839+
int tiny_fd_send_ui_packet_to(tiny_fd_handle_t handle, uint8_t address, const void *data, int len)
840+
{
841+
if ( handle == NULL || (data == NULL && len > 0) )
842+
{
843+
return TINY_ERR_INVALID_DATA;
844+
}
845+
if ( len > tiny_fd_queue_get_mtu( &handle->frames.s_queue ) )
846+
{
847+
return TINY_ERR_DATA_TOO_LARGE;
848+
}
849+
uint8_t peer;
850+
if ( __is_secondary_station( handle ) && address == TINY_FD_PRIMARY_ADDR )
851+
{
852+
address = handle->addr;
853+
}
854+
peer = __address_field_to_peer( handle, (address << 2) | HDLC_E_BIT );
855+
if ( peer == HDLC_INVALID_PEER_INDEX )
856+
{
857+
return TINY_ERR_UNKNOWN_PEER;
858+
}
859+
860+
tiny_mutex_lock(&handle->frames.mutex);
861+
tiny_fd_frame_info_t *slot = tiny_fd_queue_allocate( &handle->frames.s_queue, TINY_FD_QUEUE_U_FRAME, data, len );
862+
if ( slot == NULL )
863+
{
864+
tiny_mutex_unlock(&handle->frames.mutex);
865+
return TINY_ERR_FAILED;
866+
}
867+
slot->header.address = __peer_to_address_field( handle, peer );
868+
slot->header.control = HDLC_U_FRAME_TYPE_UI | HDLC_U_FRAME_BITS;
869+
LOG(TINY_LOG_DEB, "[%p] QUEUE UI-PUT: [%02X] [%02X] len=%d\n", handle, slot->header.address, slot->header.control, len);
870+
tiny_events_set(&handle->events, FD_EVENT_TX_DATA_AVAILABLE);
871+
tiny_mutex_unlock(&handle->frames.mutex);
872+
return TINY_SUCCESS;
873+
}
874+
875+
///////////////////////////////////////////////////////////////////////////////
876+
877+
int tiny_fd_send_ui_packet(tiny_fd_handle_t handle, const void *data, int len)
878+
{
879+
return tiny_fd_send_ui_packet_to(handle, TINY_FD_PRIMARY_ADDR, data, len);
880+
}
881+
882+
///////////////////////////////////////////////////////////////////////////////
883+
826884
int tiny_fd_buffer_size_by_mtu(int mtu, int window)
827885
{
828886
return tiny_fd_buffer_size_by_mtu_ex(0, mtu, window, HDLC_CRC_16, 1);

src/proto/fd/tiny_fd.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ extern "C"
117117
TINY_FD_FRAME_SUBTYPE_SABM = 0x2C, ///< U-frame subtype SABM
118118
TINY_FD_FRAME_SUBTYPE_SNRM = 0x80, ///< U-frame subtype SNRM
119119
TINY_FD_FRAME_SUBTYPE_DISC = 0x40, ///< U-frame subtype DISC
120+
TINY_FD_FRAME_SUBTYPE_UI = 0x00, ///< U-frame subtype UI (Unnumbered Information)
120121
} tiny_fd_frame_subtype_t;
121122

122123
/**
@@ -255,6 +256,13 @@ extern "C"
255256
*/
256257
uint8_t mode;
257258

259+
/**
260+
* Callback to process incoming UI (Unnumbered Information) frames.
261+
* UI frames are connectionless — they can be received in any state (even disconnected).
262+
* Can be NULL if UI frames are not needed.
263+
*/
264+
on_frame_read_cb_t on_read_ui_cb;
265+
258266
} tiny_fd_init_t;
259267

260268
/**
@@ -495,6 +503,39 @@ extern "C"
495503
*/
496504
extern int tiny_fd_send_packet(tiny_fd_handle_t handle, const void *buf, int len, uint32_t timeout);
497505

506+
/**
507+
* @brief Sends UI (Unnumbered Information) frame over full-duplex protocol.
508+
*
509+
* Sends a connectionless UI frame to the specified address. UI frames can be sent
510+
* in any connection state (even when disconnected). They are not acknowledged and
511+
* do not use sequence numbers.
512+
*
513+
* @param handle tiny_fd_handle_t handle
514+
* @param address address of remote peer. For primary device, please use TINY_FD_PRIMARY_ADDR
515+
* @param buf data to send
516+
* @param len length of data to send
517+
*
518+
* @return TINY_SUCCESS if the UI frame was queued for sending.
519+
* TINY_ERR_FAILED if no room in internal queue.
520+
* TINY_ERR_DATA_TOO_LARGE if data exceeds MTU.
521+
* TINY_ERR_INVALID_DATA if parameters are invalid.
522+
*/
523+
extern int tiny_fd_send_ui_packet_to(tiny_fd_handle_t handle, uint8_t address, const void *buf, int len);
524+
525+
/**
526+
* @brief Sends UI (Unnumbered Information) frame to primary station.
527+
*
528+
* Sends a connectionless UI frame to the primary station.
529+
* For details, please refer to tiny_fd_send_ui_packet_to().
530+
*
531+
* @param handle tiny_fd_handle_t handle
532+
* @param buf data to send
533+
* @param len length of data to send
534+
*
535+
* @return Success result or error code
536+
*/
537+
extern int tiny_fd_send_ui_packet(tiny_fd_handle_t handle, const void *buf, int len);
538+
498539
/**
499540
* @}
500541
*/

src/proto/fd/tiny_fd_defines_int.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ enum
102102
#define HDLC_U_FRAME_TYPE_SABM 0x2C
103103
#define HDLC_U_FRAME_TYPE_SNRM 0x80
104104
#define HDLC_U_FRAME_TYPE_DISC 0x40
105+
#define HDLC_U_FRAME_TYPE_UI 0x00
105106
#define HDLC_U_FRAME_TYPE_MASK 0xEC
106107

107108
#define HDLC_P_BIT 0x10

src/proto/fd/tiny_fd_int.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ extern "C"
105105
on_frame_read_cb_t on_read_cb;
106106
/// Callback to process received frames
107107
on_frame_send_cb_t on_send_cb;
108+
/// Callback to process received UI frames
109+
on_frame_read_cb_t on_read_ui_cb;
108110
/// Callback to get connect/disconnect notification
109111
on_connect_event_cb_t on_connect_event_cb;
110112
/// Callback to log frames

src/proto/fd/tiny_fd_on_rx_int.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ static int __on_s_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
142142
tiny_frame_header_t frame = {
143143
.address = __peer_to_address_field( handle, peer ),
144144
.control = HDLC_S_FRAME_BITS | HDLC_S_FRAME_TYPE_RR |
145+
(control & HDLC_P_BIT) |
145146
(__i_queue_control_get_next_frame_to_receive(&handle->peers[peer].i_queue_control) << 5),
146147
};
147148
__put_u_s_frame_to_tx_queue(handle, TINY_FD_QUEUE_S_FRAME, &frame, 2);
@@ -236,6 +237,19 @@ static int __on_u_frame_read(tiny_fd_handle_t handle, uint8_t peer, void *data,
236237
__switch_to_disconnected_state(handle, peer);
237238
}
238239
}
240+
else if ( type == HDLC_U_FRAME_TYPE_UI )
241+
{
242+
// UI (Unnumbered Information) — connectionless data, accepted in any state
243+
LOG(TINY_LOG_INFO, "[%p] UI frame received, len=%d\n", handle, len);
244+
if ( handle->on_read_ui_cb && len > 2 )
245+
{
246+
tiny_mutex_unlock(&handle->frames.mutex);
247+
handle->on_read_ui_cb(handle->user_data,
248+
__is_primary_station( handle ) ? (__peer_to_address_field( handle, peer ) >> 2) : TINY_FD_PRIMARY_ADDR,
249+
(uint8_t *)data + 2, len - 2);
250+
tiny_mutex_lock(&handle->frames.mutex);
251+
}
252+
}
239253
else
240254
{
241255
LOG(TINY_LOG_WRN, "[%p] Unknown hdlc U-frame received\n", handle);

src/proto/fd/tiny_fd_proto_logger.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ static const char *__get_frame_subtype_str(uint8_t control)
130130
case HDLC_U_FRAME_TYPE_SABM: return "SABM";
131131
case HDLC_U_FRAME_TYPE_SNRM: return "SNRM";
132132
case HDLC_U_FRAME_TYPE_DISC: return "DISC";
133+
case HDLC_U_FRAME_TYPE_UI: return " UI";
133134
default: return " UNK";
134135
}
135136
}

unittest/fd_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ TEST(FD, resend_timeout)
261261
helper1.send("#");
262262
std::this_thread::sleep_for(std::chrono::milliseconds(70 * 2 + 100));
263263
helper1.stop();
264-
const uint8_t reconnect_dat[] = {0x7E, 0x01, 0x10, '#', 0x18, 0x1A, 0x7E, // 1-st attempt
265-
0x7E, 0x01, 0x10, '#', 0x18, 0x1A, 0x7E, // 2-nd attempt (1st retry)
266-
0x7E, 0x01, 0x10, '#', 0x18, 0x1A, 0x7E, // 3-rd attempt (2nd retry)
264+
const uint8_t reconnect_dat[] = {0x7E, 0x01, 0x00, '#', 0x89, 0x8F, 0x7E, // 1-st attempt (I-frame, no P bit in ABM)
265+
0x7E, 0x01, 0x00, '#', 0x89, 0x8F, 0x7E, // 2-nd attempt (1st retry)
266+
0x7E, 0x01, 0x00, '#', 0x89, 0x8F, 0x7E, // 3-rd attempt (2nd retry)
267267
0x7E, 0x03, 0x3F, 0x5B, 0xEC, 0x7E}; // Attempt to reconnect (SABM)
268268
uint8_t buffer[64]{};
269269
conn.endpoint2().read(buffer, sizeof(buffer));

0 commit comments

Comments
 (0)