From 3f3c5a797caff13db1258e4ed3846cc6a8268e05 Mon Sep 17 00:00:00 2001 From: Eric Thorne Date: Wed, 20 Oct 2021 11:48:53 -0700 Subject: [PATCH] Release v2.0.1 See nexus/RELEASE_NOTES --- README.md | 172 ++++++++---------- nexus/RELEASE_NOTES | 14 ++ .../.vscode/extensions.json | 7 - .../.vscode/extensions.json | 7 - nexus/include/MODULE_VERSION.h | 2 +- nexus/include/nxp_common.h | 18 +- nexus/src/nexus_channel_res_lm.c | 129 ++++++++----- nexus/test/test_nexus_channel_res_link_hs.c | 72 +------- nexus/test/test_nexus_channel_res_lm.c | 169 ++++++++++++++++- 9 files changed, 355 insertions(+), 235 deletions(-) delete mode 100644 nexus/examples/Nexus_Channel_Accessory_F103RB/.vscode/extensions.json delete mode 100644 nexus/examples/Nexus_Channel_Controller_F103RB/.vscode/extensions.json diff --git a/README.md b/README.md index c462012..546cf29 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Nexus Firmware Libraries -This repository contains the embedded implementations of two [Nexus](https://nexus.angaza.com/) technologies: -- [Nexus Keycode](https://nexus.angaza.com/keycode) (an interoperable PAYG token system deployed in millions of devices) -- [Nexus Channel](https://nexus.angaza.com/channel) (an application layer for secure device-to-device communication) - +This repository contains the embedded implementations of Nexus technology. These platform-independent libraries are standard, portable C99 requiring no dynamic memory allocation, suitable for use on highly constrained embedded platforms. @@ -12,83 +9,15 @@ embedded platforms. [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=angaza_nexus-keycode-embedded-internal&metric=alert_status&token=3c0218f9fde1d544fd2060ec1075c15fefeffd4f)](https://sonarcloud.io/dashboard?id=angaza_nexus-keycode-embedded-internal) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=angaza_nexus-keycode-embedded-internal&metric=coverage&token=3c0218f9fde1d544fd2060ec1075c15fefeffd4f)](https://sonarcloud.io/dashboard?id=angaza_nexus-keycode-embedded-internal) -## BEFORE YOU BEGIN -1. Make sure you're familiar with Angaza's [Integration Process](https://nexus.angaza.com/mfg_home#integration-overview) -2. Review Angaza's [PAYG Requirements](https://nexus.angaza.com/mfg_home#payg-requirements) and [Tamper Mitigation Strategies](https://nexus.angaza.com/mfg_home#tamper-mitigation-strategies) to ensure that your product can be sold on Angaza and that common tamper risks have been eliminated - -## INTEGRATION PROCESS -1. Download the source code of this repository -2. Copy the [`nexus` directory](#nexus-directory-structure) into your project -3. Decide which [Configuration Options](#configuration-options) you want to use for your project -4. Run the [Configuration Tool](#configuration-setup) -5. [Integrate](#implementation-details) the Nexus library with your product firmware - -## ADDITIONAL INFORMATION +## Using in Your Project -### Nexus Directory Structure - -The directory contains these folders: - -* `nexus/include` - Header files that must be included in a project using the -Nexus embedded solutions (do not modify) -* `nexus/src` - Nexus module implementation files (do not modify) -* `nexus/oc` - IoTivity-based files for Nexus Channel (do not modify) -* `nexus/utils` - Nexus support utilities and functions (do not modify) -* `nexus/stub` - Stub functions used during static analysis -* `nexus/build` - temporary output artifacts related to unit tests and static -* `nexus/test` - Unit tests for the code contained in `src` -* `nexus/examples` - Examples of the Nexus protocol in use - -You must add include paths in your project to the following subset: -* `nexus` -* `nexus/src` -* `nexus/include` -* `nexus/utils` -* `nexus/oc` (Required only for Nexus Channel or Nexus debug logs) - -### Configuration Options - -This library contains an interactive tool to select which features and configuration options you want to use for your project. The tool will automatically update and saves your configuration into a header. This header is used by Nexus code to determine which features to expose to your product firmware. -- Nexus Keycode: turn on if you want to implement the nexus keycode (likely yes) -- Protocol Type: what digits are available on your product keypad? - - Full Pad (0-9) OR Small Pad (0-5) -- Nexus Keycode Rate Limiting: feature to prevent brute force attacks - - Enabled OR Disabled - - See [FAQ](#faq) for more info on settings -- "Universal" Factory Test Codes: feature to give access to special tokens that can be used in a factory, manufacturing or test setting. - - Enabled OR Disabled - - See [FAQ](#faq) for more info on settings -- Keycode Entry - - The number of seconds between user keypresses before nexus resets to accept a new keycode. -- Nexus Channel: turn on if you're looking for device to device communication - -### Configuration Setup - -To set up this configuration: -1. Go to the `nexus` directory (required because the tools needs the `Kconfiglib` files are stored here) -2. Run the python tool located at `nexus/conf_nexus.py`. (any platform using python 3) - -``` -python conf_nexus.py -``` -4) You may also need to install the package `python3-tk`. -5) Once the interactive menu opens, use "enter" to toggle each option on and off. Use arrow keys to move between options. - Note: For Nexus Keycode only, turn off oc-logging and nexus-channel -6) Select options (see below for more info about each option). -7) Save (shift+S) -8) Now the setup is done! You can proceed with development. - -### Implementation Details - -Warning: Do NOT modify any of the src code. - -The `nexus/include` file contains all of the information you need to integrate with nexus. - -Within `nexus/include` there are files that begin with: -- `nxp_` (i.e nxp_keycode.h) - these contain the functions that *your code* must implement. These are functions that nexus src code will call. -- `nx_` (i.e nx_keycode.h) - these contain the functions available in the Nexus system and modules that *your code* must call and utilize at the appropriate times. - -**Additional Information** +1. Copy the `nexus` directory into your project +2. Add include paths for `nexus` and `nexus/include` +3. Run `cd nexus && python conf_nexus.py` and select configuration options (*Important*: Must run `python conf_nexus.py` from within the `nexus` folder). +4. Implement the functions specified in `nexus/include/nxp_common.h` +5. [Keycode only] Implement the functions specified in `nexus/include/nxp_keycode.h` +6. [Channel only] Implement the functions specified in `nexus/include/nxp_channel.h` +7. Use the functions provided by `nexus/include/nx_keycode.h`, `nexus/include/nx_channel.h`, and `nexus/include/nx_common.h` to interact with Nexus The functions declared in `include/nxp_common.h` provide the Nexus System with the ability to store and retrieve data from nonvolatile @@ -109,47 +38,79 @@ hardware (dependent on the implementing platform), and retrieve unique keying information used to validate Nexus Channel link communications. (This is a non-exhaustive list). -## DOCUMENTATION +Please add the following folders to your project include paths: -To regenerate the code documentation locally, execute: +* `nexus` +* `nexus/src` +* `nexus/include` +* `nexus/utils` +* `nexus/oc` (Required only for Nexus Channel or Nexus debug logs) -`doxygen ./Doxyfile` +Other folders are used for automated testing or support, and are not required +to build a project using Nexus. -from the repository root directory. The documentation will be placed in a -`docs` folder, open `html/index.html` to view it. +## Project Structure -## FAQ +The C implementation of Nexus uses the [ceedling](https://www.throwtheswitch.org/ceedling) +framework to organize automated testing of this source code. -**What is rate-limiting and the options available?** -Rate limiting is the ability to prevent and discourage users from trying a bruteforce attack to find an unlock code by randomly entering tokens. This functionality is implemented using a “rate limiting bucket” which tracks how many token attempts are allowed. Once this bucket is empty (0), rate-limiting is active and no keys will be accepted. +All source code is contained under the `nexus` folder. -(6) Initial number of tokens in rate limiting bucket - this is the # of tokens that a freshly-programmed device starts with. It is nonzero to allow keycodes to be entered immediately as part of factory testing. +Note that files named `nxp` contain functions that *your code* must implement, +and files named `nx` expose functions and structures that the Nexus system +and modules provide. -(128) Maximum number of tokens in rate limiting bucket. The maximum number of keycodes, defined as *(any number of digits)# that can be accumulated in the rate-limiting bucket. +**The only folders which must be copied to your own project when using the Nexus +Keycode protocol are `nexus/include`, `nexus/src`, and `nexus/utils`**. -(720) Seconds per each token attempt - this is the number of seconds required to add another keycode to the rate-limiting bucket, up to the maximum. If the device is rate-limited (0 keycodes in the rate-limiting bucket), the user will have to wait this number of seconds before they can enter another token. For example - if your device uses the default values, after ~1 day (720 seconds * 128 max tokens), the rate limiting bucket will have 128 tokens available. An attacker would only be able to enter 128 tokens in a brute force attack. After the 128 tokens are used, the attacker would have to wait 720 seconds before every new token entry. +The folders in this project are: -**What are the factory codes and options available?** +* `nexus/include` - Header files that must be included in a project using the +Nexus embedded solutions (do not modify) +* `nexus/src` - Nexus module implementation files (do not modify) +* `nexus/oc` - IoTivity-based files for Nexus Channel (do not modify) +* `nexus/utils` - Nexus support utilities and functions (do not modify) +* `nexus/stub` - Stub functions used during static analysis +* `nexus/build` - temporary output artifacts related to unit tests and static +* `nexus/test` - Unit tests for the code contained in `src` +* `nexus/examples` - Examples of the Nexus protocol in use +* `buildkite` - Scripts for continuous integration tests (on Buildkite) +* `support` - Scripts related to code formatting and analysis -(5) Number of times a device may accept '10 minute' universal code (NEW) +### Configuration Options + +To adjust configuration options (such as keycode protocol options), run +the configuration tool located at `nexus/conf_nexus.py`. + +The tool can be run on any platform using Python 3, as below: + +``` +python conf_nexus.py +``` + +You may also need to install the package `python3-tk`. -(5) Number of times a device may accept '1 hour' universal code (NEW) +This tool must be run from within the `nexus` directory to gain access to +the required `Kconfiglib` files. -## DEVELOPER TOOLS +This tool will launch an interactive configuration menu, where you may +modify the configuration of Nexus features to suit your application. +Afterwards, the tool automatically updates and saves your selections into +a header which is parsed by the Nexus code to determine what features to +expose to your application. -The C implementation of Nexus uses the [ceedling](https://www.throwtheswitch.org/ceedling) framework to organize automated testing of this source code. +## Static analysis -### Static analysis `ceedling release` will attempt to build a stub implementation of Nexus ( -contained in `nexus/stub`) with Channel and Keycode features enabled. This +contained in `nexus/stub`) with Channel and Keycode featured enabled. This build is used as a supplemental static analysis build (static analysis is also performed against unit test builds). -### Unit tests -The unit tests are found within the `nexus/test` folder. The +## Unit tests +The unit tests themselves are found within the `nexus/test` folder. The configuration of `ceedling` is contained within the `nexus/project.yml` file. -#### Installing Tools for Unit Tests +### Installing Tools for Unit Tests First, install [Conda](https://docs.conda.io/en/latest/), which is used to manage the packages for building and testing the `nexus-embedded` repository. @@ -194,3 +155,12 @@ following commands: * `ceedling clobber` - destroy all generated test files * `ceedling test:all` - compile and execute all unit tests * `ceedling gcov:all` - generate gcov test coverage reports + +## Documentation + +To regenerate the code documentation locally, execute: + +`doxygen ./Doxyfile` + +from the repository root directory. The documentation will be placed in a +`docs` folder, open `html/index.html` to view it. diff --git a/nexus/RELEASE_NOTES b/nexus/RELEASE_NOTES index 1228744..3eb47ff 100644 --- a/nexus/RELEASE_NOTES +++ b/nexus/RELEASE_NOTES @@ -19,6 +19,20 @@ Where any code from the same major version should be backwards-compatible with other code of the same major version, but is *not* compatible with different major version codes. +## Embedded Module Version 2.0.1 + +Compatible with NEXUS_GLOBAL_VERSION >= 3.0.0 + +This version includes bugfixes related to linking behavior and clarifies some +confusing docstrings. + +* Controllers and accessories, when completing a new link handshake to a device that they are already +linked to, will replace the existing link (remove and then add the link again) +* Controllers and accessories will replace their oldest existing accessory links (this device is +in the accessory role) when they reach their maximum number of links and a new link handshake +is completed. Existing controller links (this device is in the controller role) will not be replaced +* Remove mention of payments/loans from docstrings + ## Embedded Module Version 2.0.0 Compatible with NEXUS_GLOBAL_VERSION >= 3.0.0 diff --git a/nexus/examples/Nexus_Channel_Accessory_F103RB/.vscode/extensions.json b/nexus/examples/Nexus_Channel_Accessory_F103RB/.vscode/extensions.json deleted file mode 100644 index 0f0d740..0000000 --- a/nexus/examples/Nexus_Channel_Accessory_F103RB/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} diff --git a/nexus/examples/Nexus_Channel_Controller_F103RB/.vscode/extensions.json b/nexus/examples/Nexus_Channel_Controller_F103RB/.vscode/extensions.json deleted file mode 100644 index 0f0d740..0000000 --- a/nexus/examples/Nexus_Channel_Controller_F103RB/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} diff --git a/nexus/include/MODULE_VERSION.h b/nexus/include/MODULE_VERSION.h index e2a50b1..7ea854c 100644 --- a/nexus/include/MODULE_VERSION.h +++ b/nexus/include/MODULE_VERSION.h @@ -1,3 +1,3 @@ #define NEXUS_EMBEDDED_VERSION_MAJOR 2 #define NEXUS_EMBEDDED_VERSION_MINOR 0 -#define NEXUS_EMBEDDED_VERSION_PATCH 0 +#define NEXUS_EMBEDDED_VERSION_PATCH 1 diff --git a/nexus/include/nxp_common.h b/nexus/include/nxp_common.h index c93f282..dac3648 100644 --- a/nexus/include/nxp_common.h +++ b/nexus/include/nxp_common.h @@ -155,31 +155,31 @@ bool nxp_common_nv_read(const struct nx_common_nv_block_meta block_meta, * Certain Nexus features and modules may use the existing device * PAYG state to determine whether to perform an action or not. For example, * the Nexus Keycode module will first detect whether a unit is already - * 'unlocked', before attempting to apply an "ADD_CREDIT" keycode to + * unlocked before attempting to apply an ADD_CREDIT keycode to * that unit. */ enum nxp_common_payg_state { /** Unit functionality should be restricted. * - * The unit has not been paid off and its payment period has elapsed. - * Product functionality should be disabled or otherwise restricted. + * The unit's PAYG credit has expired. + * Product functionality should be disabled or otherwise restricted + * until more PAYG credit is added or the unit is unlocked. */ NXP_COMMON_PAYG_STATE_DISABLED, /** Unit functionality should be unrestricted. * - * The unit has not yet been fully paid off, so eventually it will - * return to NXP_COMMON_PAYG_STATE_DISABLED state. + * The unit will return to NXP_COMMON_PAYG_STATE_DISABLED state after + * the PAYG credit expires. */ NXP_COMMON_PAYG_STATE_ENABLED, /** Unit functionality should be unrestricted. * - * The unit has been fully paid off, so will never become - * NXP_COMMON_PAYG_STATE_DISABLED. Alternatively, this value may be - * returned for a device which does not implement any PAYG - * functionality (and is always enabled). + * The unit will not automatically disable, and will not become + * NXP_COMMON_PAYG_STATE_DISABLED unless it receives a keycode/command + * to go back into a PAYG ENABLED/DISABLED state. */ NXP_COMMON_PAYG_STATE_UNLOCKED }; diff --git a/nexus/src/nexus_channel_res_lm.c b/nexus/src/nexus_channel_res_lm.c index 21c5478..6c9e783 100644 --- a/nexus/src/nexus_channel_res_lm.c +++ b/nexus/src/nexus_channel_res_lm.c @@ -320,51 +320,29 @@ bool nexus_channel_link_manager_link_from_nxid( return found; } -bool nexus_channel_link_manager_create_link( - const struct nx_id* linked_device_id, - enum nexus_channel_link_operating_mode operating_mode, - enum nexus_channel_link_security_mode security_mode, - const union nexus_channel_link_security_data* security_data) +// Return false if there are no existing accessory links. +static bool +_nexus_channel_link_manager_oldest_accessory_link_idx(uint8_t* oldest_idx) { - if (_this.link_count >= NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS) - { - return false; - } - - struct nexus_channel_link_t matching_link; - // first, check if a link already exists with this NXID - if (nexus_channel_link_manager_link_from_nxid(linked_device_id, - &matching_link)) - { - OC_WRN("Cannot create new link, already exists"); - return false; - } - - // Not a true mutex, but we don't expect `create_link` to be called - // multiple times without processing (this would indicate an error). - if (_this.pending_add_link) + bool found = false; + uint32_t oldest_secs_since_init = 0; + for (uint8_t i = 0; i < NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS; i++) { - NEXUS_ASSERT_FAIL_IN_DEBUG_ONLY(0, "Already modifying list of links"); - return false; + if (_this.stored.links[i].operating_mode != + CHANNEL_LINK_OPERATING_MODE_ACCESSORY) + { + continue; + } + else if (_this.link_idx_in_use[i] && + _this.stored.links[i].seconds_since_init >= + oldest_secs_since_init) + { + oldest_secs_since_init = _this.stored.links[i].seconds_since_init; + *oldest_idx = i; + found = true; + } } - - PRINT("res_lm: Identified new link to persist\n"); - _this.pending_add_link = true; - - memset(&_this.pending_link_to_create, 0x00, sizeof(nexus_channel_link_t)); - _this.pending_link_to_create.operating_mode = (uint8_t) operating_mode; - _this.pending_link_to_create.security_mode = (uint8_t) security_mode; - memcpy(&_this.pending_link_to_create.linked_device_id, - linked_device_id, - sizeof(struct nx_id)); - memcpy(&_this.pending_link_to_create.security_data, - security_data, - sizeof(union nexus_channel_link_security_data)); - - nxp_common_request_processing(); - - // will try to add link on next `_process` call - return true; + return found; } // clear a single link @@ -412,6 +390,73 @@ void nexus_channel_link_manager_clear_all_links(void) nxp_common_request_processing(); } +bool nexus_channel_link_manager_create_link( + const struct nx_id* linked_device_id, + enum nexus_channel_link_operating_mode operating_mode, + enum nexus_channel_link_security_mode security_mode, + const union nexus_channel_link_security_data* security_data) +{ + // Not a true mutex, but we don't expect `create_link` to be called + // multiple times without processing (this would indicate an error). + if (_this.pending_add_link) + { + NEXUS_ASSERT_FAIL_IN_DEBUG_ONLY(0, "Already modifying list of links"); + return false; + } + + NEXUS_ASSERT(_this.link_count <= NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS, + "_this.link_count indicates that too many links exist!"); + + uint8_t idx_to_delete; + if (_nexus_channel_link_manager_link_index_from_nxid(linked_device_id, + &idx_to_delete)) + { + // replace existing links, as controllers and accessories can delete + // links to each other silently + PRINT("deleting existing link before re-linking"); + _nexus_channel_link_manager_clear_link_internal(idx_to_delete); + } + else if (_this.link_count == NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS) + { + if (operating_mode != CHANNEL_LINK_OPERATING_MODE_ACCESSORY) + { + // we do not overwrite any existing links if we're trying to + // establish a new link where this device is *not* in an accessory + // role. + return false; + } + // Otherwise, we delete the oldest link where this device is an + // accessory, and replace it with the newly created link. + if (!_nexus_channel_link_manager_oldest_accessory_link_idx( + &idx_to_delete)) + { + // No existing links where this device is an accessory. + return false; + } + // Delete the oldest link to a controller where this device is an + // accessory. + _nexus_channel_link_manager_clear_link_internal(idx_to_delete); + } + + PRINT("res_lm: Identified new link to persist\n"); + _this.pending_add_link = true; + + memset(&_this.pending_link_to_create, 0x00, sizeof(nexus_channel_link_t)); + _this.pending_link_to_create.operating_mode = (uint8_t) operating_mode; + _this.pending_link_to_create.security_mode = (uint8_t) security_mode; + memcpy(&_this.pending_link_to_create.linked_device_id, + linked_device_id, + sizeof(struct nx_id)); + memcpy(&_this.pending_link_to_create.security_data, + security_data, + sizeof(union nexus_channel_link_security_data)); + + nxp_common_request_processing(); + + // will try to add link on next `_process` call + return true; +} + enum nexus_channel_link_operating_mode nexus_channel_link_manager_operating_mode(void) { diff --git a/nexus/test/test_nexus_channel_res_link_hs.c b/nexus/test/test_nexus_channel_res_link_hs.c index 9e0f3ba..e135234 100644 --- a/nexus/test/test_nexus_channel_res_link_hs.c +++ b/nexus/test/test_nexus_channel_res_link_hs.c @@ -190,38 +190,6 @@ oc_endpoint_t FAKE_CONTROLLER_ENDPOINT = { 0, // ocf_version_t (unused) }; -oc_endpoint_t FAKE_CONTROLLER_ENDPOINT_B = { - NULL, // 'next' - 0, // device - IPV6, // flags - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // di - {(oc_ipv6_addr_t){ - 5683, // port - {// arbitrary link local address that represents a Nexus ID - 0xff, - 0x80, - 0, - 0, - 0, - 0, - 0, - 0, - 0xAE, - 0xD2, - 0x22, - 0xFF, - 0xFE, - 0xC1, - 0xA5, - 0xFC}, - 2 // scope - }}, - {{0}}, // addr_local (not used) - 0, // interface index (not used) - 0, // priority (not used) - 0, // ocf_version_t (unused) -}; - // Global message that can be allocated and deallocated at start and end // of tests regardless of failures static oc_message_t* G_OC_MESSAGE = 0; @@ -1534,34 +1502,23 @@ void test_res_link_hs_server_post_response__separate_commands_window_moved__both coap_serialize_message(&request_packet, G_OC_MESSAGE->data); nxp_channel_symmetric_origin_key_ExpectAndReturn(fake_origin_key); - // `request_processing` will be called to finalize the new link - memset(&response_packet, 0x00, sizeof(response_packet)); - handled = oc_ri_invoke_coap_entity_handler(&request_packet, - &response_packet, - (void*) &RESP_BUFFER, - &FAKE_CONTROLLER_ENDPOINT); - TEST_ASSERT_TRUE(handled); - - // Check response code and content -- fails because we have not changed - // the endpoint (cannot create two links to the same nexus ID) - TEST_ASSERT_EQUAL_UINT(BAD_REQUEST_4_00, response_packet.code); - TEST_ASSERT_EQUAL_UINT(0, response_packet.payload_len); - - // try again from a different endpoint - should succeed - nxp_channel_symmetric_origin_key_ExpectAndReturn(fake_origin_key); + // delete the existing link to this controller + nxp_channel_notify_event_Expect(NXP_CHANNEL_EVENT_LINK_DELETED); // `request_processing` will be called to finalize the new link nxp_common_request_processing_Expect(); - memset(&response_packet, 0x00, sizeof(response_packet)); - handled = oc_ri_invoke_coap_entity_handler(&request_packet, &response_packet, (void*) &RESP_BUFFER, - &FAKE_CONTROLLER_ENDPOINT_B); + &FAKE_CONTROLLER_ENDPOINT); TEST_ASSERT_TRUE(handled); - TEST_ASSERT_EQUAL_UINT(CREATED_2_01, response_packet.code); TEST_ASSERT_EQUAL_UINT(14, response_packet.payload_len); + + // create the new link + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_ACCESSORY); + nexus_channel_core_process(1); } void test_res_link_hs_challenge_mode_3_key_derivation__result_expected(void) @@ -2199,7 +2156,6 @@ void test_res_link_hs_server_post_finalize_state__move_window_right__preserves_i matched_handshake_index, &window, &derived_link_key); } } -/* void test_res_link_hs_client_post_cb__null_data__returns_early(void) { @@ -2212,15 +2168,3 @@ void test_res_link_hs_client_post_cb__null_data__returns_early(void) dummy_post_resp.user_data = (void*) 0x0; nexus_channel_res_link_hs_client_post(&dummy_post_resp); } - -void test_res_link_hs_client_get_cb__dummy__todo(void) -{ - oc_client_response_t dummy_get_resp; - dummy_get_resp.code = OC_STATUS_OK; - nexus_channel_res_link_hs_client_get(&dummy_get_resp); - dummy_get_resp.code = OC_STATUS_FORBIDDEN; - nexus_channel_res_link_hs_client_get(&dummy_get_resp); - - TEST_ASSERT_TRUE(1); -} -*/ diff --git a/nexus/test/test_nexus_channel_res_lm.c b/nexus/test/test_nexus_channel_res_lm.c index 6ea2371..a3b9fc0 100644 --- a/nexus/test/test_nexus_channel_res_lm.c +++ b/nexus/test/test_nexus_channel_res_lm.c @@ -587,7 +587,7 @@ void test_link_manager_next_linked_accessory__rolls_around_list_of_ids_finds_nex TEST_ASSERT_EQUAL_MEMORY(&next_id, &second_nxid, sizeof(struct nx_id)); } -void test_link_manager_create_identical_link__create_link_fails(void) +void test_link_manager_create_identical_link__create_link_success(void) { // initializes with no links present struct nx_id linked_id = {0}; @@ -617,17 +617,89 @@ void test_link_manager_create_identical_link__create_link_fails(void) NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_CONTROLLER); nexus_channel_link_manager_process(0); - // creating a link to a device which is already linked will fail. + // creating a link to a device which is already linked will succeed. + nxp_channel_notify_event_Expect(NXP_CHANNEL_EVENT_LINK_DELETED); + nxp_common_request_processing_Expect(); result = nexus_channel_link_manager_create_link( &linked_id, CHANNEL_LINK_OPERATING_MODE_CONTROLLER, NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, &sec_data); // first link is created OK + TEST_ASSERT_TRUE(result); + // actually create the link + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_CONTROLLER); + nexus_channel_link_manager_process(0); +} + +void test_link_manager_create_controller_link__exceeded_link_limit__create_controller_link__returns_false( + void) +{ + // initializes with no links present + struct nx_id linked_id = {0}; + linked_id.authority_id = 5921; + linked_id.device_id = 123456; + + struct nx_common_check_key link_key; + memset(&link_key, 0xFA, sizeof(link_key)); // arbitrary + + union nexus_channel_link_security_data sec_data; + memset(&sec_data, 0xBB, sizeof(sec_data)); // arbitrary + + sec_data.mode0.nonce = 5; + memcpy( + &sec_data.mode0.sym_key, &link_key, sizeof(struct nx_common_check_key)); + + nexus_channel_link_t result_link = {0}; + + // first, no links exist + bool result = + nexus_channel_link_manager_link_from_nxid(&linked_id, &result_link); + TEST_ASSERT_FALSE(result); + + for (uint8_t i = 0; i < NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS; i++) + { + nxp_common_request_processing_Expect(); + // increment device ID each time so we don't attempt to create an + // identical link + linked_id.device_id++; + result = nexus_channel_link_manager_create_link( + &linked_id, + CHANNEL_LINK_OPERATING_MODE_CONTROLLER, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); + TEST_ASSERT_TRUE(result); + + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_CONTROLLER); + nexus_channel_link_manager_process(0); + } + + // cannot create more links than MAX_SIMULTANEOUS + // does not expect to call `nxp_common_request_processing_Expect` + linked_id.device_id++; + result = nexus_channel_link_manager_create_link( + &linked_id, + CHANNEL_LINK_OPERATING_MODE_CONTROLLER, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); + TEST_ASSERT_FALSE(result); + + // Creating an accessory link will also fail, as new accessory + // links can only replace the oldest existing accessory link, never + // controller links + linked_id.device_id++; + result = nexus_channel_link_manager_create_link( + &linked_id, + CHANNEL_LINK_OPERATING_MODE_ACCESSORY, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); TEST_ASSERT_FALSE(result); } -void test_link_manager_create_link__exceeded_link_limit__return_false(void) +void test_link_manager_create_accessory_link__exceeded_link_limit__create_accessory_link__only_controller_links_present__returns_false( + void) { // initializes with no links present struct nx_id linked_id = {0}; @@ -669,14 +741,103 @@ void test_link_manager_create_link__exceeded_link_limit__return_false(void) TEST_ASSERT_TRUE(result); } - // cannot create more links than MAX_SIMULTANEOUS // does not expect to call `nxp_common_request_processing_Expect` + linked_id.device_id++; + result = nexus_channel_link_manager_create_link( + &linked_id, + CHANNEL_LINK_OPERATING_MODE_ACCESSORY, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); + TEST_ASSERT_FALSE(result); +} + +void test_link_manager_create_link__exceeded_link_limit__create_accessory_link__replaces_accessory_link( + void) +{ + // initializes with no links present + struct nx_id linked_id = {0}; + linked_id.authority_id = 5921; + linked_id.device_id = 123456; + + struct nx_common_check_key link_key; + memset(&link_key, 0xFA, sizeof(link_key)); // arbitrary + + union nexus_channel_link_security_data sec_data; + memset(&sec_data, 0xBB, sizeof(sec_data)); // arbitrary + + sec_data.mode0.nonce = 5; + memcpy( + &sec_data.mode0.sym_key, &link_key, sizeof(struct nx_common_check_key)); + + nexus_channel_link_t result_link = {0}; + + // first, no links exist + bool result = + nexus_channel_link_manager_link_from_nxid(&linked_id, &result_link); + TEST_ASSERT_FALSE(result); + + enum nexus_channel_link_operating_mode link_op_mode; + for (uint8_t i = 0; i < NEXUS_CHANNEL_MAX_SIMULTANEOUS_LINKS; i++) + { + nxp_common_request_processing_Expect(); + // increment device ID each time so we don't attempt to create an + // identical link + linked_id.device_id++; + + // create both controller and accessory links + link_op_mode = CHANNEL_LINK_OPERATING_MODE_CONTROLLER; + if (i % 2 == 0) + { + link_op_mode = CHANNEL_LINK_OPERATING_MODE_ACCESSORY; + } + + result = nexus_channel_link_manager_create_link( + &linked_id, + link_op_mode, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); + + if (link_op_mode == CHANNEL_LINK_OPERATING_MODE_ACCESSORY) + { + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_ACCESSORY); + } + else + { + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_CONTROLLER); + } + + nexus_channel_link_manager_process(0); + TEST_ASSERT_TRUE(result); + } + + // the only 'replacement' occurs if we are trying to create a new accessory + // link as an accessory. New Controller links do not replace existing + // controller links, they must be explicitly cleared. + linked_id.device_id++; result = nexus_channel_link_manager_create_link( &linked_id, CHANNEL_LINK_OPERATING_MODE_CONTROLLER, NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, &sec_data); + nexus_channel_link_manager_process(0); TEST_ASSERT_FALSE(result); + + // However, *accessory* links will be overwritten + linked_id.device_id++; + nxp_channel_notify_event_Expect(NXP_CHANNEL_EVENT_LINK_DELETED); + nxp_common_request_processing_Expect(); + result = nexus_channel_link_manager_create_link( + &linked_id, + CHANNEL_LINK_OPERATING_MODE_ACCESSORY, + NEXUS_CHANNEL_LINK_SECURITY_MODE_KEY128SYM_COSE_MAC0_AUTH_SIPHASH24, + &sec_data); + + nxp_channel_notify_event_Expect( + NXP_CHANNEL_EVENT_LINK_ESTABLISHED_AS_ACCESSORY); + nexus_channel_link_manager_process(0); + TEST_ASSERT_TRUE(result); } void test_link_manager__security_data_from_nxid__no_data_present__returns_false(