Skip to content

Commit f0990cd

Browse files
committed
API: file: Add support for CP file tx notifications
File transfer command unlike other OSDP commands is a long running command that can finish with different status not just a success/failure outcome. Some of these status such as OK_REBOOTING is useful information that a CP might want to know about. This patch adds support for file transfer outcome notification. It also makes a breaking change where the file transfer status query moves the state machine to idle after the transfer is complete. So callers will have to rely on consuming these events moving forward -- all in-tree consumers are updated to the latest model. Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
1 parent 2a70403 commit f0990cd

11 files changed

Lines changed: 294 additions & 105 deletions

File tree

include/osdp.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,27 @@ enum osdp_event_notification_type {
909909
* arg0: status -- 0: offline; 1: online
910910
*/
911911
OSDP_EVENT_NOTIFICATION_PD_STATUS,
912+
/**
913+
* File transfer terminated (CP only).
914+
*
915+
* arg0: file ID that was being transferred
916+
* arg1: outcome -- see enum osdp_file_tx_outcome
917+
*
918+
* Fires exactly once per transfer, on any terminal state (success,
919+
* local abort, or a negative status reported by the PD).
920+
*/
921+
OSDP_EVENT_NOTIFICATION_FILE_TX_DONE,
922+
};
923+
924+
/**
925+
* @brief Outcome reported by OSDP_EVENT_NOTIFICATION_FILE_TX_DONE
926+
*/
927+
enum osdp_file_tx_outcome {
928+
OSDP_FILE_TX_OUTCOME_OK = 0, /**< Contents processed by PD */
929+
OSDP_FILE_TX_OUTCOME_OK_REBOOTING = 1, /**< OK; PD will now reset */
930+
OSDP_FILE_TX_OUTCOME_ABORTED = 2, /**< Transfer aborted (local or remote) */
931+
OSDP_FILE_TX_OUTCOME_UNRECOGNIZED = 3, /**< PD did not recognize file contents */
932+
OSDP_FILE_TX_OUTCOME_INVALID = 4, /**< PD rejected file data as malformed */
912933
};
913934

914935
/**

python/osdp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .key_store import KeyStore
1010
from .constants import (
1111
LibFlag, Command, CommandLEDColor, CommandFileTxFlags, Event, EventNotification,
12-
CardFormat, Capability, LogLevel, StatusReportType, CompletionStatus
12+
FileTxOutcome, CardFormat, Capability, LogLevel, StatusReportType, CompletionStatus
1313
)
1414
from .helpers import PdId, PDInfo, PDCapabilities
1515
from .channel import Channel

python/osdp/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ class EventNotification:
5959
Command = osdp_sys.EVENT_NOTIFICATION_COMMAND
6060
SecureChannelStatus = osdp_sys.EVENT_NOTIFICATION_SC_STATUS
6161
PeripheralDeviceStatus = osdp_sys.EVENT_NOTIFICATION_PD_STATUS
62+
FileTransferDone = osdp_sys.EVENT_NOTIFICATION_FILE_TX_DONE
63+
64+
class FileTxOutcome:
65+
Ok = osdp_sys.FILE_TX_OUTCOME_OK
66+
OkRebooting = osdp_sys.FILE_TX_OUTCOME_OK_REBOOTING
67+
Aborted = osdp_sys.FILE_TX_OUTCOME_ABORTED
68+
Unrecognized = osdp_sys.FILE_TX_OUTCOME_UNRECOGNIZED
69+
Invalid = osdp_sys.FILE_TX_OUTCOME_INVALID
6270

6371
class CompletionStatus:
6472
Ok = getattr(osdp_sys, "COMPLETION_OK", 0)

python/osdp_sys/module.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ void pyosdp_add_module_constants(PyObject *module)
6060
ADD_CONST("EVENT_NOTIFICATION_COMMAND", OSDP_EVENT_NOTIFICATION_COMMAND);
6161
ADD_CONST("EVENT_NOTIFICATION_SC_STATUS", OSDP_EVENT_NOTIFICATION_SC_STATUS);
6262
ADD_CONST("EVENT_NOTIFICATION_PD_STATUS", OSDP_EVENT_NOTIFICATION_PD_STATUS);
63+
ADD_CONST("EVENT_NOTIFICATION_FILE_TX_DONE", OSDP_EVENT_NOTIFICATION_FILE_TX_DONE);
64+
65+
/* For arg1 of OSDP_EVENT_NOTIFICATION_FILE_TX_DONE */
66+
ADD_CONST("FILE_TX_OUTCOME_OK", OSDP_FILE_TX_OUTCOME_OK);
67+
ADD_CONST("FILE_TX_OUTCOME_OK_REBOOTING", OSDP_FILE_TX_OUTCOME_OK_REBOOTING);
68+
ADD_CONST("FILE_TX_OUTCOME_ABORTED", OSDP_FILE_TX_OUTCOME_ABORTED);
69+
ADD_CONST("FILE_TX_OUTCOME_UNRECOGNIZED", OSDP_FILE_TX_OUTCOME_UNRECOGNIZED);
70+
ADD_CONST("FILE_TX_OUTCOME_INVALID", OSDP_FILE_TX_OUTCOME_INVALID);
6371

