Skip to content

Commit 71556a6

Browse files
committed
PD: Retransmit cached reply on sequence-repeatgc
Per OSDP specification, a PD must re-emit its previous reply verbatim when the CP re-sends a command with the same sequence number. Rebuilding the reply corrupts the SC MAC chain and tears down the secure channel on every CP retry probe. Cache the last sent reply and re-emit it on a matching seq-repeat. Add unit tests covering the retransmit path and cache invalidation. Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
1 parent a54c57e commit 71556a6

8 files changed

Lines changed: 457 additions & 13 deletions

File tree

src/osdp_common.h

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ struct osdp_pd {
437437
uint32_t packet_scan_skip;
438438
bool reply_prebuilt;
439439

440+
/* Retransmit cache: last successfully-sent reply bytes live in the
441+
* shared tx_buf as long as nothing overwrites them. On a sequence
442+
* repeat we re-emit those bytes verbatim (see phy_check_packet). */
443+
uint16_t last_tx_len; /* 0 = cache empty */
444+
uint8_t last_cmd_id;
445+
440446
int cmd_id; /* Currently processing command ID */
441447
int reply_id; /* Currently processing reply ID */
442448
union {
@@ -477,6 +483,7 @@ struct osdp {
477483
struct osdp_pd *pd; /* base of PD list (must be at lest one) */
478484
struct osdp_channel channel; /* OSDP channel */
479485
uint8_t tx_buf[OSDP_PACKET_BUF_SIZE];
486+
uint8_t *rx_buf; /* RX landing buffer: aliased to tx_buf in CP; distinct in PD */
480487

481488
/* CP event callback to app with opaque arg pointer as passed by app */
482489
void *event_callback_arg;
@@ -704,6 +711,8 @@ static inline void sc_deactivate(struct osdp_pd *pd)
704711
osdp_sc_teardown(pd);
705712
}
706713
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
714+
/* Cached retransmit reply is no longer meaningful without SC. */
715+
pd->last_tx_len = 0;
707716
}
708717

709718
static inline void make_request(struct osdp_pd *pd, uint32_t req) {
@@ -791,13 +800,20 @@ static inline struct osdp_rb *cp_static_rx_rb_array_get(void)
791800

792801
static inline struct osdp *cp_ctx_alloc(void)
793802
{
803+
struct osdp *ctx;
794804
#ifdef OPT_OSDP_STATIC
795-
struct osdp *ctx = cp_static_ctx_get();
805+
ctx = cp_static_ctx_get();
796806
memset(ctx, 0, sizeof(struct osdp));
797-
return ctx;
798807
#else
799-
return calloc(1, sizeof(struct osdp));
808+
ctx = calloc(1, sizeof(struct osdp));
809+
if (!ctx) {
810+
return NULL;
811+
}
800812
#endif /* OPT_OSDP_STATIC */
813+
/* CP mode does not cache retransmit replies; alias rx_buf to tx_buf
814+
* to preserve the legacy shared-buffer behavior with zero cost. */
815+
ctx->rx_buf = ctx->tx_buf;
816+
return ctx;
801817
}
802818

803819
static inline struct osdp_pd *cp_pd_array_alloc(int old_num_pd, int num_pd)
@@ -861,6 +877,12 @@ static inline struct osdp_pd *pd_static_ctx_pd_get(void)
861877
return &g_osdp_pd_ctx;
862878
}
863879

880+
static inline uint8_t *pd_static_rx_buf_get(void)
881+
{
882+
static uint8_t g_osdp_pd_rx_buf[OSDP_PACKET_BUF_SIZE];
883+
return g_osdp_pd_rx_buf;
884+
}
885+
864886
#ifdef OPT_OSDP_RX_ZERO_COPY
865887

866888
static inline struct osdp_rx_pkt *pd_static_rx_pkt_get(void)
@@ -883,13 +905,23 @@ static inline struct osdp_rb *pd_static_rx_rb_get(void)
883905

884906
static inline struct osdp *pd_ctx_alloc(void)
885907
{
908+
struct osdp *ctx;
886909
#ifdef OPT_OSDP_STATIC
887-
struct osdp *ctx = pd_static_ctx_get();
910+
ctx = pd_static_ctx_get();
888911
memset(ctx, 0, sizeof(struct osdp));
889-
return ctx;
912+
ctx->rx_buf = pd_static_rx_buf_get();
890913
#else
891-
return calloc(1, sizeof(struct osdp));
914+
ctx = calloc(1, sizeof(struct osdp));
915+
if (!ctx) {
916+
return NULL;
917+
}
918+
ctx->rx_buf = calloc(1, OSDP_PACKET_BUF_SIZE);
919+
if (!ctx->rx_buf) {
920+
free(ctx);
921+
return NULL;
922+
}
892923
#endif /* OPT_OSDP_STATIC */
924+
return ctx;
893925
}
894926

895927
static inline struct osdp_pd *pd_instance_alloc(void)

src/osdp_pd.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,8 @@ static int pd_send_reply(struct osdp_pd *pd)
10071007
if (ret < 0) {
10081008
return OSDP_PD_ERR_GENERIC;
10091009
}
1010+
pd->last_tx_len = (uint16_t)ret;
1011+
pd->last_cmd_id = (uint8_t)pd->cmd_id;
10101012
return OSDP_PD_ERR_NONE;
10111013
}
10121014

