From b190c57a143d221f4b2f48e7818ca01ca4286813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Bj=C3=B8rlykke?= Date: Mon, 12 Aug 2024 14:34:20 +0200 Subject: [PATCH] lib: uicc_lwm2m: Add UICC LwM2M library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functionality to read LwM2M bootstrap data from UICC. Signed-off-by: Stig Bjørlykke --- .checkpatch.conf | 1 + CODEOWNERS | 3 + doc/nrf/libraries/modem/uicc_lwm2m.rst | 28 ++ .../releases/release-notes-changelog.rst | 6 + include/modem/uicc_lwm2m.h | 51 ++++ lib/CMakeLists.txt | 1 + lib/Kconfig | 1 + lib/uicc_lwm2m/CMakeLists.txt | 17 ++ lib/uicc_lwm2m/Kconfig | 11 + lib/uicc_lwm2m/asn1_decode.c | 85 ++++++ lib/uicc_lwm2m/asn1_decode.h | 89 +++++++ lib/uicc_lwm2m/pkcs15_decode.c | 245 ++++++++++++++++++ lib/uicc_lwm2m/pkcs15_decode.h | 59 +++++ lib/uicc_lwm2m/uicc_lwm2m.c | 153 +++++++++++ samples/cellular/uicc_lwm2m/CMakeLists.txt | 16 ++ samples/cellular/uicc_lwm2m/README.rst | 87 +++++++ samples/cellular/uicc_lwm2m/prj.conf | 18 ++ samples/cellular/uicc_lwm2m/sample.yaml | 18 ++ samples/cellular/uicc_lwm2m/src/main.c | 98 +++++++ scripts/ci/tags.yaml | 5 + tests/lib/uicc_lwm2m/CMakeLists.txt | 25 ++ tests/lib/uicc_lwm2m/prj.conf | 7 + tests/lib/uicc_lwm2m/src/main.c | 171 ++++++++++++ tests/lib/uicc_lwm2m/testcase.yaml | 7 + 24 files changed, 1202 insertions(+) create mode 100644 doc/nrf/libraries/modem/uicc_lwm2m.rst create mode 100644 include/modem/uicc_lwm2m.h create mode 100644 lib/uicc_lwm2m/CMakeLists.txt create mode 100644 lib/uicc_lwm2m/Kconfig create mode 100644 lib/uicc_lwm2m/asn1_decode.c create mode 100644 lib/uicc_lwm2m/asn1_decode.h create mode 100644 lib/uicc_lwm2m/pkcs15_decode.c create mode 100644 lib/uicc_lwm2m/pkcs15_decode.h create mode 100644 lib/uicc_lwm2m/uicc_lwm2m.c create mode 100644 samples/cellular/uicc_lwm2m/CMakeLists.txt create mode 100644 samples/cellular/uicc_lwm2m/README.rst create mode 100644 samples/cellular/uicc_lwm2m/prj.conf create mode 100644 samples/cellular/uicc_lwm2m/sample.yaml create mode 100644 samples/cellular/uicc_lwm2m/src/main.c create mode 100644 tests/lib/uicc_lwm2m/CMakeLists.txt create mode 100644 tests/lib/uicc_lwm2m/prj.conf create mode 100644 tests/lib/uicc_lwm2m/src/main.c create mode 100644 tests/lib/uicc_lwm2m/testcase.yaml diff --git a/.checkpatch.conf b/.checkpatch.conf index 971cf6ba56db..3248690b08e8 100644 --- a/.checkpatch.conf +++ b/.checkpatch.conf @@ -51,3 +51,4 @@ --exclude applications/nrf5340_audio/src/utils/macros --exclude lib/at_parser/generated --exclude lib/bin/lwm2m_carrier/include +--exclude tests/lib/uicc_lwm2m diff --git a/CODEOWNERS b/CODEOWNERS index f8ab5a718aa8..5d5e81dee348 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -175,6 +175,7 @@ /lib/st25r3911b/ @nrfconnect/ncs-si-muffin /lib/supl/ @nrfconnect/ncs-co-networking @tokangas /lib/tone/ @nrfconnect/ncs-audio +/lib/uicc_lwm2m/ @stig-bjorlykke /lib/wave_gen/ @nrfconnect/ncs-si-muffin # Modules @@ -213,6 +214,7 @@ /samples/cellular/modem_trace_flash/ @nrfconnect/ncs-modem /samples/cellular/slm_shell/ @nrfconnect/ncs-iot-oulu /samples/cellular/sms/ @trantanen @tokangas +/samples/cellular/uicc_lwm2m/ @stig-bjorlykke /samples/common/ @nrfconnect/ncs-si-muffin @nrfconnect/ncs-si-bluebagel /samples/crypto/ @nrfconnect/ncs-aegir /samples/debug/memfault/ @nrfconnect/ncs-cia @@ -416,6 +418,7 @@ /tests/lib/sfloat/ @nrfconnect/ncs-si-muffin /tests/lib/sms/ @trantanen @tokangas /tests/lib/tone/ @nrfconnect/ncs-audio +/tests/lib/uicc_lwm2m/ @stig-bjorlykke /tests/mocks/nrf_rpc/ @nrfconnect/ncs-protocols-serialization /tests/modules/lib/zcbor/ @oyvindronningstad /tests/modules/mcuboot/direct_xip/ @nrfconnect/ncs-pluto diff --git a/doc/nrf/libraries/modem/uicc_lwm2m.rst b/doc/nrf/libraries/modem/uicc_lwm2m.rst new file mode 100644 index 000000000000..0056c4f7ae4a --- /dev/null +++ b/doc/nrf/libraries/modem/uicc_lwm2m.rst @@ -0,0 +1,28 @@ +.. _lib_uicc_lwm2m: + +UICC LwM2M +########## + +.. contents:: + :local: + :depth: 2 + +The UICC LwM2M library provides functionality to read LwM2M bootstrap configuration from SIM. + +Configuration +************* + +To enable the UICC LwM2M library, configure the :kconfig:option:`CONFIG_UICC_LWM2M` Kconfig option. + +Dependencies +************ + +The UICC LwM2M library requires the :ref:`nrfxlib:nrf_modem` library to use AT commands. + +API documentation +***************** + +| Header file: :file:`include/modem/uicc_lwm2m.h` +| Source file: :file:`lib/uicc_lwm2m/uicc_lwm2m.c` + +.. doxygengroup:: uicc_lwm2m diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 2b3cc19bbb41..7cfb663c2aae 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -484,6 +484,10 @@ Cellular samples * Added sysbuild configuration files. +* :ref:`uicc_lwm2m_sample` sample: + + * Added the :ref:`uicc_lwm2m_sample` sample. + Cryptography samples -------------------- @@ -749,6 +753,8 @@ Modem libraries The :ref:`at_parser_readme` is a library that parses AT command responses, notifications, and events. Compared to the deprecated :ref:`at_cmd_parser_readme` library, it does not allocate memory dynamically and has a smaller footprint. For more information on how to transition from the :ref:`at_cmd_parser_readme` library to the :ref:`at_parser_readme` library, see the :ref:`migration guide `. + * The :ref:`lib_uicc_lwm2m` library. + The :ref:`lib_uicc_lwm2m` is a library that reads LwM2M bootstrap configuration from SIM. * :ref:`at_cmd_parser_readme` library: diff --git a/include/modem/uicc_lwm2m.h b/include/modem/uicc_lwm2m.h new file mode 100644 index 000000000000..0cbe39bfd5f1 --- /dev/null +++ b/include/modem/uicc_lwm2m.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef UICC_LWM2M_H_ +#define UICC_LWM2M_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file uicc_lwm2m.h + * + * @defgroup uicc_lwm2m UICC LwM2M + * + * @{ + * + * @brief Public APIs of the UICC LwM2M library. + */ + +/** UICC record max size is 256 bytes. The buffer size needed for the AT response is + * (256 * 2) + 4 bytes for SW + 1 byte for NUL. Using 516 bytes is adequate to read + * a full UICC record. + */ +#define UICC_RECORD_BUFFER_MAX ((256 * 2) + 4 + 1) + +/** + * @brief Read UICC LwM2M bootstrap record. + * + * @param[inout] buffer Buffer to store UICC LwM2M bootstrap record. This buffer is also + * used internally by the function reading the AT response, so it must + * be twice the size of expected LwM2M content + 4 bytes for UICC SW. + * @param[in] buffer_size Total size of buffer. + * + * @return Length of UICC LwM2M bootstrap record, -errno on error. + */ +int uicc_lwm2m_bootstrap_read(uint8_t *buffer, int buffer_size); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* UICC_LWM2M_H_ */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 203f79c4369e..b74915671bec 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -50,3 +50,4 @@ add_subdirectory_ifdef(CONFIG_DATA_FIFO data_fifo) add_subdirectory_ifdef(CONFIG_FEM_AL_LIB fem_al) add_subdirectory_ifdef(CONFIG_SAMPLE_RATE_CONVERTER sample_rate_converter) add_subdirectory_ifdef(CONFIG_NCS_BOOT_BANNER boot_banner) +add_subdirectory_ifdef(CONFIG_UICC_LWM2M uicc_lwm2m) diff --git a/lib/Kconfig b/lib/Kconfig index b55ce44a052c..bb2f65d2f077 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -51,5 +51,6 @@ rsource "data_fifo/Kconfig" rsource "fem_al/Kconfig" rsource "sample_rate_converter/Kconfig" rsource "boot_banner/Kconfig" +rsource "uicc_lwm2m/Kconfig" endmenu diff --git a/lib/uicc_lwm2m/CMakeLists.txt b/lib/uicc_lwm2m/CMakeLists.txt new file mode 100644 index 000000000000..f21707656ca6 --- /dev/null +++ b/lib/uicc_lwm2m/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() + +zephyr_include_directories( + ${ZEPHYR_NRFXLIB_MODULE_DIR}/nrf_modem/include/ +) + +zephyr_library_sources( + asn1_decode.c + pkcs15_decode.c + uicc_lwm2m.c +) diff --git a/lib/uicc_lwm2m/Kconfig b/lib/uicc_lwm2m/Kconfig new file mode 100644 index 000000000000..cbacbc4b689d --- /dev/null +++ b/lib/uicc_lwm2m/Kconfig @@ -0,0 +1,11 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config UICC_LWM2M + bool "UICC LwM2M bootstrap support" + depends on NRF_MODEM_LIB + help + Enable UICC LwM2M bootstrap library diff --git a/lib/uicc_lwm2m/asn1_decode.c b/lib/uicc_lwm2m/asn1_decode.c new file mode 100644 index 000000000000..4ced0369a705 --- /dev/null +++ b/lib/uicc_lwm2m/asn1_decode.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include "asn1_decode.h" + +#define ASN1_MAX_LENGTH_BYTES 3 + +bool asn1_dec_head(asn1_ctx_t *ctx, uint8_t *tag, size_t *len) +{ + uint32_t hlen = 2; /* Minimum two bytes for header */ + + if (ctx->error || ((ctx->offset + hlen) > ctx->length)) { + /* Error detected or out of data (happens at end of sequence) */ + return false; + } + + *tag = ctx->asnbuf[ctx->offset++]; + *len = ctx->asnbuf[ctx->offset++]; + + if ((*tag & 0x1F) == 0x1F) { + /* Extended tag number is unsupported */ + ctx->error = true; + return false; + } + + if (*len & 0x80) { + int n = *len & 0x7F; + + hlen += n; + if (n > ASN1_MAX_LENGTH_BYTES || (ctx->offset + hlen) > ctx->length) { + /* Unsupported header length or out of data (header is past buffer) */ + ctx->error = true; + return false; + } + + *len = 0; + for (int i = 0; i < n; i++) { + *len = (*len << 8) + ctx->asnbuf[ctx->offset++]; + } + } + + if ((ctx->offset + *len) > ctx->length) { + /* Out of data (value is past buffer) */ + ctx->error = true; + return false; + } + + return true; +} + +void asn1_dec_octet_string(asn1_ctx_t *ctx, size_t len, uint8_t *value, size_t max_len) +{ + if (bin2hex(&ctx->asnbuf[ctx->offset], len, value, max_len) == 0) { + /* OCTET STRING too long for buffer */ + ctx->error = true; + return; + } + + ctx->offset += len; +} + +void asn1_dec_sequence(asn1_ctx_t *ctx, size_t len, void *data, asn1_sequence_func_t sequence_func) +{ + /* Create a subset from the buffer */ + asn1_ctx_t seq_ctx = { + .asnbuf = &ctx->asnbuf[ctx->offset], + .length = len + }; + + sequence_func(&seq_ctx, data); + ctx->offset += len; + + /* Copy error from subset */ + ctx->error = seq_ctx.error; +} + +void asn1_dec_skip(asn1_ctx_t *ctx, size_t len) +{ + ctx->offset += len; +} diff --git a/lib/uicc_lwm2m/asn1_decode.h b/lib/uicc_lwm2m/asn1_decode.h new file mode 100644 index 000000000000..605678a0e13e --- /dev/null +++ b/lib/uicc_lwm2m/asn1_decode.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* A minimalistic ASN.1 BER/DER decoder (X.690). + * + * Supported types: + * OCTET STRING + * SEQUENCE / SEQUENCE OF + */ + +#ifndef ASN1_DECODE_H_ +#define ASN1_DECODE_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Commonly used ASN.1 tags. */ +#define UP4 0x04 +#define UP6 0x06 +#define UC16 0x30 +#define AP15 0x4F +#define AP16 0x50 +#define CC1 0xA1 +#define CC7 0xA7 + +/** ASN.1 context values */ +typedef struct { + const uint8_t *asnbuf; /**< ASN.1 BER/DER encoded buffer */ + size_t length; /**< Length of the data buffer */ + uint32_t offset; /**< Current offset into asnbuf */ + bool error; /**< Error detected in ASN.1 syntax */ +} asn1_ctx_t; + +/** Function called to handle elements in a SEQUENCE */ +typedef void (*asn1_sequence_func_t)(asn1_ctx_t *ctx, void *data); + +/** + * @brief Decode ASN.1 header. + * + * @param[in] ctx ASN.1 context values. + * @param[out] tag ASN.1 TAG. + * @param[out] len Length of ASN.1 value. + * + * @return true when valid ASN.1 and a value exists + */ +bool asn1_dec_head(asn1_ctx_t *ctx, uint8_t *tag, size_t *len); + +/** + * @brief Decode ASN.1 OCTET STRING. + * + * @param[in] ctx ASN.1 context values. + * @param[in] len Length of octet string to decode. + * @param[out] value Decoded octet string. + * @param[in] max_len Maximum length of octet string to decode. + */ +void asn1_dec_octet_string(asn1_ctx_t *ctx, size_t len, uint8_t *value, size_t max_len); + +/** + * @brief Decode ASN.1 SEQUENCE. + * + * @param[in] ctx ASN.1 context values. + * @param[in] len Length of sequence to decode. + * @param[inout] data Pointer to application specific values. + * @param[in] sequence_func Function to be called to handle sequence elements. + */ +void asn1_dec_sequence(asn1_ctx_t *ctx, size_t len, void *data, asn1_sequence_func_t sequence_func); + +/** + * @brief Skip a subset of ASN.1 content. + * This is used to skip parts of the content which is not of interest. + * + * @param[in] ctx ASN.1 context values. + * @param[in] len Length of data to skip. + */ +void asn1_dec_skip(asn1_ctx_t *ctx, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* ASN1_DECODE_H_ */ diff --git a/lib/uicc_lwm2m/pkcs15_decode.c b/lib/uicc_lwm2m/pkcs15_decode.c new file mode 100644 index 000000000000..31e4e637493f --- /dev/null +++ b/lib/uicc_lwm2m/pkcs15_decode.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include "asn1_decode.h" +#include "pkcs15_decode.h" + +/* ASN.1 Object Identifier 2.23.43.9.1 in encoded form. */ +#define ASN1_LWM2M_BOOTSTRAP_OID "672b0901" +#define ASN1_OMA_TS_LWM2M_BOOTSTRAP_OID "0604672b0901" + +static void asn1_dec_Path(asn1_ctx_t *ctx, void *data) +{ + pkcs15_object_t *object = (pkcs15_object_t *)data; + uint8_t tag; + size_t len; + + /* Here only path is handled. + * + * PKCS15Path ::= SEQUENCE { + * path OCTET STRING, + * index INTEGER (0..pkcs15-ub-index) OPTIONAL, + * length [0] INTEGER (0..pkcs15-ub-index) OPTIONAL + * } (WITH COMPONENTS {..., index PRESENT, length PRESENT} | + * WITH COMPONENTS {..., index ABSENT, length ABSENT}) + */ + + while (asn1_dec_head(ctx, &tag, &len)) { + switch (tag) { + case UP4: + asn1_dec_octet_string(ctx, len, object->path, sizeof(object->path)); + break; + default: + asn1_dec_skip(ctx, len); + break; + } + } +} + +static void asn1_dec_PathOrObjects_Data(asn1_ctx_t *ctx, void *data) +{ + pkcs15_object_t *object = (pkcs15_object_t *)data; + uint8_t tag; + size_t len; + + /* Here only the PKCS15Path choice is handled. + * + * PKCS15PathOrObjects {ObjectType} ::= CHOICE { + * path PKCS15Path, + * objects [0] SEQUENCE OF ObjectType, + * ..., + * indirect-protected [1] ReferencedValue {EnvelopedData {SEQUENCE OF ObjectType}}, + * direct-protected [2] EnvelopedData {SEQUENCE OF ObjectType}, + * } + */ + + while (asn1_dec_head(ctx, &tag, &len)) { + switch (tag) { + case UC16: /* PKCS15Path */ + asn1_dec_sequence(ctx, len, &object->path, asn1_dec_Path); + break; + default: + asn1_dec_skip(ctx, len); + break; + } + } +} + +static void asn1_dec_OidDO(asn1_ctx_t *ctx, void *data) +{ + pkcs15_object_t *object = (pkcs15_object_t *)data; + uint8_t tag; + size_t len; + + /* Here only decode value when OID match 2.23.43.9.1 "lwm2m_bootstrap". + * + * Because of a bug in the OMA-TS-LwM2M_Core spec example this + * variant must also be checked. + * + * PKCS15OidDO ::= SEQUENCE { + * id OBJECT IDENTIFIER, + * value ObjectValue {PKCS15-OPAQUE.&Type} + * } + */ + + uint8_t oid[32]; + bool decode_path = false; + + while (asn1_dec_head(ctx, &tag, &len)) { + switch (tag) { + case UP6: /* id */ + /* Using octet string decoder to simplify code using string matching */ + asn1_dec_octet_string(ctx, len, oid, sizeof(oid)); + + /* Check for Object Identifier 2.23.43.9.1 or the OMA-TS variant */ + if ((strcmp(oid, ASN1_LWM2M_BOOTSTRAP_OID) == 0) || + (strcmp(oid, ASN1_OMA_TS_LWM2M_BOOTSTRAP_OID) == 0)) { + decode_path = true; + } else { + decode_path = false; + } + break; + case UC16: /* value (Path) */ + if (decode_path) { + asn1_dec_sequence(ctx, len, &object->path, asn1_dec_Path); + } else { + asn1_dec_skip(ctx, len); + } + break; + default: + asn1_dec_skip(ctx, len); + break; + } + } +} + +static void asn1_dec_TypeAttributes_OidDO(asn1_ctx_t *ctx, void *data) +{ + uint8_t tag; + size_t len; + + while (asn1_dec_head(ctx, &tag, &len)) { + switch (tag) { + case UC16: /* PKCS15OidDO */ + asn1_dec_sequence(ctx, len, data, asn1_dec_OidDO); + break; + default: + asn1_dec_skip(ctx, len); + break; + } + } +} + +static void asn1_dec_DataObject_OidDO(asn1_ctx_t *ctx, void *data) +{ + uint8_t tag; + size_t len; + + /* Here only TypeAttributes{OidDO} is handled. + * + * PKCS15Object {ClassAttributes, SubClassAttributes, TypeAttributes} ::= SEQUENCE { + * commonObjectAttributes PKCS15CommonObjectAttributes, + * classAttributes ClassAttributes + * subClassAttributes [0] SubClassAttributes OPTIONAL, + * typeAttributes [1] TypeAttributes + * } + * + * PKCS15DataObject {DataObjectAttributes} ::= PKCS15Object { + * PKCS15CommonDataObjectAttributes, NULL, DataObjectAttributes + * } + */ + + while (asn1_dec_head(ctx, &tag, &len)) { + switch (tag) { + case CC1: /* TypeAttributes {OidDO} */ + asn1_dec_sequence(ctx, len, data, asn1_dec_TypeAttributes_OidDO); + break; + default: + asn1_dec_skip(ctx, len); + break; + } + } +} + +bool pkcs15_ef_odf_path_decode(const uint8_t *bytes, size_t bytes_len, pkcs15_object_t *object) +{ + asn1_ctx_t asn1_ctx = { + .asnbuf = bytes, + .length = bytes_len + }; + uint8_t tag; + size_t len; + + /* The EF(ODF) (Elementary File - Object Directory File) may contain pointers + * to multiple Directory Files. Here only the PKCS15DataObjects choice is handled. + * + * PKCS15Objects ::= CHOICE { + * privateKeys [0] PKCS15PrivateKeys, + * publicKeys [1] PKCS15PublicKeys, + * trustedPublicKeys [2] PKCS15PublicKeys, + * secretKeys [3] PKCS15SecretKeys, + * certificates [4] PKCS15Certificates, + * trustedCertificates [5] PKCS15Certificates, + * usefulCertificates [6] PKCS15Certificates, + * dataObjects [7] PKCS15DataObjects, + * authObjects [8] PKCS15AuthObjects, + * ... -- For future extensions + * } + * + * PKCS15DataObjects ::= PKCS15PathOrObjects {PKCS15Data} + */ + + while (asn1_dec_head(&asn1_ctx, &tag, &len)) { + switch (tag) { + case CC7: /* PKCS15DataObjects (PKCS15PathOrObjects {PKCS15Data}) */ + asn1_dec_sequence(&asn1_ctx, len, object, asn1_dec_PathOrObjects_Data); + break; + default: /* Other PKCS15Objects choices */ + asn1_dec_skip(&asn1_ctx, len); + break; + } + } + + /* The UICC record may be padded with 0xFF, this is no error */ + return !(asn1_ctx.error && tag != 0xFF && len != 0xFF); +} + +bool pkcs15_ef_dodf_path_decode(const uint8_t *bytes, size_t bytes_len, pkcs15_object_t *object) +{ + asn1_ctx_t asn1_ctx = { + .asnbuf = bytes, + .length = bytes_len + }; + uint8_t tag; + size_t len; + + /* The EF(DODF) (Elementary File - Data Object Directory File) may contain + * multiple Data Objects. Here only the PKCS15OidDO choice is handled. + * + * PKCS15Data ::= CHOICE { + * opaqueDO PKCS15DataObject {PKCS15Opaque}, + * externalIDO [0] PKCS15DataObject {PKCS15ExternalIDO}, + * oidDO [1] PKCS15DataObject {PKCS15OidDO}, + * ... -- For future extensions + * } + */ + + while (asn1_dec_head(&asn1_ctx, &tag, &len)) { + switch (tag) { + case CC1: /* PKCS15DataObject {PKCS15OidDO} */ + asn1_dec_sequence(&asn1_ctx, len, object, asn1_dec_DataObject_OidDO); + break; + default: /* Other PKCS15Data choices */ + asn1_dec_skip(&asn1_ctx, len); + break; + } + } + + /* The UICC record may be padded with 0xFF, this is no error */ + return !(asn1_ctx.error && tag != 0xFF && len != 0xFF); +} diff --git a/lib/uicc_lwm2m/pkcs15_decode.h b/lib/uicc_lwm2m/pkcs15_decode.h new file mode 100644 index 000000000000..cdec5f996f92 --- /dev/null +++ b/lib/uicc_lwm2m/pkcs15_decode.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* A minimalistic PKCS #15 Elementary File (EF) decoder. + * + * Supported objects: + * EF(ODF) (Object Directory File) - Path element + * EF(DODF) (Data Object Directory File) - Path element + */ + +#ifndef PKCS15_DECODE_H_ +#define PKCS15_DECODE_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Maximum Path size supported in this decoder. */ +#define PATH_SIZE 16 + +/** PKCS#15 object with values of interest. */ +typedef struct { + uint8_t path[PATH_SIZE]; +} pkcs15_object_t; + +/** + * @brief Decode PKCS #15 EF(ODF) Path. + * + * @param[in] bytes Array of bytes containing ASN.1. + * @param[in] len Length of bytes array. + * @param[out] object PKCS #15 object with decoded values. + * + * @return true on success. + */ +bool pkcs15_ef_odf_path_decode(const uint8_t *bytes, size_t len, pkcs15_object_t *object); + +/** + * @brief Decode PKCS #15 EF(DODF) Path. + * + * @param[in] bytes Array of bytes containing ASN.1. + * @param[in] len Length of bytes array. + * @param[out] object PKCS #15 object with decoded values. + * + * @return true on success. + */ +bool pkcs15_ef_dodf_path_decode(const uint8_t *bytes, size_t len, pkcs15_object_t *object); + +#ifdef __cplusplus +} +#endif + +#endif /* PKCS15_DECODE_H_ */ diff --git a/lib/uicc_lwm2m/uicc_lwm2m.c b/lib/uicc_lwm2m/uicc_lwm2m.c new file mode 100644 index 000000000000..3f61a1746ff4 --- /dev/null +++ b/lib/uicc_lwm2m/uicc_lwm2m.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "pkcs15_decode.h" + +static int csim_send(const uint8_t *csim_command, uint8_t *response, int buffer_size) +{ + uint8_t csim_fmt[20]; + int length, rc; + + /* Create csim_fmt based on buffer size */ + rc = snprintf(csim_fmt, sizeof(csim_fmt), "+CSIM: %%d,\"%%%ds\"", buffer_size - 1); + if (rc >= (int)sizeof(csim_fmt)) { + return -EINVAL; + } + + /* Use response buffer for both command and response */ + rc = snprintf(response, buffer_size, "AT+CSIM=%u,\"%s\"", + (unsigned int)strlen(csim_command), csim_command); + if (rc >= buffer_size) { + return -EINVAL; + } + + /* Send AT command */ + rc = nrf_modem_at_scanf(response, csim_fmt, &length, response); + if (rc < 0) { + return rc; + } + + /* Check for success and trailing status word 9000 in response */ + if ((rc != 2) || (length < 4) || (memcmp(&response[length - 4], "9000", 4) != 0)) { + return -EINVAL; + } + + /* Remove status word from response */ + return length - 4; +} + +static int csim_read_file(const uint8_t *path, uint8_t *response, int buffer_size) +{ + uint8_t csim_select[] = "01A40804047FFF****00"; + uint8_t csim_read[] = "01B0000000"; + int length; + + /* Select path */ + memcpy(&csim_select[14], path, 4); + length = csim_send(csim_select, response, buffer_size); + if (length <= 0) { + return length; + } + + /* Check buffer size, needs to be max*2 + 4 bytes for SW for AT response */ + if (buffer_size < UICC_RECORD_BUFFER_MAX) { + /* Expected maximum response length: 1-255, 0=256 */ + snprintf(&csim_read[8], 3, "%2.2X", (uint8_t)((buffer_size - 4) / 2)); + } + + /* Read path */ + length = csim_send(csim_read, response, buffer_size); + if (length <= 0) { + return length; + } + + /* Convert from hex to binary (inplace) */ + length = hex2bin(response, length, response, length); + + return length; +} + +static int uicc_bootstrap_read_records(uint8_t *buffer, int buffer_size) +{ + pkcs15_object_t pkcs15_object; + int length; + + /* Read EF(ODF) */ + length = csim_read_file("5031", buffer, buffer_size); + if (length <= 0) { + return length; + } + + /* Decode PKCS #15 EF(ODF) Path */ + memset(&pkcs15_object, 0, sizeof(pkcs15_object)); + if (!pkcs15_ef_odf_path_decode(buffer, length, &pkcs15_object)) { + return -ENOENT; + } + + /* Check if EF(DODF) Path is found */ + if (pkcs15_object.path[0] == 0) { + return -ENOENT; + } + + /* Read EF(DODF) */ + length = csim_read_file(pkcs15_object.path, buffer, buffer_size); + if (length <= 0) { + return length; + } + + /* Decode PKCS #15 EF(DODF) Path */ + memset(&pkcs15_object, 0, sizeof(pkcs15_object)); + if (!pkcs15_ef_dodf_path_decode(buffer, length, &pkcs15_object)) { + return -ENOENT; + } + + /* Check if EF(DODF-bootstrap) Path is found */ + if (pkcs15_object.path[0] == 0) { + return -ENOENT; + } + + /* Read EF(DODF-bootstrap) */ + length = csim_read_file(pkcs15_object.path, buffer, buffer_size); + + return length; +} + +int uicc_lwm2m_bootstrap_read(uint8_t *buffer, int buffer_size) +{ + int length; + + /* Open a logical channel 1 */ + length = csim_send("0070000001", buffer, buffer_size); + if (length <= 0) { + return length; + } + + /* Select PKCS#15 on channel 1 using the default AID */ + length = csim_send("01A404040CA000000063504B43532D313500", buffer, buffer_size); + + if (length > 0) { + /* Read bootstrap records */ + length = uicc_bootstrap_read_records(buffer, buffer_size); + } + + /* Close the logical channel (using separate buffer to keep content from last file) */ + uint8_t close_response[21]; + int close_length; + + close_length = csim_send("01708001", close_response, sizeof(close_response)); + if (length >= 0 && close_length < 0) { + return close_length; + } + + return length; +} diff --git a/samples/cellular/uicc_lwm2m/CMakeLists.txt b/samples/cellular/uicc_lwm2m/CMakeLists.txt new file mode 100644 index 000000000000..576ad099b9be --- /dev/null +++ b/samples/cellular/uicc_lwm2m/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(uicc_lwm2m) + +# NORDIC SDK APP START +target_sources(app PRIVATE src/main.c) +# NORDIC SDK APP END + +zephyr_include_directories(src) diff --git a/samples/cellular/uicc_lwm2m/README.rst b/samples/cellular/uicc_lwm2m/README.rst new file mode 100644 index 000000000000..a521dd045e47 --- /dev/null +++ b/samples/cellular/uicc_lwm2m/README.rst @@ -0,0 +1,87 @@ +.. _uicc_lwm2m_sample: + +Cellular: UICC LwM2M +#################### + +.. contents:: + :local: + :depth: 2 + +The UICC LwM2M sample demonstrates how to use the :ref:`lib_uicc_lwm2m` library on an nRF91 Series device. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +.. include:: /includes/tfm.txt + +Overview +******** + +The UICC LwM2M sample turns on UICC and tries to read the LwM2M bootstrap data record from SIM. + +.. note:: + + This sample requires a SIM with LwM2M bootstrap data. + +Configuration +************* + +|config| + +.. include:: /libraries/modem/nrf_modem_lib/nrf_modem_lib_trace.rst + :start-after: modem_lib_sending_traces_UART_start + :end-before: modem_lib_sending_traces_UART_end + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/cellular/uicc_lwm2m` + +.. include:: /includes/build_and_run_ns.txt + +Testing +======= + +|test_sample| + +1. |connect_kit| +#. |connect_terminal| +#. Observe that the sample starts and shows the following output from the device. + This is an example, and the output need not be identical to your observed output. + + .. code-block:: console + + UICC LwM2M sample started + LwM2M bootstrap data found, length: 256 + + 0000 00 01 00 36 00 00 00 00 31 08 00 2e c8 00 25 63 ...6.... 1.....%c + 0010 6f 61 70 3a 2f 2f 6c 65 73 68 61 6e 2e 65 63 6c oap://le shan.ecl + 0020 69 70 73 65 70 72 6f 6a 65 63 74 73 2e 69 6f 3a ipseproj ects.io: + 0030 35 37 38 33 c1 01 01 c1 02 03 ff ff ff ff ff ff 5783.... ........ + 0040 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 0050 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 0060 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 0070 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 0080 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 0090 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00b0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00c0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00d0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00e0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + 00f0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ........ ........ + +Dependencies +************ + +This sample uses the following `sdk-nrfxlib`_ library: + +* :ref:`nrfxlib:nrf_modem` + +The sample also uses the following secure firmware component: + +* :ref:`Trusted Firmware-M ` diff --git a/samples/cellular/uicc_lwm2m/prj.conf b/samples/cellular/uicc_lwm2m/prj.conf new file mode 100644 index 000000000000..fe2418ba2ba5 --- /dev/null +++ b/samples/cellular/uicc_lwm2m/prj.conf @@ -0,0 +1,18 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# General config +CONFIG_SERIAL=y + +# Modem library +CONFIG_NRF_MODEM_LIB=y + +# UICC LwM2M library +CONFIG_UICC_LWM2M=y + +# Heap and stacks +CONFIG_HEAP_MEM_POOL_SIZE=1024 +CONFIG_MAIN_STACK_SIZE=1536 diff --git a/samples/cellular/uicc_lwm2m/sample.yaml b/samples/cellular/uicc_lwm2m/sample.yaml new file mode 100644 index 000000000000..3f5cc6059882 --- /dev/null +++ b/samples/cellular/uicc_lwm2m/sample.yaml @@ -0,0 +1,18 @@ +sample: + name: UICC LwM2M sample +tests: + sample.cellular.uicc_lwm2m: + sysbuild: true + build_only: true + platform_allow: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + - thingy91x/nrf9151/ns + integration_platforms: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + tags: ci_build sysbuild ci_samples_cellular diff --git a/samples/cellular/uicc_lwm2m/src/main.c b/samples/cellular/uicc_lwm2m/src/main.c new file mode 100644 index 000000000000..47dd620b1f94 --- /dev/null +++ b/samples/cellular/uicc_lwm2m/src/main.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include + +uint8_t buffer[UICC_RECORD_BUFFER_MAX]; + +static void printk_hexdump(char *data, int len) +{ + static const char hexchars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' }; + int pos = 0; + + while (pos < len) { + char hexbuf[256]; + char *cur = hexbuf; + int i; + + /* Dump offset */ + cur += snprintf(cur, 20, "%04x ", pos); + + /* Dump bytes as hex */ + for (i = 0; i < 16 && pos + i < len; i++) { + *cur++ = hexchars[(data[pos + i] & 0xf0) >> 4]; + *cur++ = hexchars[data[pos + i] & 0x0f]; + *cur++ = ' '; + if (i == 7) { + *cur++ = ' '; + } + } + + while (cur < hexbuf + 58) { + /* Fill it up with space to ascii column */ + *cur++ = ' '; + } + + /* Dump bytes as text */ + for (i = 0; i < 16 && pos + i < len; i++) { + if (isprint((int)data[pos + i])) { + *cur++ = data[pos + i]; + } else { + *cur++ = '.'; + } + if (i == 7) { + *cur++ = ' '; + } + } + + pos += i; + *cur = 0; + + printk("%s\n", hexbuf); + } +} + +int main(void) +{ + int ret; + + printk("UICC LwM2M sample started\n"); + + ret = nrf_modem_lib_init(); + if (ret) { + printk("Modem library initialization failed, error: %d\n", ret); + return -1; + } + + ret = nrf_modem_at_printf("AT+CFUN=41"); + if (ret) { + printk("Activate UICC failed, error: %d\n", ret); + return -1; + } + + ret = uicc_lwm2m_bootstrap_read(buffer, sizeof(buffer)); + if (ret > 0) { + printk("LwM2M bootstrap data found, length: %d\n\n", ret); + printk_hexdump(buffer, ret); + } else { + printk("Failed to read data, error: %d\n", -ret); + } + + ret = nrf_modem_lib_shutdown(); + if (ret) { + printk("Modem library shutdown failed, error: %d\n", ret); + return -1; + } + + return 0; +} diff --git a/scripts/ci/tags.yaml b/scripts/ci/tags.yaml index 445d29b32c7a..e7f9503b0508 100644 --- a/scripts/ci/tags.yaml +++ b/scripts/ci/tags.yaml @@ -1150,6 +1150,11 @@ ci_tests_lib_sample_rate_converter: - nrf/lib/sample_rate_converter/ - nrf/tests/lib/sample_rate_converter/ +ci_tests_lib_uicc_lwm2m: + files: + - nrf/lib/uicc_lwm2m/ + - nrf/tests/lib/uicc_lwm2m/ + ci_tests_drivers_lpuart: files: - modules/hal/nordic/nrfx/ diff --git a/tests/lib/uicc_lwm2m/CMakeLists.txt b/tests/lib/uicc_lwm2m/CMakeLists.txt new file mode 100644 index 000000000000..97ad33f60994 --- /dev/null +++ b/tests/lib/uicc_lwm2m/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(uicc_lwm2m) + +test_runner_generate(src/main.c) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE + ${app_sources} + ${ZEPHYR_NRF_MODULE_DIR}/lib/uicc_lwm2m/asn1_decode.c + ${ZEPHYR_NRF_MODULE_DIR}/lib/uicc_lwm2m/pkcs15_decode.c + ${ZEPHYR_NRF_MODULE_DIR}/lib/uicc_lwm2m/uicc_lwm2m.c +) + +zephyr_include_directories( + ${ZEPHYR_NRF_MODULE_DIR}/lib/uicc_lwm2m/include/ + ${ZEPHYR_NRFXLIB_MODULE_DIR}/nrf_modem/include/ +) diff --git a/tests/lib/uicc_lwm2m/prj.conf b/tests/lib/uicc_lwm2m/prj.conf new file mode 100644 index 000000000000..c4750ff9ae70 --- /dev/null +++ b/tests/lib/uicc_lwm2m/prj.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_UNITY=y diff --git a/tests/lib/uicc_lwm2m/src/main.c b/tests/lib/uicc_lwm2m/src/main.c new file mode 100644 index 000000000000..75d5ee1081a0 --- /dev/null +++ b/tests/lib/uicc_lwm2m/src/main.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "unity.h" + +struct at_csim_cmd_rsp { + char *command; + char *response; +}; + +static struct at_csim_cmd_rsp *at_csim_command; +static int at_csim_command_size; +static int at_csim_command_num; + +static struct at_csim_cmd_rsp at_csim_application_not_found[] = { + /* Open a logical channel 1 */ + { "AT+CSIM=10,\"0070000001\"", "+CSIM: 6,\"019000\"" }, + /* Select PKCS#15 on channel 1 using the default AID */ + { "AT+CSIM=36,\"01A404040CA000000063504B43532D313500\"", "+CSIM: 4,\"6A82\"" }, + /* Close the logical channel */ + { "AT+CSIM=8,\"01708001\"", "+CSIM: 4,\"9000\"" } +}; + +static struct at_csim_cmd_rsp at_csim_bootstrap_not_found[] = { + /* Open a logical channel 1 */ + { "AT+CSIM=10,\"0070000001\"", "+CSIM: 6,\"019000\"" }, + /* Select PKCS#15 on channel 1 using the default AID */ + { "AT+CSIM=36,\"01A404040CA000000063504B43532D313500\"", + "+CSIM: 62,\"621B8202412183025031A5038001718A01058B036F06028002003888009000\"" }, + /* Select EF(ODF) (path 5031) */ + { "AT+CSIM=20,\"01A40804047FFF503100\"", + "+CSIM: 62,\"621B8202412183025031A5038001718A01058B036F06028002003888009000\"" }, + /* Read EF(ODF) */ + { "AT+CSIM=10,\"01B0000080\"", "+CSIM: 42,\"A706300404024407A506300404024404FFFFFF9000\"" }, + /* Select (path 4407) */ + { "AT+CSIM=20,\"01A40804047FFF440700\"", + "+CSIM: 62,\"621B8202412183024407A5038001718A01058B036F0602800200C888009000\"" }, + /* Read record 4407 (empty) */ + { "AT+CSIM=10,\"01B0000080\"", "+CSIM: 42,\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000\"" }, + /* Close the logical channel */ + { "AT+CSIM=8,\"01708001\"", "+CSIM: 4,\"9000\"" } +}; + +static struct at_csim_cmd_rsp at_csim_success[] = { + /* Open a logical channel 1 */ + { "AT+CSIM=10,\"0070000001\"", "+CSIM: 6,\"019000\"" }, + /* Select PKCS#15 on channel 1 using the default AID */ + { "AT+CSIM=36,\"01A404040CA000000063504B43532D313500\"", + "+CSIM: 82,\"622582027821840CA000000063504B43532D31358A01058B032F060CC60990014083010183" + "010A9000\"" }, + /* Select EF(ODF) (path 5031) */ + { "AT+CSIM=20,\"01A40804047FFF503100\"", + "+CSIM: 52,\"621682024121830250318A01058B036F06028002002088009000\"" }, + /* Read EF(ODF) */ + { "AT+CSIM=10,\"01B0000080\"", "+CSIM: 20,\"A7063004040264309000\"" }, + /* Select EF(DODF) (path 6430) */ + { "AT+CSIM=20,\"01A40804047FFF643000\"", + "+CSIM: 52,\"621682024121830264308A01058B036F06028002007888009000\"" }, + /* Read EF(DODF) */ + { "AT+CSIM=10,\"01B0000080\"", + "+CSIM: 86,\"A127300030110C0F4C774D324D20426F6F747374726170A110300E06060604672B09013004" + "040264329000\"" }, + /* Select EF(DODF-bootstrap) (path 6432) */ + { "AT+CSIM=20,\"01A40804047FFF643200\"", + "+CSIM: 52,\"621682024121830264308A01058B036F06028002007888009000\"" }, + /* Read EF(DODF-bootstrap) */ + { "AT+CSIM=10,\"01B0000080\"", + "+CSIM: 124,\"00010036000000003108002EC80025636F61703A2F2F6C657368616E2E65636C697073657" + "0726F6A656374732E696F3A35373833C10101C10203FFFF9000\"" }, + /* Close the logical channel */ + { "AT+CSIM=8,\"01708001\"", "+CSIM: 4,\"9000\"" } +}; + +int nrf_modem_at_scanf(const char *cmd, const char *fmt, ...) +{ + va_list args; + int ret; + + /* Check expected command */ + TEST_ASSERT_EQUAL_STRING(at_csim_command[at_csim_command_num].command, cmd); + + /* Check expected format */ + if (at_csim_command_num == at_csim_command_size - 1) { + /* Buffer is smaller on last command (close) */ + TEST_ASSERT_EQUAL_STRING("+CSIM: %d,\"%20s\"", fmt); + } else { + TEST_ASSERT_EQUAL_STRING("+CSIM: %d,\"%260s\"", fmt); + } + + va_start(args, fmt); + ret = vsscanf(at_csim_command[at_csim_command_num].response, fmt, args); + va_end(args); + + at_csim_command_num++; + + return ret; +} + +static uint8_t lwm2m_tlv[] = { + 0x00, 0x01, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x31, 0x08, 0x00, 0x2e, + 0xc8, 0x00, 0x25, 0x63, 0x6f, 0x61, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x65, + 0x73, 0x68, 0x61, 0x6e, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, + 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x69, 0x6f, 0x3a, + 0x35, 0x37, 0x38, 0x33, 0xc1, 0x01, 0x01, 0xc1, 0x02, 0x03, 0xff, 0xff +}; + +void test_uicc_lwm2m_bootstrap_read_application_not_found(void) +{ + uint8_t buffer[256 + 4 + 1]; /* 256 bytes content + 4 bytes SW + 1 byte NUL */ + + at_csim_command = at_csim_application_not_found; + at_csim_command_size = ARRAY_SIZE(at_csim_application_not_found); + at_csim_command_num = 0; + + int ret = uicc_lwm2m_bootstrap_read(buffer, sizeof(buffer)); + + TEST_ASSERT_EQUAL(-EINVAL, ret); + TEST_ASSERT_EQUAL(at_csim_command_size, at_csim_command_num); +} + +void test_uicc_lwm2m_bootstrap_read_bootstrap_not_found(void) +{ + uint8_t buffer[256 + 4 + 1]; /* 256 bytes content + 4 bytes SW + 1 byte NUL */ + + at_csim_command = at_csim_bootstrap_not_found; + at_csim_command_size = ARRAY_SIZE(at_csim_bootstrap_not_found); + at_csim_command_num = 0; + + int ret = uicc_lwm2m_bootstrap_read(buffer, sizeof(buffer)); + + TEST_ASSERT_EQUAL(-ENOENT, ret); + TEST_ASSERT_EQUAL(at_csim_command_size, at_csim_command_num); +} + +void test_uicc_lwm2m_bootstrap_read_success(void) +{ + uint8_t buffer[256 + 4 + 1]; /* 256 bytes content + 4 bytes SW + 1 byte NUL */ + + at_csim_command = at_csim_success; + at_csim_command_size = ARRAY_SIZE(at_csim_success); + at_csim_command_num = 0; + + int ret = uicc_lwm2m_bootstrap_read(buffer, sizeof(buffer)); + + TEST_ASSERT_EQUAL(sizeof(lwm2m_tlv), ret); + TEST_ASSERT_EQUAL_MEMORY(lwm2m_tlv, buffer, sizeof(lwm2m_tlv)); + TEST_ASSERT_EQUAL(at_csim_command_size, at_csim_command_num); +} + +/* It is required to be added to each test. That is because unity's + * main may return nonzero, while zephyr's main currently must + * return 0 in all cases (other values are reserved). + */ +extern int unity_main(void); + +int main(void) +{ + (void)unity_main(); + + return 0; +} diff --git a/tests/lib/uicc_lwm2m/testcase.yaml b/tests/lib/uicc_lwm2m/testcase.yaml new file mode 100644 index 000000000000..edd376133169 --- /dev/null +++ b/tests/lib/uicc_lwm2m/testcase.yaml @@ -0,0 +1,7 @@ +tests: + uicc_lwm2m.unit_test: + sysbuild: true + tags: uicc_lwm2m sysbuild ci_tests_lib_uicc_lwm2m + platform_allow: native_sim + integration_platforms: + - native_sim