6472
ADD_CONST("COMPLETION_OK", OSDP_COMPLETION_OK);
6573
ADD_CONST("COMPLETION_FAILED", OSDP_COMPLETION_FAILED);

src/osdp_cp.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,24 @@ static void notify_sc_status(struct osdp_pd *pd)
979979
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
980980
}
981981

982+
void osdp_file_tx_notify_done(struct osdp_pd *pd, int file_id,
983+
enum osdp_file_tx_outcome outcome)
984+
{
985+
struct osdp *ctx = pd_to_osdp(pd);
986+
struct osdp_event evt;
987+
988+
if (!ctx->event_callback || !is_notifications_enabled(pd)) {
989+
return;
990+
}
991+
992+
evt.type = OSDP_EVENT_NOTIFICATION;
993+
evt.notif.type = OSDP_EVENT_NOTIFICATION_FILE_TX_DONE;
994+
evt.notif.arg0 = file_id;
995+
evt.notif.arg1 = outcome;
996+
ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt);
997+
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
998+
}
999+
9821000
static void cp_keyset_complete(struct osdp_pd *pd)
9831001
{
9841002
const struct osdp_cmd *cmd = pd->active_cmd;

src/osdp_file.c

Lines changed: 95 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define FILE_TRANSFER_HEADER_SIZE 11
1212
#define FILE_TRANSFER_STAT_SIZE 7
1313

14+
/* Wire-protocol status codes carried in struct osdp_cmd_file_stat::status */
1415
#define OSDP_FILE_TX_STATUS_ACK 0
1516
#define OSDP_FILE_TX_STATUS_CONTENTS_PROCESSED 1
1617
#define OSDP_FILE_TX_STATUS_PD_RESET 2
@@ -30,16 +31,64 @@ static inline void file_state_reset(struct osdp_file *f)
3031
f->length = 0;
3132
f->errors = 0;
3233
f->size = 0;
33-
f->state = OSDP_FILE_IDLE;
34+
f->state = OSDP_FILE_TX_STATE_IDLE;
35+
f->outcome = OSDP_FILE_TX_OUTCOME_OK;
36+
f->is_open = false;
3437
f->file_id = 0;
3538
f->tstamp = 0;
3639
f->wait_time_ms = 0;
3740
f->cancel_req = false;
3841
}
3942