@@ -1032,6 +1034,12 @@ static int pd_send_reply(struct osdp_pd *pd)
10321034
return OSDP_PD_ERR_GENERIC;
10331035
}
10341036

1037+
/* Snapshot the just-sent reply for potential retransmit on seq
1038+
* repeat. The bytes (including CRC/MAC/encrypted payload) live in
1039+
* tx_buf and are preserved until the next reply is built. */
1040+
pd->last_tx_len = (uint16_t)ret;
1041+
pd->last_cmd_id = (uint8_t)pd->cmd_id;
1042+
10351043
return OSDP_PD_ERR_NONE;
10361044
}
10371045

@@ -1355,6 +1363,7 @@ void osdp_pd_teardown(osdp_t *ctx)
13551363
}
13561364
#endif /* OPT_OSDP_RX_ZERO_COPY */
13571365
safe_free(pd->file);
1366+
safe_free(pd_ctx->rx_buf);
13581367
safe_free(pd);
13591368
safe_free(ctx);
13601369
#endif

src/osdp_phy.c

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,9 @@ int osdp_phy_send_packet(struct osdp_pd *pd, uint8_t *buf,
395395
return OSDP_ERR_PKT_BUILD;
396396
}
397397

398-
return OSDP_ERR_PKT_NONE;
398+
/* return the number of bytes actually put on the wire so that
399+
* callers can cache the full finalized packet for retransmit */
400+
return len;
399401
}
400402

401403
static int phy_validate_header(struct osdp_pd *pd, uint8_t *buf,
@@ -578,16 +580,38 @@ static int phy_check_packet(struct osdp_pd *pd, uint8_t *buf, int pkt_len)
578580
* secure channels.
579581
*/
580582
phy_reset_seq_number(pd);
583+
pd->last_tx_len = 0;
581584
sc_deactivate(pd);
582585
}
583586
else if (comp == pd->seq_number) {
584587
/**
585-
* Sometimes, a CP re-sends the same command without
586-
* incrementing the sequence number. To handle such cases,
587-
* we will move the sequence back one step (with do_inc
588-
* set to -1) and then process the packet all over again
589-
* as if it was the first time we are seeing it.
588+
* CP re-sent the same command without incrementing the
589+
* sequence number. Per OSDP spec, we must re-emit the
590+
* previous reply bit-identically. Rebuilding it here would
591+
* corrupt the SC MAC chain (r_mac/c_mac have already
592+
* advanced) and re-consume state (event/card/file data).
593+
*
594+
* If we have a cached reply and the command id matches,
595+
* re-send the bytes still sitting in tx_buf (RX goes to a
596+
* separate rx_buf in PD mode, so tx_buf is preserved).
590597
*/
598+
if (pd->last_tx_len > 0) {
599+
int scb_len = (pkt->control & PKT_CONTROL_SCB) ?
600+
pkt->data[0] : 0;
601+
uint8_t rx_cmd_id = buf[sizeof(*pkt) + scb_len];
602+
603+
if (rx_cmd_id == pd->last_cmd_id) {
604+
LOG_INF("Seq repeat: retransmitting cached reply");
605+
osdp_channel_send(pd,
606+
osdp_tx_staging_buf(pd),
607+
pd->last_tx_len);
608+
return OSDP_ERR_PKT_SKIP;
609+
}
610+
/* seq matches but cmd id doesn't: drop the cache
611+
* and fall through to the normal path which will
612+
* MAC-verify and NAK if the packet is bogus. */
613+
pd->last_tx_len = 0;
614+
}
591615
phy_rollback_seq_number(pd);
592616
LOG_INF("Received a sequence repeat packet!");
593617
}
@@ -897,10 +921,11 @@ void osdp_phy_state_reset(struct osdp_pd *pd, bool is_error)
897921
pd->packet_buf_len = 0;
898922
pd->packet_len = 0;
899923
pd->phy_state = 0;
900-
pd->packet_buf = osdp_tx_staging_buf(pd);
924+
pd->packet_buf = pd_to_osdp(pd)->rx_buf;
901925
if (is_error) {
902926
pd->phy_retry_count = 0;
903927
pd->phy_tx_seq = 0;
928+
pd->last_tx_len = 0;
904929
phy_reset_seq_number(pd);
905930
if (channel->flush) {
906931
channel->flush(channel->data);

src/osdp_sc.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ void osdp_sc_setup(struct osdp_pd *pd)
242242

243243
osdp_crypt_setup();
244244

245+
/* Any cached retransmit reply belongs to the previous session and
246+
* must not be re-emitted after a handshake. */
247+
pd->last_tx_len = 0;
248+
245249
memcpy(scbk, pd->sc.scbk, 16);
246250
memset(&pd->sc, 0, sizeof(struct osdp_secure_channel));
247251
memcpy(pd->sc.scbk, scbk, 16);

tests/unit-tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_library(${LIB_OSDP_TEST} STATIC EXCLUDE_FROM_ALL
1919
list(APPEND OSDP_UNIT_TEST_SRC
2020
test.c
2121
test-cp-phy.c
22+
test-pd-phy.c
2223
test-cp-fsm.c
2324
test-file.c
2425
test-commands.c

0 commit comments

Comments
 (0)