Skip to content

Commit

Permalink
API: Add support notification events
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
sidcha committed Jun 6, 2024
1 parent 120ad81 commit eefa5f1
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 11 deletions.
39 changes: 39 additions & 0 deletions include/osdp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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 */
};

Expand All @@ -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 */
};
};

Expand Down
4 changes: 2 additions & 2 deletions python/osdp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions python/osdp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions python/osdp_sys/data.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down Expand Up @@ -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 --- */
Expand Down
4 changes: 4 additions & 0 deletions python/osdp_sys/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
64 changes: 61 additions & 3 deletions src/osdp_cp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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");
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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; "
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions tests/pytest/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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)
Expand Down
Loading

0 comments on commit eefa5f1

Please sign in to comment.