40-
static inline bool file_tx_in_progress(struct osdp_file *f)
43+
static inline void file_close_if_open(struct osdp_pd *pd)
4144
{
42-
return f && f->state == OSDP_FILE_INPROG;
45+
struct osdp_file *f = TO_FILE(pd);
46+
47+
if (f->is_open) {
48+
if (f->ops.close(f->ops.arg) < 0) {
49+
LOG_ERR("File close failed; continuing");
50+
}
51+
f->is_open = false;
52+
}
53+
}
54+
55+
/* Converge every terminal path here: close the file, emit the
56+
* notification (CP only), reset to IDLE. */
57+
static void file_transition_done(struct osdp_pd *pd,
58+
enum osdp_file_tx_outcome outcome)
59+
{
60+
struct osdp_file *f = TO_FILE(pd);
61+
int file_id = f->file_id;
62+
63+
file_close_if_open(pd);
64+
f->outcome = outcome;
65+
f->state = OSDP_FILE_TX_STATE_DONE;
66+
67+
if (is_cp_mode(pd)) {
68+
if (outcome == OSDP_FILE_TX_OUTCOME_OK_REBOOTING) {
69+
make_request(pd, CP_REQ_OFFLINE);
70+
}
71+
osdp_file_tx_notify_done(pd, file_id, outcome);
72+
}
73+
74+
file_state_reset(f);
75+
}
76+
77+
static enum osdp_file_tx_outcome file_outcome_from_wire_status(int16_t status)
78+
{
79+
switch (status) {
80+
case OSDP_FILE_TX_STATUS_CONTENTS_PROCESSED:
81+
return OSDP_FILE_TX_OUTCOME_OK;
82+
case OSDP_FILE_TX_STATUS_PD_RESET:
83+
return OSDP_FILE_TX_OUTCOME_OK_REBOOTING;
84+
case OSDP_FILE_TX_STATUS_ERR_UNKNOWN:
85+
return OSDP_FILE_TX_OUTCOME_UNRECOGNIZED;
86+
case OSDP_FILE_TX_STATUS_ERR_INVALID:
87+
return OSDP_FILE_TX_OUTCOME_INVALID;
88+
case OSDP_FILE_TX_STATUS_ERR_ABORT:
89+
default:
90+
return OSDP_FILE_TX_OUTCOME_ABORTED;
91+
}
4392
}
4493

4594
/* --- Sender CMD/RESP Handers --- */
@@ -61,12 +110,11 @@ int osdp_file_cmd_tx_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
61110
struct osdp_file *f = TO_FILE(pd);
62111
uint8_t *data = buf + FILE_TRANSFER_HEADER_SIZE;
63112

64-
/**
65-
* We should never reach this function if a valid file transfer as in
66-
* progress.
67-
*/
68-
BUG_ON(f == NULL);
69-
BUG_ON(f->state != OSDP_FILE_INPROG && f->state != OSDP_FILE_KEEP_ALIVE);
113+
/* Reached only once a transfer is live; osdp_file_tx_get_command()
114+
* has already gated on pd->file and state. */
115+
assert(f != NULL);
116+
assert(f->state == OSDP_FILE_TX_STATE_INPROG ||
117+
f->state == OSDP_FILE_TX_STATE_WAIT);
70118

