From eefa5f10faa6d86de51a2cf96673f2042a265976 Mon Sep 17 00:00:00 2001 From: Siddharth Chandrasekaran Date: Tue, 4 Jun 2024 07:09:14 +0200 Subject: [PATCH] API: Add support notification events This patch adds a new event type "notification" which allows LibOSDP to generate some local events such as app command outcome and secure channel state changes and notify the CP app about it. Also, update tests to use this new API and fix others so they don't get confused by the new SC status report notification. Fixes: #182 Signed-off-by: Siddharth Chandrasekaran --- include/osdp.h | 39 +++++++++++++++++++++ python/osdp/__init__.py | 4 +-- python/osdp/constants.py | 5 +++ python/osdp_sys/data.c | 35 +++++++++++++++++++ python/osdp_sys/module.c | 4 +++ src/osdp_cp.c | 64 +++++++++++++++++++++++++++++++++-- tests/pytest/test_commands.py | 22 ++++++++++++ tests/pytest/test_events.py | 19 +++++++---- 8 files changed, 181 insertions(+), 11 deletions(-) diff --git a/include/osdp.h b/include/osdp.h index 1220c451..d93a9029 100644 --- a/include/osdp.h +++ b/include/osdp.h @@ -779,6 +779,43 @@ struct osdp_event_mfgrep { uint8_t data[OSDP_EVENT_MFGREP_MAX_DATALEN]; }; +/** + * @brief LibOSDP event notification type + */ +enum osdp_event_notification_type { + /** + * Application command outcome report. + * + * arg0: The command ID + * arg1: outcome -- 0: success; -1: failure; + */ + OSDP_EVENT_NOTIFICATION_COMMAND, + /** + * Secure Channel state change + * + * arg0: status -- 0: inactive; 1: active + * arg1: scbk type -- 0: scbk; 1: scbk-d + */ + OSDP_EVENT_NOTIFICATION_SC_STATUS, +}; + +/** + * @brief LibOSDP event notification + * + * These are events generated by LibOSDP for the application to indicate various + * status such as external command outcomes, SC state change notifications, etc. + * The app can use these events to perform housekeeping activities as needed. + * + * Each notification event type can use the provided additional data members + * @a arg0, @a arg1, ... in custom ways. See @ref osdp_event_notification_type + * for documentation on how to use them. + */ +struct osdp_event_notification { + enum osdp_event_notification_type type; /**< Notification type */ + int arg0; /**< Additional data member */ + int arg1; /**< Additional data member */ +}; + /** * @brief OSDP PD Events */ @@ -787,6 +824,7 @@ enum osdp_event_type { OSDP_EVENT_KEYPRESS, /**< Keypad press event */ OSDP_EVENT_MFGREP, /**< Manufacturer specific reply event */ OSDP_EVENT_STATUS, /**< Status event */ + OSDP_EVENT_NOTIFICATION, /**< LibOSDP notification event */ OSDP_EVENT_SENTINEL /**< Max event value */ }; @@ -804,6 +842,7 @@ struct osdp_event { struct osdp_event_cardread cardread; /**< Card read event structure */ struct osdp_event_mfgrep mfgrep; /**< Manufacturer specific response event struture */ struct osdp_status_report status; /**< Status report event structure */ + struct osdp_event_notification notif;/**< Notification event structure */ }; }; diff --git a/python/osdp/__init__.py b/python/osdp/__init__.py index 241fac65..b1c3975a 100644 --- a/python/osdp/__init__.py +++ b/python/osdp/__init__.py @@ -8,8 +8,8 @@ from .peripheral_device import PeripheralDevice from .key_store import KeyStore from .constants import ( - LibFlag, Command, CommandLEDColor, CommandFileTxFlags, - Event, CardFormat, Capability, LogLevel, StatusReportType + LibFlag, Command, CommandLEDColor, CommandFileTxFlags, Event, EventNotification, + CardFormat, Capability, LogLevel, StatusReportType ) from .helpers import PdId, PDInfo, PDCapabilities from .channel import Channel diff --git a/python/osdp/constants.py b/python/osdp/constants.py index e8fbb228..3b6e4219 100644 --- a/python/osdp/constants.py +++ b/python/osdp/constants.py @@ -51,11 +51,16 @@ class CommandLEDColor: class CommandFileTxFlags: Cancel = osdp_sys.CMD_FILE_TX_FLAG_CANCEL +class EventNotification: + Command = osdp_sys.EVENT_NOTIFICATION_COMMAND + SecureChannelStatus = osdp_sys.EVENT_NOTIFICATION_SC_STATUS + class Event: CardRead = osdp_sys.EVENT_CARDREAD KeyPress = osdp_sys.EVENT_KEYPRESS ManufacturerReply = osdp_sys.EVENT_MFGREP Status = osdp_sys.EVENT_STATUS + Notification = osdp_sys.EVENT_NOTIFICATION class CardFormat: Unspecified = osdp_sys.CARD_FMT_RAW_UNSPECIFIED diff --git a/python/osdp_sys/data.c b/python/osdp_sys/data.c index 8227a14f..75490aae 100644 --- a/python/osdp_sys/data.c +++ b/python/osdp_sys/data.c @@ -543,6 +543,37 @@ static int pyosdp_make_struct_event_status(struct osdp_event *p, PyObject *dict) return 0; } +static int pyosdp_make_dict_event_notif(PyObject *obj, struct osdp_event *event) +{ + if (pyosdp_dict_add_int(obj, "type", event->notif.type)) + return -1; + if (pyosdp_dict_add_int(obj, "arg0", event->notif.arg0)) + return -1; + if (pyosdp_dict_add_int(obj, "arg1", event->notif.arg1)) + return -1; + return 0; +} + +static int pyosdp_make_struct_event_notif(struct osdp_event *p, PyObject *dict) +{ + int type, arg0, arg1; + struct osdp_event_notification *ev = &p->notif; + + if (pyosdp_dict_get_int(dict, "type", &type)) + return -1; + + if (pyosdp_dict_get_int(dict, "arg0", &arg0)) + return -1; + + if (pyosdp_dict_get_int(dict, "arg1", &arg1)) + return -1; + + ev->type = type; + ev->arg0 = arg0; + ev->arg1 = arg1; + return 0; +} + static struct { int (*dict_to_struct)(struct osdp_cmd *, PyObject *); int (*struct_to_dict)(PyObject *, struct osdp_cmd *); @@ -605,6 +636,10 @@ static struct { .struct_to_dict = pyosdp_make_dict_event_status, .dict_to_struct = pyosdp_make_struct_event_status, }, + [OSDP_EVENT_NOTIFICATION] = { + .struct_to_dict = pyosdp_make_dict_event_notif, + .dict_to_struct = pyosdp_make_struct_event_notif, + }, }; /* --- Exposed Methods --- */ diff --git a/python/osdp_sys/module.c b/python/osdp_sys/module.c index 48ae849d..8a54ee8a 100644 --- a/python/osdp_sys/module.c +++ b/python/osdp_sys/module.c @@ -52,11 +52,15 @@ void pyosdp_add_module_constants(PyObject *module) /* For `struct osdp_cmd_file_tx`::flags */ ADD_CONST("CMD_FILE_TX_FLAG_CANCEL", OSDP_CMD_FILE_TX_FLAG_CANCEL); + ADD_CONST("EVENT_NOTIFICATION_COMMAND", OSDP_EVENT_NOTIFICATION_COMMAND); + ADD_CONST("EVENT_NOTIFICATION_SC_STATUS", OSDP_EVENT_NOTIFICATION_SC_STATUS); + /* enum osdp_event_type */ ADD_CONST("EVENT_CARDREAD", OSDP_EVENT_CARDREAD); ADD_CONST("EVENT_KEYPRESS", OSDP_EVENT_KEYPRESS); ADD_CONST("EVENT_MFGREP", OSDP_EVENT_MFGREP); ADD_CONST("EVENT_STATUS", OSDP_EVENT_STATUS); + ADD_CONST("EVENT_NOTIFICATION", OSDP_EVENT_NOTIFICATION); /* enum osdp_led_color_e */ ADD_CONST("LED_COLOR_NONE", OSDP_LED_COLOR_NONE); diff --git a/src/osdp_cp.c b/src/osdp_cp.c index 68c4f4d2..1c181652 100644 --- a/src/osdp_cp.c +++ b/src/osdp_cp.c @@ -761,7 +761,7 @@ static inline bool cp_sc_should_retry(struct osdp_pd *pd) osdp_millis_since(pd->sc_tstamp) > OSDP_PD_SC_RETRY_MS); } -int cp_translate_cmd(struct osdp_pd *pd, struct osdp_cmd *cmd) +static int cp_translate_cmd(struct osdp_pd *pd, struct osdp_cmd *cmd) { int cmd_id = -1; @@ -940,6 +940,21 @@ static int cp_get_online_command(struct osdp_pd *pd) return -1; } +static void notify_sc_status(struct osdp_pd *pd) +{ + struct osdp *ctx = pd_to_osdp(pd); + struct osdp_event evt; + + + evt.type = OSDP_EVENT_NOTIFICATION; + evt.notif.type = OSDP_EVENT_NOTIFICATION_SC_STATUS; + evt.notif.arg0 = sc_is_active(pd); + evt.notif.arg1 = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD); + if (ctx->event_callback) { + ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt); + } +} + static void cp_keyset_complete(struct osdp_pd *pd) { struct osdp_cmd *cmd; @@ -951,6 +966,7 @@ static void cp_keyset_complete(struct osdp_pd *pd) CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD); } sc_deactivate(pd); + notify_sc_status(pd); make_request(pd, CP_REQ_RESTART_SC); LOG_INF("SCBK set; restarting SC to verify new SCBK"); } @@ -1060,6 +1076,7 @@ static enum osdp_cp_state_e get_next_ok_state(struct osdp_pd *pd) return OSDP_CP_STATE_SC_SCRYPT; case OSDP_CP_STATE_SC_SCRYPT: sc_activate(pd); + notify_sc_status(pd); if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) { LOG_WRN("SC active with SCBK-D. Set SCBK"); fill_local_keyset_cmd(pd); @@ -1119,6 +1136,7 @@ static enum osdp_cp_state_e get_next_err_state(struct osdp_pd *pd) return OSDP_CP_STATE_ONLINE; case OSDP_CP_STATE_SET_SCBK: sc_deactivate(pd); + notify_sc_status(pd); if (is_enforce_secure(pd) || ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) { LOG_ERR("Failed to set SCBK; " @@ -1173,6 +1191,7 @@ static void cp_state_change(struct osdp_pd *pd, enum osdp_cp_state_e next) } } sc_deactivate(pd); + notify_sc_status(pd); LOG_ERR("Going offline for %d seconds; Was in '%s' state", pd->wait_ms / 1000, state_get_name(cur)); break; @@ -1193,10 +1212,47 @@ static void cp_state_change(struct osdp_pd *pd, enum osdp_cp_state_e next) pd->state = next; } +static void notify_command_status(struct osdp_pd *pd, int status) +{ + int app_cmd; + struct osdp_event evt; + struct osdp *ctx = pd_to_osdp(pd); + + switch (pd->cmd_id) { + case CMD_OUT: app_cmd = OSDP_CMD_OUTPUT; break; + case CMD_LED: app_cmd = OSDP_CMD_LED; break; + case CMD_BUZ: app_cmd = OSDP_CMD_BUZZER; break; + case CMD_TEXT: app_cmd = OSDP_CMD_TEXT; break; + case CMD_COMSET: app_cmd = OSDP_CMD_COMSET; break; + case CMD_ISTAT: app_cmd = OSDP_CMD_STATUS; break; + case CMD_OSTAT: app_cmd = OSDP_CMD_STATUS; break; + case CMD_LSTAT: app_cmd = OSDP_CMD_STATUS; break; + case CMD_RSTAT: app_cmd = OSDP_CMD_STATUS; break; + case CMD_KEYSET: app_cmd = OSDP_CMD_KEYSET; break; + case CMD_MFG: + if (pd->reply_id == REPLY_ACK) { + app_cmd = OSDP_CMD_MFG; + break; + } + default: + return; + } + + evt.type = OSDP_EVENT_NOTIFICATION; + evt.notif.type = OSDP_EVENT_NOTIFICATION_COMMAND; + evt.notif.arg0 = app_cmd; + evt.notif.arg1 = status; + + if (ctx->event_callback) { + ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt); + } +} + static int state_update(struct osdp_pd *pd) { - enum osdp_cp_state_e next, cur = pd->state; int err; + bool status; + enum osdp_cp_state_e next, cur = pd->state; if (cp_phy_running(pd)) { err = cp_phy_state_update(pd); @@ -1217,7 +1273,9 @@ static int state_update(struct osdp_pd *pd) err = OSDP_CP_ERR_GENERIC; __fallthrough; case OSDP_CP_PHY_STATE_DONE: - if (!state_check_reply(pd)) { + status = state_check_reply(pd); + notify_command_status(pd, status); + if (!status) { err = OSDP_CP_ERR_GENERIC; } osdp_phy_state_reset(pd, false); diff --git a/tests/pytest/test_commands.py b/tests/pytest/test_commands.py index 831f228a..34dde126 100644 --- a/tests/pytest/test_commands.py +++ b/tests/pytest/test_commands.py @@ -50,6 +50,20 @@ cp = ControlPanel(pd_info_list, log_level=LogLevel.Debug) +def cp_check_command_status(cmd): + event = { + 'event': Event.Notification, + 'type': EventNotification.Command, + 'arg0': cmd, + 'arg1': 1, + } + while True: + e = cp.get_event(secure_pd.address, timeout=1) + if (e['event'] == Event.Notification and + e['type'] == EventNotification.Command): + break + assert e == event + @pytest.fixture(scope='module', autouse=True) def setup_test(): for pd in pd_list: @@ -76,6 +90,7 @@ def test_command_output(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Output) def test_command_buzzer(): test_cmd = { @@ -89,6 +104,7 @@ def test_command_buzzer(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Buzzer) def test_command_text(): test_cmd = { @@ -103,6 +119,7 @@ def test_command_text(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Text) def test_command_led(): test_cmd = { @@ -119,12 +136,14 @@ def test_command_led(): } assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.LED) test_cmd['temporary'] = False del test_cmd['timer_count'] assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.LED) def test_command_comset(): test_cmd = { @@ -135,6 +154,7 @@ def test_command_comset(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Comset) def test_command_mfg(): test_cmd = { @@ -146,6 +166,7 @@ def test_command_mfg(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Manufacturer) def test_command_keyset(): test_cmd = { @@ -156,6 +177,7 @@ def test_command_keyset(): assert cp.is_online(secure_pd_addr) assert cp.send_command(secure_pd_addr, test_cmd) assert secure_pd.get_command() == test_cmd + cp_check_command_status(Command.Keyset) # PD must be online and SC active after a KEYSET command time.sleep(0.5) diff --git a/tests/pytest/test_events.py b/tests/pytest/test_events.py index cef04fd3..363fd468 100644 --- a/tests/pytest/test_events.py +++ b/tests/pytest/test_events.py @@ -51,6 +51,13 @@ def teardown_test(): pd.teardown() cleanup_fifo_pair("events") +def check_event(event): + while True: + e = cp.get_event(secure_pd.address) + if e['event'] != Event.Notification: + break + assert e == event + def test_event_keypad(): event = { 'event': Event.KeyPress, @@ -58,7 +65,7 @@ def test_event_keypad(): 'data': bytes([9,1,9,2,6,3,1,7,7,0]), } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event) def test_event_mfg_reply(): event = { @@ -68,7 +75,7 @@ def test_event_mfg_reply(): 'data': bytes([9,1,9,2,6,3,1,7,7,0]), } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event) def test_event_cardread_ascii(): event = { @@ -79,7 +86,7 @@ def test_event_cardread_ascii(): 'data': bytes([9,1,9,2,6,3,1,7,7,0]), } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event) def test_event_cardread_wiegand(): event = { @@ -91,7 +98,7 @@ def test_event_cardread_wiegand(): 'data': bytes([0x55, 0xAA]), } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event) def test_event_input(): event = { @@ -101,7 +108,7 @@ def test_event_input(): 'mask': 0xAA, # bit mask of input/output status (upto 32) } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event) def test_event_output(): event = { @@ -111,4 +118,4 @@ def test_event_output(): 'mask': 0x55, # bit mask of input/output status (upto 32) } secure_pd.notify_event(event) - assert cp.get_event(secure_pd.address) == event + check_event(event)