diff --git a/CHANGELOG.md b/CHANGELOG.md index 84869e09..c2366c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 3.4.1 (June 23rd, 2023) + +### Features + +- (commercial feature only) New ``sim_bootstrap`` module that implements the + logic necessary to extract the EF(DODF-bootstrap) file contents from a smart + card + +### Bugfixes + +- Fixed a potential crash in case of a specific out-of-memory condition in + Advanced Firmware Update +- Fixed `anjay_config_log.h` so that all non-binary configuration options are + properly logged +- Fixed a regression from 3.4.0 that prevented ``nsh_lwm2m.py`` from launching + ## 3.4.0 (June 14th, 2023) ### Features diff --git a/CMakeLists.txt b/CMakeLists.txt index 56ac986e..a00a5f76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.6.0) project(anjay C) -set(ANJAY_VERSION "3.4.0" CACHE STRING "Anjay library version") +set(ANJAY_VERSION "3.4.1" CACHE STRING "Anjay library version") set(ANJAY_BINARY_VERSION 1.0.0) set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") @@ -495,7 +495,8 @@ add_library(anjay src/modules/server/anjay_server_transaction.c src/modules/server/anjay_server_transaction.h src/modules/server/anjay_server_utils.c - src/modules/server/anjay_server_utils.h) + src/modules/server/anjay_server_utils.h + ) target_include_directories(anjay PUBLIC $ diff --git a/Doxyfile.in b/Doxyfile.in index 45046c9a..db30881b 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -366,7 +366,7 @@ EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. diff --git a/demo/advanced_firmware_update.c b/demo/advanced_firmware_update.c index 24b2fa51..3853a546 100644 --- a/demo/advanced_firmware_update.c +++ b/demo/advanced_firmware_update.c @@ -120,7 +120,7 @@ handle_multipackage(FILE *f, return -1; } mm.package_len[i] = avs_convert_be32(mm.package_len[i]); - if (mm.package_len == 0) { + if (mm.package_len[i] == 0) { demo_log( ERROR, "Zero-length packages within multipackage not allowed"); @@ -133,7 +133,10 @@ handle_multipackage(FILE *f, } else { /* It is not multipackage, move stream to the beginning to easily handle * like standard package */ - fseek(f, 0L, SEEK_SET); + if (fseek(f, 0L, SEEK_SET)) { + demo_log(ERROR, "Could not seek in the multipackage file"); + return -1; + } } return 0; } diff --git a/doc/sphinx/snippet_sources.md5 b/doc/sphinx/snippet_sources.md5 index 257e44bf..6da1a894 100644 --- a/doc/sphinx/snippet_sources.md5 +++ b/doc/sphinx/snippet_sources.md5 @@ -17,14 +17,14 @@ ca82847266e70bd9728611eda7b1a19e deps/avs_commons/src/net/avs_net_impl.h 4613be67a0c66ba130fe5601e75ef5f0 examples/commercial-features/CF-EST-PKCS11/src/main.c bcefabd7777e025ed9b0503b81365a59 examples/commercial-features/CF-EST/src/main.c 0500194428e4129e06a8dd5fec2da08e examples/commercial-features/CF-NIDD/src/main.c -4e6bd88507be2ec8dc27ca08cb319ac8 examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c +671d403eecc85d842c0faec745e8889c examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c f7b0898ab6ee5fe363e45f010468c8bc examples/commercial-features/CF-OSCORE/src/main.c 63981c17cad5eec1151b88e40655f780 examples/commercial-features/CF-PKCS11/src/main.c fabf0d50997dac05e8b3ddc63db092c9 examples/commercial-features/CF-PSA-bootstrap/src/main.c 4202016537ea33e39cdf82cce280c3cd examples/commercial-features/CF-PSA-management/src/main.c 1ae4a0f3eec2e64d3377c681a7ce51c9 examples/commercial-features/CF-PSA-PKI/src/main.c 9e6292087532b87f0865b691a5f9a0db examples/commercial-features/CF-PSA-PSK/src/main.c -25dc50d72d98e0faf95e193c3255cbb7 examples/commercial-features/CF-SmartCardBootstrap/src/main.c +d95709d6755012199650d59a4250516c examples/commercial-features/CF-SmartCardBootstrap/src/main.c 5ab384142e2cf91735e367fa4c8be9c3 examples/commercial-features/CF-SMS-PSK/src/main.c 991f1cd2582963c8174bb4cd20e2bb29 examples/commercial-features/CF-SMS/src/main.c 3e324c15570ac568c5efc079dd5139ba examples/commercial-features/CF-SMS-UDP/src/main.c diff --git a/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst b/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst index 49279f9c..4479b47a 100644 --- a/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst +++ b/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst @@ -6,8 +6,8 @@ Licensed under the AVSystem-5-clause License. See the attached LICENSE file for details. -Bootstrapper (smart card bootstrap) -=================================== +Bootstrapper and SIM bootstrap +============================== .. contents:: :local: @@ -28,11 +28,21 @@ between Smartcard and LwM2M Device Storage in `Appendix H `_ thereof. -While communicating with the smart card is considered outside the scope for the -Anjay library, the "bootstrapper" feature, available commercially, implements a -parser for the file format described in `section G.3.4 of the Appendix G -`_ -mentioned above. +The "bootstrapper" feature, available as a commercial extension to the Anjay +library, includes two modules that aid in implementing this part of the +specification: + +* ``bootstrapper`` implements a parser for the file format described in + `section G.3.4 of the Appendix G + `_ + mentioned above +* ``sim_bootstrap`` implements the flow of `ISO/IEC 7816-4 + `_ commands + necessary to retrieve the aforementioned file + +With the above features in place, all that's left to implement is actual +communication with the smart card, typically sending and receiving ``AT+CSIM`` +commands to a cellular modem. Bootstrapping from smart card has a number of advantages, including: @@ -64,7 +74,42 @@ be used. The user will need to provide an implementation of ``avs_stream_t`` that allows the Anjay code to read the file contained on the smartcard. The ``avs_stream_simple_input_create()`` function from the `avs_stream_simple_io.h `_ -header is likely to be the easiest way to provide such an implementation. +header is likely to be the easiest way to provide such an implementation, aside +from using the SIM bootstrap module described below. + +Enabling and configuring the sim_bootstrap module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly, to enable the sim_bootstrap module, you can enable the +``ANJAY_WITH_MODULE_SIM_BOOTSTRAP`` macro in the ``anjay_config.h`` file or, if +using CMake, enable the corresponding ``WITH_MODULE_sim_bootstrap`` CMake +option. This requires that the bootstrapper feature is also enabled. + +By default, the module will access the PKCS#15 application directory file and +search it for the EF(DODF-bootstrap) file in a way that is compliant with LwM2M +TS Appendix G mentioned above. + +However, you can override the OID of the file to look for, by defining the +``ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX`` macro in ``anjay_config.h`` +or setting the corresponding ``MODULE_sim_bootstrap_DATA_OID_OVERRIDE_HEX`` +CMake option. It shall be set to a string containing hexlified DER +representation of the desired OID. The default, standards-compliant value is +``"672b0901"`` (which corresponds to OID 2.23.43.9.1), but you may need to +change it to a different value, for example some cards are known to use a +mistakenly encoded value of ``"0604672b0901"``. + +Alternatively, you might define the +``ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID`` macro (or set the +``MODULE_sim_bootstrap_HARDCODED_FILE_ID`` CMake option) to bypass the directory +search entirely and set a hardcoded file ID, e.g. ``0x6432``. + +Once the module is enabled and configured, you can use the +`anjay_sim_bootstrap_stream_create() +<../api/sim__bootstrap_8h.html#a7cd497f30bfc7d36c6f0efb1db1d5a19>`_ function to +create an input stream suitable for passing to ``anjay_bootstrapper()``. In the +simplest case, you can also use the `anjay_sim_bootstrap_perform() +<../api/sim__bootstrap_8h.html#aa94114321f3af6532babde1efd9bdcec>`_ function +that combines both calls and automatically closes the stream as well. Bootstrap information generator tool ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -191,10 +236,11 @@ Example code commercial version of Anjay that includes the bootstrapper feature. The example is loosely based on the :doc:`../BasicClient/BC-MandatoryObjects` -tutorial. However, since the bootstrap information will be loaded from a file, -the ``setup_security_object()`` and ``setup_server_object()`` functions are no -longer necessary, and the calls to them can be replaced with direct calls to -`anjay_security_object_install() +tutorial, and additionally borrows much of the modem communication code from +:doc:`CF-NIDD`. Since the bootstrap information will be loaded from a smart +card, the ``setup_security_object()`` and ``setup_server_object()`` functions +are no longer necessary, and the calls to them can be replaced with direct calls +to `anjay_security_object_install() <../api/security_8h.html#a5fffaeedfc5c2933e58ac1446fd0401d>`_ and `anjay_server_object_install() <../api/server_8h.html#a36a369c0d7d1b2ad42c898ac47b75765>`_: @@ -205,8 +251,7 @@ longer necessary, and the calls to them can be replaced with direct calls to int main(int argc, char *argv[]) { if (argc != 3) { - avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME BOOTSTRAP_INFO_FILE", - argv[0]); + avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME MODEM_PATH", argv[0]); return -1; } @@ -231,7 +276,7 @@ longer necessary, and the calls to them can be replaced with direct calls to } if (!result) { - result = bootstrap_from_file(anjay, argv[2]); + result = bootstrap_from_sim(anjay, argv[2]); } if (!result) { @@ -246,40 +291,140 @@ longer necessary, and the calls to them can be replaced with direct calls to As you can see, the command line now expects a second argument with a name of the file containing the bootstrap information. -This file is loaded using the ``bootstrap_from_file()`` function, implemented as +This file is loaded using the ``bootstrap_from_sim()`` function, implemented as follows: .. highlight:: c .. snippet-source:: examples/commercial-features/CF-SmartCardBootstrap/src/main.c - static int bootstrap_from_file(anjay_t *anjay, const char *filename) { - avs_log(tutorial, INFO, "Attempting to bootstrap from file"); - - avs_stream_t *file_stream = - avs_stream_file_create(filename, AVS_STREAM_FILE_READ); - - if (!file_stream) { - avs_log(tutorial, ERROR, "Could not open file"); + typedef struct { + avs_buffer_t *buffer; + } fifo_t; + + // ... + + typedef struct { + fifo_t fifo; + int pts_fd; + } modem_ctx_t; + + // ... + + static int sim_perform_command(void *modem_ctx_, + const void *cmd, + size_t cmd_length, + void *out_buf, + size_t out_buf_size, + size_t *out_response_size) { + modem_ctx_t *modem_ctx = (modem_ctx_t *) modem_ctx_; + char req_buf[REQ_BUF_SIZE]; + char resp_buf[RESP_BUF_SIZE] = ""; + + char *req_buf_ptr = req_buf; + char *const req_buf_end = req_buf + sizeof(req_buf); + int result = avs_simple_snprintf(req_buf_ptr, + (size_t) (req_buf_end - req_buf_ptr), + "AT+CSIM=%" PRIu32 ",\"", + (uint32_t) (2 * cmd_length)); + if (result < 0) { + return result; + } + req_buf_ptr += result; + if ((size_t) (req_buf_end - req_buf_ptr) < 2 * cmd_length) { return -1; } - - int result = 0; - if (avs_is_err(anjay_bootstrapper(anjay, file_stream))) { - avs_log(tutorial, ERROR, "Could not bootstrap from file"); - result = -1; + if ((result = avs_hexlify(req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), + NULL, cmd, cmd_length))) { + return result; + } + req_buf_ptr += 2 * cmd_length; + if ((result = avs_simple_snprintf( + req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), "\"\r\n")) + < 0) { + return result; + } + req_buf_ptr += result; + ssize_t written = + write(modem_ctx->pts_fd, req_buf, (size_t) (req_buf_ptr - req_buf)); + if (written != (ssize_t) (req_buf_ptr - req_buf)) { + return -1; } + avs_time_monotonic_t deadline = avs_time_monotonic_add( + avs_time_monotonic_now(), + avs_time_duration_from_scalar(5, AVS_TIME_S)); + bool csim_resp_received = false; + bool ok_received = false; + while (!ok_received) { + if (modem_getline(modem_ctx, resp_buf, sizeof(resp_buf), deadline)) { + return -1; + } + const char *resp_terminator = memchr(resp_buf, '\0', sizeof(resp_buf)); + if (!resp_terminator) { + return -1; + } + if (memcmp(resp_buf, CSIM_RESP, strlen(CSIM_RESP)) == 0) { + if (csim_resp_received) { + return -1; + } + errno = 0; + char *endptr = NULL; + long long resp_reported_length = + strtoll(resp_buf + strlen(CSIM_RESP), &endptr, 10); + if (errno || !endptr || endptr[0] != ',' || endptr[1] != '"' + || resp_reported_length < 0 + || endptr + resp_reported_length + 2 >= resp_terminator + || endptr[resp_reported_length + 2] != '"' + || avs_unhexlify(out_response_size, (uint8_t *) out_buf, + out_buf_size, endptr + 2, + (size_t) resp_reported_length)) { + return -1; + } + csim_resp_received = true; + } else if (strcmp(resp_buf, "OK") == 0) { + ok_received = true; + } + } + return csim_resp_received ? 0 : -1; + } + + static int bootstrap_from_sim(anjay_t *anjay, const char *modem_device) { + modem_ctx_t modem_ctx = { + .pts_fd = -1 + }; + int result = -1; + + avs_log(tutorial, INFO, "Attempting to bootstrap from SIM card"); - avs_stream_cleanup(&file_stream); + if (fifo_init(&modem_ctx.fifo)) { + avs_log(tutorial, ERROR, "could not initialize FIFO"); + goto finish; + } + if ((modem_ctx.pts_fd = open(modem_device, O_RDWR)) < 0) { + avs_log(tutorial, ERROR, "could not open modem device %s: %s", + modem_device, strerror(errno)); + goto finish; + } + if (avs_is_err(anjay_sim_bootstrap_perform(anjay, sim_perform_command, + &modem_ctx))) { + avs_log(tutorial, ERROR, "Could not bootstrap from SIM card"); + goto finish; + } + result = 0; + finish: + if (modem_ctx.pts_fd >= 0) { + close(modem_ctx.pts_fd); + } + fifo_destroy(&modem_ctx.fifo); return result; } -This shares similarities with the ``restore_objects_if_possible()`` function -from the :doc:`../AdvancedTopics/AT-Persistence` tutorial. +The ``sim_perform_command()`` function is a callback that is passed to the +``sim_bootstrap`` module logic, and performs the ``AT+CSIM`` command over a +serial port. The ``modem_getline()`` function it calls is almost identical to +the one originally implemented for :doc:`CF-NIDD`. -As mentioned in the :ref:`cf-smart-card-bootstrap-enabling` section above, to -perform bootstrap using an actual smart card, the -``avs_stream_simple_input_create()`` function from the `avs_stream_simple_io.h -`_ -header could be used instead of the ``avs_stream_file_create()`` call that is -used here to access regular file system. +The ``bootstrap_from_sim()`` function itself is a wrapper over +`anjay_sim_bootstrap_perform() +<../api/sim__bootstrap_8h.html#aa94114321f3af6532babde1efd9bdcec>`_ that +additionally initializes and closes the card communication channel. diff --git a/example_configs/embedded_lwm2m10/anjay/anjay_config.h b/example_configs/embedded_lwm2m10/anjay/anjay_config.h index a93c138e..8629e28b 100644 --- a/example_configs/embedded_lwm2m10/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m10/anjay/anjay_config.h @@ -562,6 +562,64 @@ */ /* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */ +/** + * Enable the SIM bootstrap module, which enables reading the SIM bootstrap + * information from a smartcard, which can then be passed through to the + * bootstrapper module. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */ + +/** + * Forced ID of the file to read the SIM bootstrap information from. + * + * If not defined (default), the bootstrap information file will be discovered + * through the ODF file, as mandated by the specification. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */ + +/** + * Overridden OID of the SIM bootstrap information to look for in the DODF file, + * expressed as a hexlified DER representation (without the header). + * + * This is the hexlified expected value of the 'id' field within the 'OidDO' + * sequence in the DODF file (please refer to the PKCS #15 document for more + * information). + * + * If not defined, the default value of "672b0901", which corresponds to + * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43) + * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used. + * + * No other values than the default are valid according to the specification, + * but some SIM cards are known to use other non-standard values, e.g. + * "0604672b0901" - including a superfluous nested BER-TLV header, as + * erroneously illustrated in the EF(DODF-bootstrap) file coding example in + * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as + * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist + * in the repository as of writing this note). + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */ + /** * Enable factory provisioning module. Data provided during provisioning uses * SenML CBOR format. diff --git a/example_configs/embedded_lwm2m11/anjay/anjay_config.h b/example_configs/embedded_lwm2m11/anjay/anjay_config.h index a7619d45..cbe82999 100644 --- a/example_configs/embedded_lwm2m11/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m11/anjay/anjay_config.h @@ -562,6 +562,64 @@ */ /* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */ +/** + * Enable the SIM bootstrap module, which enables reading the SIM bootstrap + * information from a smartcard, which can then be passed through to the + * bootstrapper module. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */ + +/** + * Forced ID of the file to read the SIM bootstrap information from. + * + * If not defined (default), the bootstrap information file will be discovered + * through the ODF file, as mandated by the specification. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */ + +/** + * Overridden OID of the SIM bootstrap information to look for in the DODF file, + * expressed as a hexlified DER representation (without the header). + * + * This is the hexlified expected value of the 'id' field within the 'OidDO' + * sequence in the DODF file (please refer to the PKCS #15 document for more + * information). + * + * If not defined, the default value of "672b0901", which corresponds to + * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43) + * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used. + * + * No other values than the default are valid according to the specification, + * but some SIM cards are known to use other non-standard values, e.g. + * "0604672b0901" - including a superfluous nested BER-TLV header, as + * erroneously illustrated in the EF(DODF-bootstrap) file coding example in + * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as + * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist + * in the repository as of writing this note). + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */ + /** * Enable factory provisioning module. Data provided during provisioning uses * SenML CBOR format. diff --git a/example_configs/linux_lwm2m10/anjay/anjay_config.h b/example_configs/linux_lwm2m10/anjay/anjay_config.h index 7d45e7f7..df15e888 100644 --- a/example_configs/linux_lwm2m10/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m10/anjay/anjay_config.h @@ -562,6 +562,64 @@ */ /* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */ +/** + * Enable the SIM bootstrap module, which enables reading the SIM bootstrap + * information from a smartcard, which can then be passed through to the + * bootstrapper module. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */ + +/** + * Forced ID of the file to read the SIM bootstrap information from. + * + * If not defined (default), the bootstrap information file will be discovered + * through the ODF file, as mandated by the specification. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */ + +/** + * Overridden OID of the SIM bootstrap information to look for in the DODF file, + * expressed as a hexlified DER representation (without the header). + * + * This is the hexlified expected value of the 'id' field within the 'OidDO' + * sequence in the DODF file (please refer to the PKCS #15 document for more + * information). + * + * If not defined, the default value of "672b0901", which corresponds to + * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43) + * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used. + * + * No other values than the default are valid according to the specification, + * but some SIM cards are known to use other non-standard values, e.g. + * "0604672b0901" - including a superfluous nested BER-TLV header, as + * erroneously illustrated in the EF(DODF-bootstrap) file coding example in + * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as + * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist + * in the repository as of writing this note). + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */ + /** * Enable factory provisioning module. Data provided during provisioning uses * SenML CBOR format. diff --git a/example_configs/linux_lwm2m11/anjay/anjay_config.h b/example_configs/linux_lwm2m11/anjay/anjay_config.h index 0754bde8..5257d679 100644 --- a/example_configs/linux_lwm2m11/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m11/anjay/anjay_config.h @@ -562,6 +562,64 @@ */ /* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */ +/** + * Enable the SIM bootstrap module, which enables reading the SIM bootstrap + * information from a smartcard, which can then be passed through to the + * bootstrapper module. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */ + +/** + * Forced ID of the file to read the SIM bootstrap information from. + * + * If not defined (default), the bootstrap information file will be discovered + * through the ODF file, as mandated by the specification. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */ + +/** + * Overridden OID of the SIM bootstrap information to look for in the DODF file, + * expressed as a hexlified DER representation (without the header). + * + * This is the hexlified expected value of the 'id' field within the 'OidDO' + * sequence in the DODF file (please refer to the PKCS #15 document for more + * information). + * + * If not defined, the default value of "672b0901", which corresponds to + * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43) + * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used. + * + * No other values than the default are valid according to the specification, + * but some SIM cards are known to use other non-standard values, e.g. + * "0604672b0901" - including a superfluous nested BER-TLV header, as + * erroneously illustrated in the EF(DODF-bootstrap) file coding example in + * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as + * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist + * in the repository as of writing this note). + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */ + /** * Enable factory provisioning module. Data provided during provisioning uses * SenML CBOR format. diff --git a/examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c b/examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c index 500701e0..3e39313c 100644 --- a/examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c +++ b/examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c @@ -118,17 +118,16 @@ static void fifo_strip_nullbytes(fifo_t *fifo) { } static avs_error_t fifo_push_read(fifo_t *fifo, int fd) { - size_t space_left = avs_buffer_space_left(fifo->buffer); - assert(space_left > 0); // This shall be handled in fifo_pop_line() - ssize_t bytes_read = - read(fd, avs_buffer_raw_insert_ptr(fifo->buffer), space_left); + // This shall be handled in fifo_pop_line() + assert(avs_buffer_space_left(fifo->buffer) > 0); + ssize_t bytes_read = read(fd, avs_buffer_raw_insert_ptr(fifo->buffer), 1); if (bytes_read < 0) { return avs_errno(AVS_EIO); } else if (bytes_read == 0) { return AVS_EOF; } else { - assert((size_t) bytes_read <= space_left); - avs_buffer_advance_ptr(fifo->buffer, (size_t) bytes_read); + assert(bytes_read == 1); + avs_buffer_advance_ptr(fifo->buffer, 1); fifo_strip_nullbytes(fifo); return AVS_OK; } diff --git a/examples/commercial-features/CF-SmartCardBootstrap/src/main.c b/examples/commercial-features/CF-SmartCardBootstrap/src/main.c index d3924e22..7bf08f47 100644 --- a/examples/commercial-features/CF-SmartCardBootstrap/src/main.c +++ b/examples/commercial-features/CF-SmartCardBootstrap/src/main.c @@ -1,35 +1,343 @@ #include -#include #include #include +#include +#include #include #include -static int bootstrap_from_file(anjay_t *anjay, const char *filename) { - avs_log(tutorial, INFO, "Attempting to bootstrap from file"); +#include +#include +#include - avs_stream_t *file_stream = - avs_stream_file_create(filename, AVS_STREAM_FILE_READ); +#include +#include +#include - if (!file_stream) { - avs_log(tutorial, ERROR, "Could not open file"); +#include +#include + +#define SIM_COMMAND_MAX_BINARY_SIZE 258 + +#define CSIM_RESP "+CSIM: " + +#define REQ_BUF_SIZE \ + (sizeof("AT+CSIM=999,\"\"\r\n") + 2 * SIM_COMMAND_MAX_BINARY_SIZE) +#define RESP_BUF_SIZE \ + (sizeof(CSIM_RESP "999,\"\"") + 2 * SIM_COMMAND_MAX_BINARY_SIZE) + +typedef struct { + avs_buffer_t *buffer; +} fifo_t; + +static int fifo_init(fifo_t *fifo) { + if (fifo->buffer) { return -1; } + return avs_buffer_create(&fifo->buffer, 4096); +} - int result = 0; - if (avs_is_err(anjay_bootstrapper(anjay, file_stream))) { - avs_log(tutorial, ERROR, "Could not bootstrap from file"); - result = -1; +static void fifo_destroy(fifo_t *fifo) { + avs_buffer_free(&fifo->buffer); +} + +static int fifo_find_off(fifo_t *fifo, char ch, size_t *out_off) { + const char *start = avs_buffer_data(fifo->buffer); + char *ptr = (char *) memchr(start, ch, avs_buffer_data_size(fifo->buffer)); + if (ptr) { + *out_off = (size_t) (ptr - start); + return 0; } + return -1; +} + +static void fifo_pop_n(fifo_t *fifo, char *out_buffer, size_t size, size_t n) { + assert(n <= avs_buffer_data_size(fifo->buffer)); + assert(n <= size); + (void) size; + + memcpy(out_buffer, avs_buffer_data(fifo->buffer), n); + avs_buffer_consume_bytes(fifo->buffer, n); +} + +static void fifo_discard_n(fifo_t *fifo, size_t n) { + assert(n <= avs_buffer_data_size(fifo->buffer)); + avs_buffer_consume_bytes(fifo->buffer, n); +} + +// NOTE: If out_line is too small to hold the entire line, +// excess characters will be discarded +static int fifo_pop_line(fifo_t *fifo, char *out_line, size_t out_size) { + assert(out_size > 0); + + size_t line_size; + if (!fifo_find_off(fifo, '\n', &line_size) + || !fifo_find_off(fifo, '\r', &line_size)) { + ++line_size; + } else if (avs_buffer_space_left(fifo->buffer) == 0) { + avs_log(tutorial, WARNING, + "FIFO buffer full, treating received data as a line"); + line_size = avs_buffer_data_size(fifo->buffer); + } else { + line_size = 0; + } + + size_t bytes_to_pop = AVS_MIN(out_size - 1, line_size); + fifo_pop_n(fifo, out_line, out_size - 1, bytes_to_pop); + out_line[bytes_to_pop] = '\0'; + + // Discard excess bytes, if any + if (line_size != bytes_to_pop) { + fifo_discard_n(fifo, line_size - bytes_to_pop); + avs_log(tutorial, WARNING, "buffer size too small to hold the line"); + return 1; + } + return 0; +} + +static void fifo_strip_nullbytes(fifo_t *fifo) { + size_t buffer_size = avs_buffer_data_size(fifo->buffer); + char *buffer = avs_buffer_raw_insert_ptr(fifo->buffer) - buffer_size; + ssize_t block_end_offset = (ssize_t) buffer_size; + assert(block_end_offset >= 0); + ssize_t moved_by = 0; + while (block_end_offset > 0) { + ssize_t first_null_offset = block_end_offset; + while (first_null_offset > 0 && buffer[first_null_offset - 1] == '\0') { + --first_null_offset; + } + ssize_t first_nonnull_offset = first_null_offset; + while (first_nonnull_offset > 0 + && buffer[first_nonnull_offset - 1] != '\0') { + --first_nonnull_offset; + } + if (first_null_offset != block_end_offset) { + assert(first_null_offset < block_end_offset); + moved_by += block_end_offset - first_null_offset; + if (first_nonnull_offset != first_null_offset) { + assert(first_nonnull_offset < first_null_offset); + memmove(buffer + first_nonnull_offset + moved_by, + buffer + first_nonnull_offset, + (size_t) (first_null_offset - first_nonnull_offset)); + } + } + block_end_offset = first_nonnull_offset; + } + assert(moved_by >= 0); + if (moved_by > 0) { + avs_buffer_consume_bytes(fifo->buffer, (size_t) moved_by); + } +} + +static avs_error_t fifo_push_read(fifo_t *fifo, int fd) { + // This shall be handled in fifo_pop_line() + assert(avs_buffer_space_left(fifo->buffer) > 0); + ssize_t bytes_read = read(fd, avs_buffer_raw_insert_ptr(fifo->buffer), 1); + if (bytes_read < 0) { + return avs_errno(AVS_EIO); + } else if (bytes_read == 0) { + return AVS_EOF; + } else { + assert(bytes_read == 1); + avs_buffer_advance_ptr(fifo->buffer, 1); + fifo_strip_nullbytes(fifo); + return AVS_OK; + } +} + +typedef struct { + fifo_t fifo; + int pts_fd; +} modem_ctx_t; - avs_stream_cleanup(&file_stream); +static void trim_inplace(char *buffer) { + assert(buffer); + + size_t len = strlen(buffer); + for (char *ch = buffer + len - 1; + ch >= buffer && isspace((unsigned char) *ch); + --ch) { + *ch = '\0'; + } + char *first_nonblank = buffer; + for (char *ch = buffer; *ch; ++ch) { + if (!isspace((unsigned char) *ch)) { + first_nonblank = ch; + break; + } + } + memmove(buffer, first_nonblank, strlen(first_nonblank) + 1); +} + +static int modem_getline(modem_ctx_t *modem_ctx, + char *out_line_buffer, + size_t buffer_size, + avs_time_monotonic_t deadline) { + struct pollfd fd; + fd.fd = modem_ctx->pts_fd; + fd.events = POLLIN; + + int64_t timeout_ms; + int result; + + // Note: this loop is not signal-safe. + do { + if (avs_time_duration_to_scalar( + &timeout_ms, AVS_TIME_MS, + avs_time_monotonic_diff(deadline, + avs_time_monotonic_now()))) { + timeout_ms = -1; + } else if (timeout_ms < 0) { + timeout_ms = 0; + } + + while (true) { + result = fifo_pop_line(&modem_ctx->fifo, out_line_buffer, + buffer_size); + if (*out_line_buffer == '\0') { + break; + } + trim_inplace(out_line_buffer); + if (*out_line_buffer != '\0') { + avs_log(tutorial, DEBUG, "[MODEM] recv: %s", out_line_buffer); + return result; + } + } + + assert(timeout_ms <= INT_MAX); + if ((result = poll(&fd, 1, (int) timeout_ms)) == 1) { + avs_error_t err = + fifo_push_read(&modem_ctx->fifo, modem_ctx->pts_fd); + if (avs_is_eof(err)) { + avs_log(tutorial, DEBUG, "[MODEM] recv: EOF"); + return -1; + } else if (avs_is_err(err)) { + return -1; + } + } else if (result < 0) { + return -1; + } + // read up until timeout becomes 0, or if it is 0, read up until there's + // something to read. + } while (timeout_ms != 0 || result == 1); + + avs_log(tutorial, DEBUG, "[MODEM] recv: timeout"); + assert(buffer_size > 0); + out_line_buffer[0] = '\0'; + return 0; +} + +static int sim_perform_command(void *modem_ctx_, + const void *cmd, + size_t cmd_length, + void *out_buf, + size_t out_buf_size, + size_t *out_response_size) { + modem_ctx_t *modem_ctx = (modem_ctx_t *) modem_ctx_; + char req_buf[REQ_BUF_SIZE]; + char resp_buf[RESP_BUF_SIZE] = ""; + + char *req_buf_ptr = req_buf; + char *const req_buf_end = req_buf + sizeof(req_buf); + int result = avs_simple_snprintf(req_buf_ptr, + (size_t) (req_buf_end - req_buf_ptr), + "AT+CSIM=%" PRIu32 ",\"", + (uint32_t) (2 * cmd_length)); + if (result < 0) { + return result; + } + req_buf_ptr += result; + if ((size_t) (req_buf_end - req_buf_ptr) < 2 * cmd_length) { + return -1; + } + if ((result = avs_hexlify(req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), + NULL, cmd, cmd_length))) { + return result; + } + req_buf_ptr += 2 * cmd_length; + if ((result = avs_simple_snprintf( + req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), "\"\r\n")) + < 0) { + return result; + } + req_buf_ptr += result; + ssize_t written = + write(modem_ctx->pts_fd, req_buf, (size_t) (req_buf_ptr - req_buf)); + if (written != (ssize_t) (req_buf_ptr - req_buf)) { + return -1; + } + avs_time_monotonic_t deadline = avs_time_monotonic_add( + avs_time_monotonic_now(), + avs_time_duration_from_scalar(5, AVS_TIME_S)); + bool csim_resp_received = false; + bool ok_received = false; + while (!ok_received) { + if (modem_getline(modem_ctx, resp_buf, sizeof(resp_buf), deadline)) { + return -1; + } + const char *resp_terminator = memchr(resp_buf, '\0', sizeof(resp_buf)); + if (!resp_terminator) { + return -1; + } + if (memcmp(resp_buf, CSIM_RESP, strlen(CSIM_RESP)) == 0) { + if (csim_resp_received) { + return -1; + } + errno = 0; + char *endptr = NULL; + long long resp_reported_length = + strtoll(resp_buf + strlen(CSIM_RESP), &endptr, 10); + if (errno || !endptr || endptr[0] != ',' || endptr[1] != '"' + || resp_reported_length < 0 + || endptr + resp_reported_length + 2 >= resp_terminator + || endptr[resp_reported_length + 2] != '"' + || avs_unhexlify(out_response_size, (uint8_t *) out_buf, + out_buf_size, endptr + 2, + (size_t) resp_reported_length)) { + return -1; + } + csim_resp_received = true; + } else if (strcmp(resp_buf, "OK") == 0) { + ok_received = true; + } + } + return csim_resp_received ? 0 : -1; +} + +static int bootstrap_from_sim(anjay_t *anjay, const char *modem_device) { + modem_ctx_t modem_ctx = { + .pts_fd = -1 + }; + int result = -1; + + avs_log(tutorial, INFO, "Attempting to bootstrap from SIM card"); + + if (fifo_init(&modem_ctx.fifo)) { + avs_log(tutorial, ERROR, "could not initialize FIFO"); + goto finish; + } + if ((modem_ctx.pts_fd = open(modem_device, O_RDWR)) < 0) { + avs_log(tutorial, ERROR, "could not open modem device %s: %s", + modem_device, strerror(errno)); + goto finish; + } + if (avs_is_err(anjay_sim_bootstrap_perform(anjay, sim_perform_command, + &modem_ctx))) { + avs_log(tutorial, ERROR, "Could not bootstrap from SIM card"); + goto finish; + } + result = 0; +finish: + if (modem_ctx.pts_fd >= 0) { + close(modem_ctx.pts_fd); + } + fifo_destroy(&modem_ctx.fifo); return result; } int main(int argc, char *argv[]) { if (argc != 3) { - avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME BOOTSTRAP_INFO_FILE", - argv[0]); + avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME MODEM_PATH", argv[0]); return -1; } @@ -54,7 +362,7 @@ int main(int argc, char *argv[]) { } if (!result) { - result = bootstrap_from_file(anjay, argv[2]); + result = bootstrap_from_sim(anjay, argv[2]); } if (!result) { diff --git a/include_public/anjay/anjay_config.h.in b/include_public/anjay/anjay_config.h.in index 2e285c75..93cb1f91 100644 --- a/include_public/anjay/anjay_config.h.in +++ b/include_public/anjay/anjay_config.h.in @@ -562,6 +562,64 @@ */ #cmakedefine ANJAY_WITH_MODULE_BOOTSTRAPPER +/** + * Enable the SIM bootstrap module, which enables reading the SIM bootstrap + * information from a smartcard, which can then be passed through to the + * bootstrapper module. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +#cmakedefine ANJAY_WITH_MODULE_SIM_BOOTSTRAP + +/** + * Forced ID of the file to read the SIM bootstrap information from. + * + * If not defined (default), the bootstrap information file will be discovered + * through the ODF file, as mandated by the specification. + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +#cmakedefine ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID @ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID@ + +/** + * Overridden OID of the SIM bootstrap information to look for in the DODF file, + * expressed as a hexlified DER representation (without the header). + * + * This is the hexlified expected value of the 'id' field within the 'OidDO' + * sequence in the DODF file (please refer to the PKCS #15 document for more + * information). + * + * If not defined, the default value of "672b0901", which corresponds to + * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43) + * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used. + * + * No other values than the default are valid according to the specification, + * but some SIM cards are known to use other non-standard values, e.g. + * "0604672b0901" - including a superfluous nested BER-TLV header, as + * erroneously illustrated in the EF(DODF-bootstrap) file coding example in + * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as + * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist + * in the repository as of writing this note). + * + * Requires ANJAY_WITH_MODULE_BOOTSTRAPPER to be enabled. At most one of + * ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID and + * ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX may be defined at the + * same time. + * + * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open + * source version. + */ +#cmakedefine ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX "@ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX@" + /** * Enable factory provisioning module. Data provided during provisioning uses * SenML CBOR format. diff --git a/src/anjay_config_log.h b/src/anjay_config_log.h index 067713e2..b907acb7 100644 --- a/src/anjay_config_log.h +++ b/src/anjay_config_log.h @@ -24,6 +24,16 @@ static inline void _anjay_log_feature_list(void) { _anjay_log(anjay, TRACE, "ANJAY_MAX_SECRET_KEY_SIZE = " AVS_QUOTE_MACRO(ANJAY_MAX_SECRET_KEY_SIZE)); _anjay_log(anjay, TRACE, "ANJAY_MAX_URI_QUERY_SEGMENT_SIZE = " AVS_QUOTE_MACRO(ANJAY_MAX_URI_QUERY_SEGMENT_SIZE)); _anjay_log(anjay, TRACE, "ANJAY_MAX_URI_SEGMENT_SIZE = " AVS_QUOTE_MACRO(ANJAY_MAX_URI_SEGMENT_SIZE)); +#ifdef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX + _anjay_log(anjay, TRACE, "ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX = " AVS_QUOTE_MACRO(ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX)); +#else // ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX + _anjay_log(anjay, TRACE, "ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX = OFF"); +#endif // ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX +#ifdef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID + _anjay_log(anjay, TRACE, "ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID = " AVS_QUOTE_MACRO(ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID)); +#else // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID + _anjay_log(anjay, TRACE, "ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID = OFF"); +#endif // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID #ifdef ANJAY_WITHOUT_DEREGISTER _anjay_log(anjay, TRACE, "ANJAY_WITHOUT_DEREGISTER = ON"); #else // ANJAY_WITHOUT_DEREGISTER @@ -209,6 +219,11 @@ static inline void _anjay_log_feature_list(void) { #else // ANJAY_WITH_MODULE_SERVER _anjay_log(anjay, TRACE, "ANJAY_WITH_MODULE_SERVER = OFF"); #endif // ANJAY_WITH_MODULE_SERVER +#ifdef ANJAY_WITH_MODULE_SIM_BOOTSTRAP + _anjay_log(anjay, TRACE, "ANJAY_WITH_MODULE_SIM_BOOTSTRAP = ON"); +#else // ANJAY_WITH_MODULE_SIM_BOOTSTRAP + _anjay_log(anjay, TRACE, "ANJAY_WITH_MODULE_SIM_BOOTSTRAP = OFF"); +#endif // ANJAY_WITH_MODULE_SIM_BOOTSTRAP #ifdef ANJAY_WITH_NET_STATS _anjay_log(anjay, TRACE, "ANJAY_WITH_NET_STATS = ON"); #else // ANJAY_WITH_NET_STATS @@ -321,7 +336,7 @@ static inline void _anjay_log_feature_list(void) { _anjay_log(anjay, TRACE, "AVS_COMMONS_HTTP_WITH_ZLIB = OFF"); #endif // AVS_COMMONS_HTTP_WITH_ZLIB #ifdef AVS_COMMONS_LOG_MAX_LINE_LENGTH - _anjay_log(anjay, TRACE, "AVS_COMMONS_LOG_MAX_LINE_LENGTH = ON"); + _anjay_log(anjay, TRACE, "AVS_COMMONS_LOG_MAX_LINE_LENGTH = " AVS_QUOTE_MACRO(AVS_COMMONS_LOG_MAX_LINE_LENGTH)); #else // AVS_COMMONS_LOG_MAX_LINE_LENGTH _anjay_log(anjay, TRACE, "AVS_COMMONS_LOG_MAX_LINE_LENGTH = OFF"); #endif // AVS_COMMONS_LOG_MAX_LINE_LENGTH @@ -411,7 +426,7 @@ static inline void _anjay_log_feature_list(void) { _anjay_log(anjay, TRACE, "AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE = OFF"); #endif // AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE #ifdef AVS_COMMONS_POSIX_COMPAT_HEADER - _anjay_log(anjay, TRACE, "AVS_COMMONS_POSIX_COMPAT_HEADER = ON"); + _anjay_log(anjay, TRACE, "AVS_COMMONS_POSIX_COMPAT_HEADER = " AVS_QUOTE_MACRO(AVS_COMMONS_POSIX_COMPAT_HEADER)); #else // AVS_COMMONS_POSIX_COMPAT_HEADER _anjay_log(anjay, TRACE, "AVS_COMMONS_POSIX_COMPAT_HEADER = OFF"); #endif // AVS_COMMONS_POSIX_COMPAT_HEADER @@ -581,12 +596,12 @@ static inline void _anjay_log_feature_list(void) { _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_CUSTOM_TLS = OFF"); #endif // AVS_COMMONS_WITH_CUSTOM_TLS #ifdef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER - _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER = ON"); + _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER = " AVS_QUOTE_MACRO(AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER)); #else // AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER = OFF"); #endif // AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER #ifdef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER - _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER = ON"); + _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER = " AVS_QUOTE_MACRO(AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER)); #else // AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER _anjay_log(anjay, TRACE, "AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER = OFF"); #endif // AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER diff --git a/src/core/anjay_core.c b/src/core/anjay_core.c index cfa066c5..4175550c 100644 --- a/src/core/anjay_core.c +++ b/src/core/anjay_core.c @@ -47,7 +47,7 @@ VISIBILITY_SOURCE_BEGIN #ifndef ANJAY_VERSION -# define ANJAY_VERSION "3.4.0" +# define ANJAY_VERSION "3.4.1" #endif // ANJAY_VERSION #ifdef ANJAY_WITH_LWM2M11 diff --git a/src/core/servers/anjay_connection_ip.c b/src/core/servers/anjay_connection_ip.c index 469e60a7..8ee8ab47 100644 --- a/src/core/servers/anjay_connection_ip.c +++ b/src/core/servers/anjay_connection_ip.c @@ -211,16 +211,22 @@ try_bind_to_last_local_port(anjay_server_connection_t *connection, const char *local_addr) { avs_error_t err = avs_errno(AVS_EBADF); if (*connection->nontransient_state.last_local_port) { + if (avs_is_ok(avs_net_socket_bind( + _anjay_connection_internal_get_socket(connection), + local_addr, + connection->nontransient_state.last_local_port))) { + return AVS_OK; + } + // Binding to a specific address family may not work if a different + // family has been forced. Let's try without the local address. if (avs_is_ok(( err = avs_net_socket_bind( _anjay_connection_internal_get_socket(connection), - local_addr, + NULL, connection->nontransient_state.last_local_port)))) { return AVS_OK; } - anjay_log(WARNING, - _("could not bind socket to last known address ") "[%s]:%s", - local_addr ? local_addr : "", + anjay_log(WARNING, _("could not bind socket to port ") "%s", connection->nontransient_state.last_local_port); } return err; diff --git a/src/core/servers/anjay_connections.c b/src/core/servers/anjay_connections.c index 08f2944e..3db160d7 100644 --- a/src/core/servers/anjay_connections.c +++ b/src/core/servers/anjay_connections.c @@ -364,8 +364,8 @@ avs_error_t _anjay_server_connection_internal_bring_online( } bool session_resumed; - avs_error_t err = def->connect_socket(server->anjay, connection); - if (avs_is_err(err)) { + avs_error_t err = AVS_OK; + if (avs_is_err((err = def->connect_socket(server->anjay, connection)))) { goto error; } diff --git a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c index 2ce404bf..5d7b640b 100644 --- a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c +++ b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c @@ -933,8 +933,8 @@ static int enqueue_download(anjay_unlocked_t *anjay, cleanup: fw_log(ERROR, _("Out of memory")); - avs_free((void *) (intptr_t) new_download->url); if (new_download) { + avs_free((void *) (intptr_t) new_download->url); AVS_LIST_DELETE(&new_download); } return -1; diff --git a/tests/integration/framework/lwm2m/coap/server.py b/tests/integration/framework/lwm2m/coap/server.py index 4cbba76c..f352b77a 100644 --- a/tests/integration/framework/lwm2m/coap/server.py +++ b/tests/integration/framework/lwm2m/coap/server.py @@ -94,10 +94,12 @@ def _disconnect_socket(old_sock, family): """ new_sock = socket.socket(family, socket.SOCK_DGRAM, 0) - orig_reuse_addr = old_sock.getsockopt( - socket.SOL_SOCKET, socket.SO_REUSEADDR) - orig_reuse_port = old_sock.getsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT) + orig_reuse_addr = old_sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) + orig_reuse_port = old_sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) + + orig_ipv6only = None + if family == socket.AF_INET6 and hasattr(socket, 'IPV6_V6ONLY'): + orig_ipv6only = old_sock.getsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY) # temporarily set REUSEADDR and REUSEPORT to allow new socket to bind # to the same port @@ -108,6 +110,9 @@ def set_sock_reuse(sock, reuse_addr, reuse_port): set_sock_reuse(new_sock, 1, 1) set_sock_reuse(old_sock, 1, 1) + if orig_ipv6only is not None: + new_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, orig_ipv6only) + new_sock.bind(old_sock.getsockname()) old_sock.close() @@ -123,6 +128,7 @@ def __init__(self, listen_port=0, use_ipv6=False, reuse_port=False, transport=Tr self.socket = None self.server_socket = None self.family = socket.AF_INET6 if use_ipv6 else socket.AF_INET + self.ipv6_only = (str(use_ipv6).lower() == 'only') self.transport = transport self.reuse_port = reuse_port self.accepted_connection = False @@ -193,8 +199,7 @@ def _fake_unclose(self) -> None: self._raw_udp_socket.connect(self._prev_remote_endpoint) self._prev_remote_endpoint = None else: - self._raw_udp_socket = _disconnect_socket( - self._raw_udp_socket, self.family) + self._raw_udp_socket = _disconnect_socket(self._raw_udp_socket, self.family) def _flush_recv_queue(self) -> None: self._filtered_messages.clear() @@ -233,10 +238,13 @@ def reset(self, listen_port=None) -> None: else: self.socket = socket.socket(self.family, socket.SOCK_STREAM if self.transport == Transport.TCP else socket.SOCK_DGRAM) - self.socket.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT, 1 if self.reuse_port else 0) - self.socket.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 if self.reuse_port else 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, + 1 if self.reuse_port else 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, + 1 if self.reuse_port else 0) + if self.family == socket.AF_INET6 and hasattr(socket, 'IPV6_V6ONLY'): + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, + 1 if self.ipv6_only else 0) self.socket.bind(('', listen_port)) self.accepted_connection = False @@ -320,14 +328,13 @@ def security_mode(self): class TlsServer(Server): - def __init__(self, psk_identity=None, psk_key=None, ca_path=None, ca_file=None, - crt_file=None, key_file=None, listen_port=0, debug=False, use_ipv6=False, - reuse_port=False, connection_id='', ciphersuites=None, transport=Transport.TCP): + def __init__(self, psk_identity=None, psk_key=None, ca_path=None, ca_file=None, crt_file=None, + key_file=None, listen_port=0, debug=False, use_ipv6=False, reuse_port=False, + connection_id='', ciphersuites=None, transport=Transport.TCP): use_psk = (psk_identity and psk_key) use_certs = any((ca_path, ca_file, crt_file, key_file)) if use_psk and use_certs: - raise ValueError( - "Cannot use PSK and Certificates at the same time") + raise ValueError("Cannot use PSK and Certificates at the same time") try: from pymbedtls import PskSecurity, CertSecurity, Context @@ -342,8 +349,7 @@ def __init__(self, psk_identity=None, psk_key=None, ca_path=None, ca_file=None, elif use_certs: security = CertSecurity(ca_path, ca_file, crt_file, key_file) else: - raise ValueError( - "Neither PSK nor Certificates were configured for use with DTLS") + raise ValueError("Neither PSK nor Certificates were configured for use with DTLS") if ciphersuites is not None: security.set_ciphersuites(ciphersuites) @@ -354,8 +360,7 @@ def __init__(self, psk_identity=None, psk_key=None, ca_path=None, ca_file=None, super().__init__(listen_port, use_ipv6, reuse_port=reuse_port, transport=transport) def connect_to_client(self, remote_addr: Tuple[str, int]) -> None: - raise NotImplementedError( - 'connect_to_client() not supported for DTLS servers') + raise NotImplementedError('connect_to_client() not supported for DTLS servers') @property def _raw_udp_socket(self) -> None: diff --git a/tests/integration/framework/lwm2m/tlv.py b/tests/integration/framework/lwm2m/tlv.py index 217de799..86740e56 100644 --- a/tests/integration/framework/lwm2m/tlv.py +++ b/tests/integration/framework/lwm2m/tlv.py @@ -10,7 +10,7 @@ import struct import typing from textwrap import indent -from ..test_utils import Objlink +from framework.test_utils import Objlink class TLVType: diff --git a/tests/integration/framework/nsh-lwm2m/nsh_lwm2m.py b/tests/integration/framework/nsh-lwm2m/nsh_lwm2m.py index f180b767..d5ab1a40 100755 --- a/tests/integration/framework/nsh-lwm2m/nsh_lwm2m.py +++ b/tests/integration/framework/nsh-lwm2m/nsh_lwm2m.py @@ -13,8 +13,8 @@ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -sys.path = [os.path.join(SCRIPT_DIR, 'powercmd'), SCRIPT_DIR, - os.path.dirname(SCRIPT_DIR)] + sys.path +sys.path = [os.path.join(SCRIPT_DIR, 'powercmd'), SCRIPT_DIR, os.path.dirname(SCRIPT_DIR), + os.path.dirname(os.path.dirname(SCRIPT_DIR))] + sys.path from lwm2m.coap.server import SecurityMode from lwm2m.server import Lwm2mServer diff --git a/tools/anjay_config_log_tool.py b/tools/anjay_config_log_tool.py index 732a8b0f..159723ba 100755 --- a/tools/anjay_config_log_tool.py +++ b/tools/anjay_config_log_tool.py @@ -14,15 +14,16 @@ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -ConfigFileVariableType = enum.Enum('ConfigFileVariableType', 'VALUE FLAG') +ConfigFileVariableType = enum.Enum('ConfigFileVariableType', 'FLAG VALUE OPT_VALUE') def default_config_files(project_root=PROJECT_ROOT): - return [os.path.join(project_root, 'deps', 'avs_commons', 'include_public', - 'avsystem', 'commons', 'avs_commons_config.h.in'), - os.path.join(project_root, 'deps', 'avs_coap', 'include_public', - 'avsystem', 'coap', 'avs_coap_config.h.in'), - os.path.join(project_root, 'include_public', 'anjay', 'anjay_config.h.in')] + return [ + os.path.join(project_root, 'deps', 'avs_commons', 'include_public', 'avsystem', 'commons', + 'avs_commons_config.h.in'), + os.path.join(project_root, 'deps', 'avs_coap', 'include_public', 'avsystem', 'coap', + 'avs_coap_config.h.in'), + os.path.join(project_root, 'include_public', 'anjay', 'anjay_config.h.in')] def default_config_log_file(project_root=PROJECT_ROOT): @@ -45,12 +46,19 @@ def emit(config_file, name, value): with open(config_file) as f: for line in f: stripped = line.strip() - match = re.match(r'#[ \t]*define[ \t]+([A-Za-z_0-9]+)[ \t]+@([A-Za-z_0-9]+)@', + match = re.match(r'#[ \t]*define[ \t]+([A-Za-z_0-9]+)[ \t]+[^@]*@([A-Za-z_0-9]+)@', stripped) if match: emit(config_file, match.group(1), ConfigFileVariableType.VALUE) continue + match = re.match( + r'#[ \t]*cmakedefine[ \t]+([A-Za-z_0-9]+)[ \t]+[^@]*@([A-Za-z_0-9]+)@', + stripped) + if match: + emit(config_file, match.group(1), ConfigFileVariableType.OPT_VALUE) + continue + match = re.match(r'#[ \t]*cmakedefine[ \t]+([A-Za-z_0-9]+)', stripped) if match: emit(config_file, match.group(1), ConfigFileVariableType.FLAG) @@ -66,12 +74,19 @@ def emit(config_file, name, value): def _generate_body(variables): lines = [] for name, type in sorted(variables.items()): - if type == ConfigFileVariableType.VALUE: + if type == ConfigFileVariableType.FLAG: + lines.append('#ifdef ' + name) + lines.append(' _anjay_log(anjay, TRACE, "%s = ON");' % (name,)) + lines.append('#else // ' + name) + lines.append(' _anjay_log(anjay, TRACE, "%s = OFF");' % (name,)) + lines.append('#endif // ' + name) + elif type == ConfigFileVariableType.VALUE: lines.append( ' _anjay_log(anjay, TRACE, "%s = " AVS_QUOTE_MACRO(%s));' % (name, name)) - else: + elif type == ConfigFileVariableType.OPT_VALUE: lines.append('#ifdef ' + name) - lines.append(' _anjay_log(anjay, TRACE, "%s = ON");' % (name,)) + lines.append( + ' _anjay_log(anjay, TRACE, "%s = " AVS_QUOTE_MACRO(%s));' % (name, name)) lines.append('#else // ' + name) lines.append(' _anjay_log(anjay, TRACE, "%s = OFF");' % (name,)) lines.append('#endif // ' + name) @@ -126,9 +141,8 @@ def _main(): formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('command', choices=['validate', 'update', 'list_flags']) parser.add_argument('-c', '--config-files', - help='%r-separated list of *.h.in files to enumerate variables from' - % (os.pathsep,), - default=default_config_files()) + help='%r-separated list of *.h.in files to enumerate variables from' % ( + os.pathsep,), default=default_config_files()) parser.add_argument('-l', '--config-log-file', help='anjay_config_log.h file to operate on', default=default_config_log_file()) args = parser.parse_args()