71119
if ((size_t)max_len <= FILE_TRANSFER_HEADER_SIZE) {
72120
LOG_ERR("TX_Build: insufficient space; need:%d have:%d",
@@ -78,7 +126,7 @@ int osdp_file_cmd_tx_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
78126
LOG_WRN("TX_Build: Ignoring plaintext file transfer request");
79127
}
80128

81-
if (f->state == OSDP_FILE_KEEP_ALIVE) {
129+
if (f->state == OSDP_FILE_TX_STATE_WAIT) {
82130
LOG_DBG("TX_Build: keep-alive");
83131
write_file_tx_header(f, buf);
84132
return FILE_TRANSFER_HEADER_SIZE;
@@ -113,14 +161,13 @@ int osdp_file_cmd_tx_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
113161

114162
reply_abort:
115163
LOG_ERR("TX_Build: Aborting file transfer due to unrecoverable error!");
116-
file_state_reset(f);
164+
file_transition_done(pd, OSDP_FILE_TX_OUTCOME_ABORTED);
117165
return -1;
118166
}
119167

120168
int osdp_file_cmd_stat_decode(struct osdp_pd *pd, uint8_t *buf, int len)
121169
{
122170
int pos = 0;
123-
bool do_close = false;
124171
struct osdp_file *f = TO_FILE(pd);
125172
struct osdp_cmd_file_stat stat;
126173

@@ -129,7 +176,8 @@ int osdp_file_cmd_stat_decode(struct osdp_pd *pd, uint8_t *buf, int len)
129176
return -1;
130177
}
131178

132-
if (f->state != OSDP_FILE_INPROG && f->state != OSDP_FILE_KEEP_ALIVE) {
179+
if (f->state != OSDP_FILE_TX_STATE_INPROG &&
180+
f->state != OSDP_FILE_TX_STATE_WAIT) {
133181
LOG_ERR("Stat_Decode: File transfer is not in progress!");
134182
return -1;
135183
}
@@ -154,41 +202,33 @@ int osdp_file_cmd_stat_decode(struct osdp_pd *pd, uint8_t *buf, int len)
154202
SET_FLAG_V(f, OSDP_FILE_TX_FLAG_POLL_RESP, stat.control & 0x04)
155203

156204
f->offset += f->length;
157-
do_close = f->length && (f->offset == f->size);
158205
f->wait_time_ms = stat.delay;
159206
f->tstamp = osdp_millis_now();
160207
f->length = 0;
161208
f->errors = 0;
162209

163-
if (f->offset != f->size) {
164-
/* Transfer is in progress */
165-
return 0;
210+
if (stat.status < 0) {
211+
LOG_ERR("Stat_Decode: File transfer error; "
212+
"status:%d offset:%d", stat.status, f->offset);
213+
file_transition_done(pd,
214+
file_outcome_from_wire_status(stat.status));
215+
return -1;
166216
}
167217

168-
/* File transfer complete; close file and end file transfer */
169-
170-
if (do_close && f->ops.close(f->ops.arg) < 0) {
171-
LOG_ERR("Stat_Decode: Close failed! ... continuing");
218+
if (f->offset != f->size) {
219+
/* Transfer still in progress. */
220+
return 0;
172221
}
173222

174-
switch (stat.status) {
175-
case OSDP_FILE_TX_STATUS_KEEP_ALIVE:
176-
f->state = OSDP_FILE_KEEP_ALIVE;
223+
if (stat.status == OSDP_FILE_TX_STATUS_KEEP_ALIVE) {
224+
f->state = OSDP_FILE_TX_STATE_WAIT;
177225
LOG_INF("Stat_Decode: File transfer done; keep alive");
178226
return 0;
179-
case OSDP_FILE_TX_STATUS_PD_RESET:
180-
make_request(pd, CP_REQ_OFFLINE);
181-
__fallthrough;
182-
case OSDP_FILE_TX_STATUS_CONTENTS_PROCESSED:
183-
f->state = OSDP_FILE_DONE;
184-
LOG_INF("Stat_Decode: File transfer complete");
185-
return 0;
186-
default:
187-
LOG_ERR("Stat_Decode: File transfer error; "
188-
"status:%d offset:%d", stat.status, f->offset);
189-
f->errors++;
190-
return -1;
191227
}
228+
229+
LOG_INF("Stat_Decode: File transfer complete");
230+
file_transition_done(pd, file_outcome_from_wire_status(stat.status));
231+
return 0;
192232
}
193233

194234
/* --- Receiver CMD/RESP Handler --- */
@@ -220,18 +260,17 @@ int osdp_file_cmd_tx_decode(struct osdp_pd *pd, uint8_t *buf, int len)
220260
assert(pos == sizeof(struct osdp_cmd_file_xfer));
221261
assert(xfer.length + pos == len);
222262

223-
if (f->state == OSDP_FILE_IDLE || f->state == OSDP_FILE_DONE) {
263+
if (f->state == OSDP_FILE_TX_STATE_IDLE) {
224264
if (pd->command_callback) {
225-
/**
226-
* Notify app of this command and make sure
227-
* we can proceed
228-
*/
265+
/* Notify app of this command and make sure we can
266+
* proceed */
229267
cmd.id = OSDP_CMD_FILE_TX;
230268
cmd.file_tx.flags = f->flags;
231269
cmd.file_tx.id = xfer.type;
232270
rc = pd->command_callback(pd->command_callback_arg, &cmd);
233-
if (rc < 0)
271+
if (rc < 0) {
234272
return -1;
273+
}
235274
}
236275

237276
/* new file write request */
@@ -245,10 +284,11 @@ int osdp_file_cmd_tx_decode(struct osdp_pd *pd, uint8_t *buf, int len)
245284
file_state_reset(f);
246285
f->file_id = xfer.type;
247286
f->size = xfer.size;
248-
f->state = OSDP_FILE_INPROG;
287+
f->is_open = true;
288+
f->state = OSDP_FILE_TX_STATE_INPROG;
249289
}
250290

251-
if (f->state != OSDP_FILE_INPROG) {
291+
if (f->state != OSDP_FILE_TX_STATE_INPROG) {
252292
LOG_ERR("TX_Decode: File transfer is not in progress!");
253293
return -1;
254294
}
@@ -278,7 +318,7 @@ int osdp_file_cmd_stat_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
278318
return -1;
279319
}
280320

281-
if (f->state != OSDP_FILE_INPROG) {
321+
if (f->state != OSDP_FILE_TX_STATE_INPROG) {
282322
LOG_ERR("Stat_Build: File transfer is not in progress!");
283323
return -1;
284324
}
@@ -298,13 +338,9 @@ int osdp_file_cmd_stat_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
298338
f->length = 0;
299339
assert(f->offset <= f->size);
300340
if (f->offset == f->size) { /* EOF */
301-
if (f->ops.close(f->ops.arg) < 0) {
302-
LOG_ERR("Stat_Build: Close failed!");
303-
return -1;
304-
}
305-
f->state = OSDP_FILE_DONE;
306341
stat.status = OSDP_FILE_TX_STATUS_CONTENTS_PROCESSED;
307342
LOG_INF("TX_Decode: File receive complete");
343+
file_transition_done(pd, OSDP_FILE_TX_OUTCOME_OK);
308344
}
309345

310346
/* fill the packet buffer (layout: struct osdp_cmd_file_stat) */
@@ -321,11 +357,8 @@ int osdp_file_cmd_stat_build(struct osdp_pd *pd, uint8_t *buf, int max_len)
321357

322358
void osdp_file_tx_abort(struct osdp_pd *pd)
323359
{
324-
struct osdp_file *f = TO_FILE(pd);
325-
326-
if (file_tx_in_progress(f)) {
327-
f->ops.close(f->ops.arg);
328-
file_state_reset(f);
360+
if (osdp_file_tx_is_active(pd)) {
361+
file_transition_done(pd, OSDP_FILE_TX_OUTCOME_ABORTED);
329362
}
330363
}
331364

@@ -341,13 +374,13 @@ int osdp_file_tx_get_command(struct osdp_pd *pd)
341374
{
342375
struct osdp_file *f = TO_FILE(pd);
343376

344-
if (!f || f->state == OSDP_FILE_IDLE || f->state == OSDP_FILE_DONE) {
377+
if (!osdp_file_tx_is_active(pd)) {
345378
return 0;
346379
}
347380

348381
if (f->errors > OSDP_FILE_ERROR_RETRY_MAX || f->cancel_req) {
349382
LOG_ERR("Aborting transfer of file fd:%d", f->file_id);
350-
osdp_file_tx_abort(pd);
383+
file_transition_done(pd, OSDP_FILE_TX_OUTCOME_ABORTED);
351384
return CMD_ABORT;
352385
}
353386

@@ -376,7 +409,7 @@ int osdp_file_tx_command(struct osdp_pd *pd, int file_id, uint32_t flags)
376409
return -1;
377410
}
378411

379-
if (file_tx_in_progress(f)) {
412+
if (osdp_file_tx_is_active(pd)) {
380413
if (flags & OSDP_CMD_FILE_TX_FLAG_CANCEL) {
381414
if (file_id == f->file_id) {
382415
f->cancel_req = true;
@@ -410,7 +443,8 @@ int osdp_file_tx_command(struct osdp_pd *pd, int file_id, uint32_t flags)
410443
f->flags = flags;
411444
f->file_id = file_id;
412445
f->size = size;
413-
f->state = OSDP_FILE_INPROG;
446+
f->is_open = true;
447+
f->state = OSDP_FILE_TX_STATE_INPROG;
414448
return 0;
415449
}
416450

@@ -464,7 +498,8 @@ int osdp_get_file_tx_status(const osdp_t *ctx, int pd_idx,
464498
input_check(ctx, pd_idx);
465499
struct osdp_file *f = TO_FILE(osdp_to_pd(ctx, pd_idx));
466500

467-
if (f->state != OSDP_FILE_INPROG && f->state != OSDP_FILE_DONE) {
501+
if (!f || (f->state != OSDP_FILE_TX_STATE_INPROG &&
502+
f->state != OSDP_FILE_TX_STATE_WAIT)) {
468503
LOG_PRINT("File TX not in progress");
469504
return -1;
470505
}

0 commit comments

Comments
 (0)