From 8c623071964e977ebec68e47656607505f5e0afe Mon Sep 17 00:00:00 2001 From: Pete Skeggs Date: Fri, 17 Mar 2023 10:59:58 -0700 Subject: [PATCH] net: lib: nrf_cloud: Add new CoAP library Uses the Zephyr coap_client library. Sends and receives data with nRF Cloud using CBOR and sometimes JSON. Supports most nRF Cloud services - FOTA, data messaging, location services (cellular, Wi-Fi, A-GPS, P-GPS). Signed-off-by: Pete Skeggs --- include/net/nrf_cloud.h | 23 +- include/net/nrf_cloud_coap.h | 254 ++++++++ subsys/net/lib/CMakeLists.txt | 1 + subsys/net/lib/nrf_cloud/CMakeLists.txt | 18 + subsys/net/lib/nrf_cloud/Kconfig | 4 +- .../net/lib/nrf_cloud/Kconfig.nrf_cloud_coap | 56 ++ .../net/lib/nrf_cloud/coap/cbor_encode.cmake | 17 + .../coap/cddl/nrf_cloud_coap_agps.cddl | 39 ++ .../coap/cddl/nrf_cloud_coap_device_msg.cddl | 37 ++ .../coap/cddl/nrf_cloud_coap_ground_fix.cddl | 86 +++ .../coap/cddl/nrf_cloud_coap_pgps.cddl | 32 + .../lib/nrf_cloud/coap/include/agps_encode.h | 34 ++ .../coap/include/agps_encode_types.h | 79 +++ .../lib/nrf_cloud/coap/include/coap_codec.h | 65 ++ .../coap/include/ground_fix_decode.h | 34 ++ .../coap/include/ground_fix_decode_types.h | 49 ++ .../coap/include/ground_fix_encode.h | 34 ++ .../coap/include/ground_fix_encode_types.h | 169 ++++++ .../lib/nrf_cloud/coap/include/msg_encode.h | 34 ++ .../nrf_cloud/coap/include/msg_encode_types.h | 81 +++ .../coap/include/nrf_cloud_coap_transport.h | 170 ++++++ .../lib/nrf_cloud/coap/include/nrfc_dtls.h | 57 ++ .../lib/nrf_cloud/coap/include/pgps_decode.h | 34 ++ .../coap/include/pgps_decode_types.h | 40 ++ .../lib/nrf_cloud/coap/include/pgps_encode.h | 34 ++ .../coap/include/pgps_encode_types.h | 41 ++ .../net/lib/nrf_cloud/coap/src/agps_encode.c | 187 ++++++ .../net/lib/nrf_cloud/coap/src/coap_codec.c | 562 ++++++++++++++++++ .../nrf_cloud/coap/src/ground_fix_decode.c | 80 +++ .../nrf_cloud/coap/src/ground_fix_encode.c | 461 ++++++++++++++ .../net/lib/nrf_cloud/coap/src/msg_encode.c | 175 ++++++ .../lib/nrf_cloud/coap/src/nrf_cloud_coap.c | 442 ++++++++++++++ .../coap/src/nrf_cloud_coap_transport.c | 531 +++++++++++++++++ subsys/net/lib/nrf_cloud/coap/src/nrfc_dtls.c | 267 +++++++++ .../net/lib/nrf_cloud/coap/src/pgps_decode.c | 62 ++ .../net/lib/nrf_cloud/coap/src/pgps_encode.c | 67 +++ .../net/lib/nrf_cloud/coap/update_codec.bat | 12 + subsys/net/lib/nrf_cloud/coap/update_codec.sh | 107 ++++ .../include/nrf_cloud_codec_internal.h | 23 + .../lib/nrf_cloud/src/nrf_cloud_client_id.c | 4 +- .../nrf_cloud/src/nrf_cloud_codec_internal.c | 132 +++- subsys/net/lib/nrf_cloud/src/nrf_cloud_jwt.c | 4 + subsys/net/lib/nrf_cloud/src/nrf_cloud_rest.c | 79 +-- 43 files changed, 4638 insertions(+), 79 deletions(-) create mode 100644 include/net/nrf_cloud_coap.h create mode 100644 subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_coap create mode 100644 subsys/net/lib/nrf_cloud/coap/cbor_encode.cmake create mode 100644 subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_agps.cddl create mode 100644 subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_device_msg.cddl create mode 100644 subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_ground_fix.cddl create mode 100644 subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_pgps.cddl create mode 100644 subsys/net/lib/nrf_cloud/coap/include/agps_encode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/agps_encode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/coap_codec.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/msg_encode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/msg_encode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/nrf_cloud_coap_transport.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/nrfc_dtls.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/pgps_decode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/pgps_decode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/pgps_encode.h create mode 100644 subsys/net/lib/nrf_cloud/coap/include/pgps_encode_types.h create mode 100644 subsys/net/lib/nrf_cloud/coap/src/agps_encode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/coap_codec.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/ground_fix_decode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/ground_fix_encode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/msg_encode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap_transport.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/nrfc_dtls.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/pgps_decode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/src/pgps_encode.c create mode 100644 subsys/net/lib/nrf_cloud/coap/update_codec.bat create mode 100644 subsys/net/lib/nrf_cloud/coap/update_codec.sh diff --git a/include/net/nrf_cloud.h b/include/net/nrf_cloud.h index 115628d9e03..5e6ef445c1f 100644 --- a/include/net/nrf_cloud.h +++ b/include/net/nrf_cloud.h @@ -246,6 +246,20 @@ enum nrf_cloud_sensor { NRF_CLOUD_SENSOR_LIGHT, }; +/** @brief Data types for nrf_cloud_sensor_data. */ +enum nrf_cloud_data_type { + /** The struct nrf_cloud_data structure points to the data and indicates its length. + * The contents of the block are identified with the enum nrf_cloud_sensor type. + */ + NRF_CLOUD_DATA_TYPE_BLOCK, + /** The const char *str_val field points to a NULL-terminated string. */ + NRF_CLOUD_DATA_TYPE_STR, + /** The double double_val field contains the data. */ + NRF_CLOUD_DATA_TYPE_DOUBLE, + /** The int int_val field contains the data. */ + NRF_CLOUD_DATA_TYPE_INT +}; + /** @brief Topic types supported by nRF Cloud. */ enum nrf_cloud_topic_type { /** Endpoint used to update the cloud-side device shadow state . */ @@ -373,8 +387,15 @@ struct nrf_cloud_topic { struct nrf_cloud_sensor_data { /** The sensor that is the source of the data. */ enum nrf_cloud_sensor type; + /** The data type to encode. */ + enum nrf_cloud_data_type data_type; /** Sensor data to be transmitted. */ - struct nrf_cloud_data data; + union { + struct nrf_cloud_data data; + const char *str_val; + double double_val; + int int_val; + }; /** Unique tag to identify the sent data. Can be used to match * acknowledgment on the NRF_CLOUD_EVT_SENSOR_DATA_ACK event. * Valid range: NCT_MSG_ID_USER_TAG_BEGIN to NCT_MSG_ID_USER_TAG_END. diff --git a/include/net/nrf_cloud_coap.h b/include/net/nrf_cloud_coap.h new file mode 100644 index 00000000000..6b4c4459038 --- /dev/null +++ b/include/net/nrf_cloud_coap.h @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef NRF_CLOUD_COAP_H_ +#define NRF_CLOUD_COAP_H_ + +/** @file nrf_cloud_coap.h + * @brief Module to provide nRF Cloud CoAP API + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/** + * @defgroup nrf_cloud_coap nRF CoAP API + * @{ + */ + +/* Transport functions */ +/** @brief Initialize nRF Cloud CoAP library. + * + * @return 0 if initialization was successful, otherwise, a negative error number. + */ +int nrf_cloud_coap_init(void); + +/** + * @brief Connect to and obtain authorization to access the nRF Cloud CoAP server. + * + * This function must return 0 indicating success so that the other functions below, + * other than nrf_cloud_coap_disconnect(), will not immediately return an error when called. + * + * @return 0 if authorized successfully, otherwise, a negative error number. + */ +int nrf_cloud_coap_connect(void); + +/** + * @brief Pause CoAP connection. + * + * This function temporarily pauses the nRF Cloud CoAP connection so that + * another DTLS socket can be opened and used. Once the new socket is no longer needed, + * close that one, then use nrf_cloud_coap_resume() to resume using CoAP. + * Do not call nrf_cloud_coap_disconnect() nor shut down the LTE connection, + * or else the requisite data for the socket will be discarded in the modem, + * and the connection cannot be resumed. In that case, call nrf_cloud_coap_connect(), + * which results in a full DTLS handshake. + * + * @retval -EACCES if DTLS CID was not active or the connection was not authenticated. + * @retval -EAGAIN if an error occurred while saving the connection; it is still usable. + * @retval -EINVAL if the operation could not be performed. + * @retval -ENOMEM if too many connections are already saved (four). + * @retval 0 If successful. + */ +int nrf_cloud_coap_pause(void); + +/** + * @brief Resume CoAP connection. + * + * This function restores a previous connection for use. + * + * @retval -EACCES if the connection was not previously paused. + * @retval -EAGAIN if an error occurred while loading the connection. + * @retval -EINVAL if the operation could not be performed. + * @retval 0 If successful. + */ +int nrf_cloud_coap_resume(void); + +/** + * @brief Disconnect the nRF Cloud CoAP connection. + * + * This does not teardown the thread in coap_client, as there is no way to do so. + * The thread's call to poll(sock) will fail, resulting in an error message. + * This is expected. Call nrf_cloud_coap_connect() to re-establish the connection, and + * the thread in coap_client will resume. + * + * @return 0 if the socket was closed successfully, or a negative error number. + */ +int nrf_cloud_coap_disconnect(void); + +/* nRF Cloud service functions */ + +/** + * @brief Request nRF Cloud CoAP Assisted GPS (A-GPS) data. + * + * @param[in] request Data to be provided in API call. + * @param[in,out] result Structure pointing to caller-provided buffer in which to store A-GPS data. + * + * @retval -EINVAL will be returned, and an error message printed, if invalid parameters + * are given. + * @retval -ENOENT will be returned if there was no A-GPS data requested for the specified + * request type. + * @retval -ENOBUFS will be returned, and an error message printed, if there is not enough + * buffer space to store retrieved AGPS data. + * @retval 0 If successful. + */ +int nrf_cloud_coap_agps_data_get(struct nrf_cloud_rest_agps_request const *const request, + struct nrf_cloud_rest_agps_result *result); + +/** + * @brief Request URL for nRF Cloud Predicted GPS (P-GPS) data. + * + * After a successful call to this function, pass the file_location to + * nrf_cloud_pgps_update(), which then downloads and processes the file's binary P-GPS data. + * + * @param[in] request Data to be provided in API call. + * @param[in,out] file_location Structure that will contain the host and path to + * the prediction file. + * + * @retval 0 If successful. + * Otherwise, a (negative) error code is returned. + */ +int nrf_cloud_coap_pgps_url_get(struct nrf_cloud_rest_pgps_request const *const request, + struct nrf_cloud_pgps_result *file_location); + +/** + * @brief Send a sensor value to nRF Cloud. + * + * The CoAP message is sent as a non-confirmable CoAP message. + * + * @param[in] app_id The app_id identifying the type of data. See the values in nrf_cloud_defs.h + * that begin with NRF_CLOUD_JSON_APPID_. You may also use custom names. + * @param[in] value Sensor reading. + * @param[in] ts_ms Timestamp the data was measured, or NRF_CLOUD_NO_TIMESTAMP. + * + * @retval 0 If successful. + * Otherwise, a (negative) error code is returned. + */ +int nrf_cloud_coap_sensor_send(const char *app_id, double value, int64_t ts_ms); + +/** + * @brief Send the device location in the @ref nrf_cloud_gnss_data PVT field to nRF Cloud. + * + * The CoAP message is sent as a non-confirmable CoAP message. Only + * @ref NRF_CLOUD_GNSS_TYPE_PVT is supported. + * + * @param[in] gnss A pointer to an @ref nrf_cloud_gnss_data struct indicating the device + * location, usually as determined by the GNSS unit. + * + * @retval 0 If successful. + * Otherwise, a (negative) error code is returned. + */ +int nrf_cloud_coap_location_send(const struct nrf_cloud_gnss_data * const gnss); + +/** + * @brief Request device location from nRF Cloud. + * + * At least one of cell_info or wifi_info must be provided within the request. + * + * @param[in] request Data to be provided in API call. + * @param[in,out] result Location information. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_location_get(struct nrf_cloud_rest_location_request const *const request, + struct nrf_cloud_location_result *const result); + +/** + * @brief Request current nRF Cloud FOTA job info for the specified device. + * + * @param[out] job Parsed job info. If no job exists, type will + * be set to invalid. If a job exists, user must call + * @ref nrf_cloud_rest_fota_job_free to free the memory + * allocated by this function. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_fota_job_get(struct nrf_cloud_fota_job_info *const job); + +/** + * @brief Free memory allocated by nrf_cloud_coap_current_fota_job_get(). + * + * @param[in,out] job Job info to be freed. + * + */ +void nrf_cloud_coap_fota_job_free(struct nrf_cloud_fota_job_info *const job); + +/** + * @brief Update the status of the specified nRF Cloud FOTA job. + * + * @param[in] job_id Null-terminated FOTA job identifier. + * @param[in] status Status of the FOTA job. + * @param[in] details Null-terminated string containing details of the + * job, such as an error description. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_fota_job_update(const char *const job_id, + const enum nrf_cloud_fota_status status, const char * const details); + +/** + * @brief Query the device's shadow delta. + * + * If there is no delta, the return value will be 0 and the length of the string stored + * in buf will be 0. + * + * @param[in,out] buf Pointer to memory in which to receive the delta. + * @param[in] buf_len Size of buffer. + * @param[in] delta True to request only changes in the shadow, if any; otherwise, + * all of desired part. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_shadow_get(char *buf, size_t buf_len, bool delta); + +/** + * @brief Update the device's "state" in the shadow via the UpdateDeviceState endpoint. + * + * @param[in] shadow_json Null-terminated JSON string to be written to the device's shadow. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_shadow_state_update(const char * const shadow_json); + +/** + * @brief Update the device status in the shadow. + * + * @param[in] dev_status Device status to be encoded. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_shadow_device_status_update(const struct nrf_cloud_device_status + *const dev_status); + +/** + * @brief Update the device's "serviceInfo" in the shadow. + * + * @param[in] svc_inf Service info items to be updated in the shadow. + * + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_shadow_service_info_update(const struct nrf_cloud_svc_info * const svc_inf); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* NRF_CLOUD_COAP_H_ */ diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 5f9d07a035b..3bc5f15124f 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -10,6 +10,7 @@ if (DEFINED CONFIG_NRF_CLOUD_MQTT OR DEFINED CONFIG_NRF_CLOUD_PGPS OR DEFINED CONFIG_NRF_CLOUD_REST OR DEFINED CONFIG_NRF_CLOUD_FOTA OR + DEFINED CONFIG_NRF_CLOUD_COAP OR DEFINED CONFIG_NRF_CLOUD_LOCATION) add_subdirectory(nrf_cloud) endif() diff --git a/subsys/net/lib/nrf_cloud/CMakeLists.txt b/subsys/net/lib/nrf_cloud/CMakeLists.txt index 0e574fe2ad8..29bdfe88889 100644 --- a/subsys/net/lib/nrf_cloud/CMakeLists.txt +++ b/subsys/net/lib/nrf_cloud/CMakeLists.txt @@ -46,4 +46,22 @@ zephyr_library_sources_ifdef( zephyr_library_sources_ifdef( CONFIG_NRF_CLOUD_REST src/nrf_cloud_rest.c) +zephyr_compile_definitions_ifdef( + CONFIG_NRF_CLOUD_COAP + nrf_cloud_coap CDDL_CBOR_CANONICAL) +zephyr_library_sources_ifdef( + CONFIG_NRF_CLOUD_COAP + coap/src/agps_encode.c + coap/src/nrf_cloud_coap_transport.c + coap/src/coap_codec.c + coap/src/nrfc_dtls.c + coap/src/ground_fix_encode.c + coap/src/ground_fix_decode.c + coap/src/msg_encode.c + coap/src/nrf_cloud_coap.c + coap/src/pgps_decode.c + coap/src/pgps_encode.c) +zephyr_include_directories_ifdef( + CONFIG_NRF_CLOUD_COAP + coap/include) zephyr_include_directories(./include) diff --git a/subsys/net/lib/nrf_cloud/Kconfig b/subsys/net/lib/nrf_cloud/Kconfig index 4101980a7e2..79b8c2f30dc 100644 --- a/subsys/net/lib/nrf_cloud/Kconfig +++ b/subsys/net/lib/nrf_cloud/Kconfig @@ -23,13 +23,15 @@ rsource "Kconfig.nrf_cloud_alert" rsource "Kconfig.nrf_cloud_log" +rsource "Kconfig.nrf_cloud_coap" + config NRF_CLOUD_GATEWAY bool "nRF Cloud Gateway" help Enables functionality in this device to be compatible with nRF Cloud LTE gateway support. -if NRF_CLOUD_MQTT || NRF_CLOUD_REST || NRF_CLOUD_PGPS || MODEM_JWT +if NRF_CLOUD_MQTT || NRF_CLOUD_REST || NRF_CLOUD_PGPS || MODEM_JWT || NRF_CLOUD_COAP config NRF_CLOUD_HOST_NAME string "nRF Cloud server hostname" diff --git a/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_coap b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_coap new file mode 100644 index 00000000000..33cbf4fdab5 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_coap @@ -0,0 +1,56 @@ +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig NRF_CLOUD_COAP + bool "nRF Cloud COAP" + select CJSON_LIB + select ZCBOR + select COAP + select COAP_EXTENDED_OPTIONS_LEN + select COAP_CLIENT + select EXPERIMENTAL + +if NRF_CLOUD_COAP + +config NRF_CLOUD_COAP_SERVER_HOSTNAME + string "CoAP server hostname" + default "coap.dev.nrfcloud.com" + +config NRF_CLOUD_COAP_SEC_TAG + int "Security tag for credentials" + default NRF_CLOUD_SEC_TAG + +config NRF_CLOUD_COAP_SERVER_PORT + int "CoAP server port" + default 5684 + +config NON_RESP_RETRIES + int "Number of times to retry waiting for a response to a NON request" + default 0 + help + Non confirmable requests are usually used to send data to the cloud + for which it is OK if it is sometimes lost. A value of 0 means it is + acceptable if no response is received from the cloud. Non-zero values + result in up to this number of retransmissions of the request followed + by waits for a response. + +if WIFI + +config NRF_CLOUD_COAP_SEND_SSIDS + bool "Include access point SSIDs with scan results" + default n + help + The nRF Cloud Wi-Fi location service does not require the human- + readable SSIDs in order to determine the device location. It does + require the access point MAC addresses. By not including SSIDs + the amount of data sent to the cloud is greatly reduced. + +endif + +module = NRF_CLOUD_COAP +module-str = nRF Cloud COAP +source "subsys/logging/Kconfig.template.log_config" + +endif # NRF_CLOUD_COAP diff --git a/subsys/net/lib/nrf_cloud/coap/cbor_encode.cmake b/subsys/net/lib/nrf_cloud/coap/cbor_encode.cmake new file mode 100644 index 00000000000..000f66b13f7 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/cbor_encode.cmake @@ -0,0 +1,17 @@ +# +# Generated using zcbor version 0.6.0 +# https://github.com/NordicSemiconductor/zcbor +# + +add_library(cbor_encode) +find_package(Python3) +target_sources(cbor_encode PRIVATE + ${Python3_LIBRARY}/zcbor/src/zcbor_decode.c + ${Python3_LIBRARY}/zcbor/src/zcbor_encode.c + ${Python3_LIBRARY}/zcbor/src/zcbor_common.c + src/cbor_encode.c + ) +target_include_directories(cbor_encode PUBLIC + src + ${Python3_LIBRARY}/zcbor/include + ) diff --git a/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_agps.cddl b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_agps.cddl new file mode 100644 index 00000000000..0505fe0f5bc --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_agps.cddl @@ -0,0 +1,39 @@ +; +; Copyright (c) 2023 Nordic Semiconductor ASA +; +; SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +; + +; ************ +; AGPS Request +; ************ + +type = rtAssistance/custom + +agps_req = { + ? types => [1*10 int], + eci => uint, + ? filtered => bool, + ? mask => uint, + mcc => uint, + mnc => uint, + ? requestType => type, + ? rsrp => int, + tac => uint +} + +types = 1 +eci = 2 +filtered = 3 +mask = 4 +mcc = 5 +mnc = 6 +requestType = 7 +rsrp = 8 +tac = 9 +rtAssistance = 10 +custom = 11 + +; *********************** +; AGPS Response is binary +; *********************** diff --git a/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_device_msg.cddl b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_device_msg.cddl new file mode 100644 index 00000000000..7730c168614 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_device_msg.cddl @@ -0,0 +1,37 @@ +; +; Copyright (c) 2023 Nordic Semiconductor ASA +; +; SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +; + +; ************ +; Send Message +; ************ + + +; TODO: add DEVICE msg for networkInfo, simInfo, deviceInfo, serviceInfo? + +pvt = { + lat => float .size 8, + lng => float .size 8, + acc => float, + ? spd => float, + ? hdg => float, + ? alt => float +} + +message_out = { + appId => tstr, + data => tstr/float/int/pvt, + ? ts => uint .size 8 +} + +appId = 1 +data = 2 +ts = 3 +lat = 4 +lng = 5 +acc = 6 +spd = 7 +hdg = 8 +alt = 9 diff --git a/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_ground_fix.cddl b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_ground_fix.cddl new file mode 100644 index 00000000000..3d80db9aae4 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_ground_fix.cddl @@ -0,0 +1,86 @@ +; +; Copyright (c) 2023 Nordic Semiconductor ASA +; +; SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +; + +; ****************** +; Ground Fix Request +; ****************** + +ncell = { + earfcn => uint, + pci => uint, + ? rsrp => int, + ? rsrq => float .size 4 / int, + ? timeDiff => int +} + +cell = { + mcc => uint, + mnc => uint, + eci => uint, + tac => uint, + ? earfcn => uint, + ? adv => uint, + ? nmr => [0*5 ncells: ncell], + ? rsrp => int, + ? rsrq => float .size 4 / int +} + +lte_ar = [1*5 cell] + +ap = { + macAddress => bstr, + ? age => uint, + ? signalStrength => int, + ? channel => uint, + ? frequency => uint, + ? ssid => tstr +} + +wifi_ob = { + accessPoints => [2*20 ap] +} + +ground_fix_req = { + ? lte => lte_ar, + ? wifi => wifi_ob +} + +earfcn = 1 +pci = 2 +rsrp = 3 +rsrq = 4 +timeDiff = 5 +mcc = 6 +mnc = 7 +eci = 8 +tac = 9 +adv = 10 +nmr = 11 +macAddress = 12 +age = 13 +signalStrength = 14 +channel = 15 +frequency = 16 +ssid = 17 +accessPoints = 18 +lte = 19 +wifi = 20 + +; ******************* +; Ground Fix Response +; ******************* + +ground_fix_resp = { + lat => float .size 8, + lon => float .size 8, + uncertainty => int/float, + fulfilledWith => tstr +} + +lat = 1 +lon = 2 +uncertainty = 3 +fulfilledWith = 4 diff --git a/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_pgps.cddl b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_pgps.cddl new file mode 100644 index 00000000000..cb5302b3e1f --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/cddl/nrf_cloud_coap_pgps.cddl @@ -0,0 +1,32 @@ +; +; Copyright (c) 2023 Nordic Semiconductor ASA +; +; SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +; +; ************ +; PGPS Request +; ************ + +pgps_req = { + predictionCount => uint, + predictionIntervalMinutes => uint, + startGPSDay => uint, + startGPSTimeOfDaySeconds => uint +} + +predictionCount = 1 +predictionIntervalMinutes = 2 +startGPSDay = 3 +startGPSTimeOfDaySeconds = 4 + +; ************* +; PGPS Response +; ************* + +pgps_resp = { + host => tstr, + path => tstr +} + +host = 1 +path = 2 diff --git a/subsys/net/lib/nrf_cloud/coap/include/agps_encode.h b/subsys/net/lib/nrf_cloud/coap/include/agps_encode.h new file mode 100644 index 00000000000..dfc4560e291 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/agps_encode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef AGPS_ENCODE_H__ +#define AGPS_ENCODE_H__ + +#include +#include +#include +#include +#include "agps_encode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_encode_agps_req(uint8_t *payload, size_t payload_len, const struct agps_req *input, + size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* AGPS_ENCODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/agps_encode_types.h b/subsys/net/lib/nrf_cloud/coap/include/agps_encode_types.h new file mode 100644 index 00000000000..90525441a48 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/agps_encode_types.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef AGPS_ENCODE_TYPES_H__ +#define AGPS_ENCODE_TYPES_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct agps_req_types_ { + int32_t _agps_req_types_int[10]; + size_t _agps_req_types_int_count; +}; + +struct agps_req_filtered { + bool _agps_req_filtered; +}; + +struct agps_req_mask { + uint32_t _agps_req_mask; +}; + +struct type_ { + enum { + _type__rtAssistance = 10, + _type__custom = 11, + } _type_choice; +}; + +struct agps_req_requestType { + struct type_ _agps_req_requestType; +}; + +struct agps_req_rsrp { + int32_t _agps_req_rsrp; +}; + +struct agps_req { + struct agps_req_types_ _agps_req_types; + bool _agps_req_types_present; + uint32_t _agps_req_eci; + struct agps_req_filtered _agps_req_filtered; + bool _agps_req_filtered_present; + struct agps_req_mask _agps_req_mask; + bool _agps_req_mask_present; + uint32_t _agps_req_mcc; + uint32_t _agps_req_mnc; + struct agps_req_requestType _agps_req_requestType; + bool _agps_req_requestType_present; + struct agps_req_rsrp _agps_req_rsrp; + bool _agps_req_rsrp_present; + uint32_t _agps_req_tac; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* AGPS_ENCODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/coap_codec.h b/subsys/net/lib/nrf_cloud/coap/include/coap_codec.h new file mode 100644 index 00000000000..abd52fee66e --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/coap_codec.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef COAP_CODEC_H_ +#define COAP_CODEC_H_ + +#include +#include +#include +#include +#include + +#define RC_FMT "%u.%02u" +#define RC_PARAM(rc) (rc) / 32u, (rc) & 0x1f + +#define LOG_CB_DBG(result_code, offset, len, last_block) { \ + LOG_DBG("result_code:" RC_FMT ", offset:0x%X, len:0x%X, last_block:%d", \ + RC_PARAM(result_code), (offset), (len), (last_block)); \ + } +#define LOG_RESULT_CODE_ERR(msg, result_code) { \ + LOG_ERR("%s " RC_FMT, (msg), RC_PARAM(result_code)); \ +} +#define LOG_RESULT_CODE_INF(msg, result_code) { \ + LOG_INF("%s " RC_FMT, (msg), RC_PARAM(result_code)); \ +} + +int coap_codec_message_encode(const char *app_id, + const char *str_val, double float_val, int int_val, + int64_t ts, uint8_t *buf, size_t *len, enum coap_content_format fmt); + +int coap_codec_sensor_encode(const char *app_id, double float_val, + int64_t ts, uint8_t *buf, size_t *len, + enum coap_content_format fmt); + +int coap_codec_pvt_encode(const char *app_id, const struct nrf_cloud_gnss_pvt *pvt, + int64_t ts, uint8_t *buf, size_t *len, enum coap_content_format fmt); + +int coap_codec_ground_fix_req_encode(struct lte_lc_cells_info const *const cell_info, + struct wifi_scan_info const *const wifi_info, + uint8_t *buf, size_t *len, enum coap_content_format fmt); + +int coap_codec_ground_fix_resp_decode(struct nrf_cloud_location_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt); + +int coap_codec_agps_encode(struct nrf_cloud_rest_agps_request const *const request, + uint8_t *buf, size_t *len, + enum coap_content_format fm); + +int coap_codec_agps_resp_decode(struct nrf_cloud_rest_agps_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt); + +int coap_codec_pgps_encode(struct nrf_cloud_rest_pgps_request const *const request, + uint8_t *buf, size_t *len, + enum coap_content_format fmt); + +int coap_codec_pgps_resp_decode(struct nrf_cloud_pgps_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt); + +int coap_codec_fota_resp_decode(struct nrf_cloud_fota_job_info *job, + const uint8_t *buf, size_t len, enum coap_content_format fmt); + +#endif /* COAP_CODEC_H_ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode.h b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode.h new file mode 100644 index 00000000000..e3f6b968f16 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef GROUND_FIX_DECODE_H__ +#define GROUND_FIX_DECODE_H__ + +#include +#include +#include +#include +#include "ground_fix_decode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_decode_ground_fix_resp(const uint8_t *payload, size_t payload_len, + struct ground_fix_resp *result, size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* GROUND_FIX_DECODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode_types.h b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode_types.h new file mode 100644 index 00000000000..85a7b6f2832 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_decode_types.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef GROUND_FIX_DECODE_TYPES_H__ +#define GROUND_FIX_DECODE_TYPES_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct ground_fix_resp { + double _ground_fix_resp_lat; + double _ground_fix_resp_lon; + union { + int32_t _ground_fix_resp_uncertainty_int; + double _ground_fix_resp_uncertainty_float; + }; + enum { + _ground_fix_resp_uncertainty_int, + _ground_fix_resp_uncertainty_float, + } _ground_fix_resp_uncertainty_choice; + struct zcbor_string _ground_fix_resp_fulfilledWith; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* GROUND_FIX_DECODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode.h b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode.h new file mode 100644 index 00000000000..a2bc1177ae7 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef GROUND_FIX_ENCODE_H__ +#define GROUND_FIX_ENCODE_H__ + +#include +#include +#include +#include +#include "ground_fix_encode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_encode_ground_fix_req(uint8_t *payload, size_t payload_len, + const struct ground_fix_req *input, size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* GROUND_FIX_ENCODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode_types.h b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode_types.h new file mode 100644 index 00000000000..2447f06da2a --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/ground_fix_encode_types.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef GROUND_FIX_ENCODE_TYPES_H__ +#define GROUND_FIX_ENCODE_TYPES_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct cell_earfcn { + uint32_t _cell_earfcn; +}; + +struct cell_adv { + uint32_t _cell_adv; +}; + +struct ncell_rsrp { + int32_t _ncell_rsrp; +}; + +struct ncell_rsrq_ { + union { + float _ncell_rsrq_float32; + int32_t _ncell_rsrq_int; + }; + enum { + _ncell_rsrq_float32, + _ncell_rsrq_int, + } _ncell_rsrq_choice; +}; + +struct ncell_timeDiff { + int32_t _ncell_timeDiff; +}; + +struct ncell { + uint32_t _ncell_earfcn; + uint32_t _ncell_pci; + struct ncell_rsrp _ncell_rsrp; + bool _ncell_rsrp_present; + struct ncell_rsrq_ _ncell_rsrq; + bool _ncell_rsrq_present; + struct ncell_timeDiff _ncell_timeDiff; + bool _ncell_timeDiff_present; +}; + +struct cell_nmr_ { + struct ncell _cell_nmr_ncells[5]; + size_t _cell_nmr_ncells_count; +}; + +struct cell_rsrp { + int32_t _cell_rsrp; +}; + +struct cell_rsrq_ { + union { + float _cell_rsrq_float32; + int32_t _cell_rsrq_int; + }; + enum { + _cell_rsrq_float32, + _cell_rsrq_int, + } _cell_rsrq_choice; +}; + +struct cell { + uint32_t _cell_mcc; + uint32_t _cell_mnc; + uint32_t _cell_eci; + uint32_t _cell_tac; + struct cell_earfcn _cell_earfcn; + bool _cell_earfcn_present; + struct cell_adv _cell_adv; + bool _cell_adv_present; + struct cell_nmr_ _cell_nmr; + bool _cell_nmr_present; + struct cell_rsrp _cell_rsrp; + bool _cell_rsrp_present; + struct cell_rsrq_ _cell_rsrq; + bool _cell_rsrq_present; +}; + +struct lte_ar { + struct cell _lte_ar__cell[5]; + size_t _lte_ar__cell_count; +}; + +struct ground_fix_req_lte { + struct lte_ar _ground_fix_req_lte; +}; + +struct ap_age { + uint32_t _ap_age; +}; + +struct ap_signalStrength { + int32_t _ap_signalStrength; +}; + +struct ap_channel { + uint32_t _ap_channel; +}; + +struct ap_frequency { + uint32_t _ap_frequency; +}; + +struct ap_ssid { + struct zcbor_string _ap_ssid; +}; + +struct ap { + struct zcbor_string _ap_macAddress; + struct ap_age _ap_age; + bool _ap_age_present; + struct ap_signalStrength _ap_signalStrength; + bool _ap_signalStrength_present; + struct ap_channel _ap_channel; + bool _ap_channel_present; + struct ap_frequency _ap_frequency; + bool _ap_frequency_present; + struct ap_ssid _ap_ssid; + bool _ap_ssid_present; +}; + +struct wifi_ob { + struct ap _wifi_ob_accessPoints__ap[20]; + size_t _wifi_ob_accessPoints__ap_count; +}; + +struct ground_fix_req_wifi { + struct wifi_ob _ground_fix_req_wifi; +}; + +struct ground_fix_req { + struct ground_fix_req_lte _ground_fix_req_lte; + bool _ground_fix_req_lte_present; + struct ground_fix_req_wifi _ground_fix_req_wifi; + bool _ground_fix_req_wifi_present; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* GROUND_FIX_ENCODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/msg_encode.h b/subsys/net/lib/nrf_cloud/coap/include/msg_encode.h new file mode 100644 index 00000000000..61dd0e638ab --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/msg_encode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef MSG_ENCODE_H__ +#define MSG_ENCODE_H__ + +#include +#include +#include +#include +#include "msg_encode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_encode_message_out(uint8_t *payload, size_t payload_len, const struct message_out *input, + size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* MSG_ENCODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/msg_encode_types.h b/subsys/net/lib/nrf_cloud/coap/include/msg_encode_types.h new file mode 100644 index 00000000000..6cf6a97b946 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/msg_encode_types.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef MSG_ENCODE_TYPES_H__ +#define MSG_ENCODE_TYPES_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct pvt_spd { + double _pvt_spd; +}; + +struct pvt_hdg { + double _pvt_hdg; +}; + +struct pvt_alt { + double _pvt_alt; +}; + +struct pvt { + double _pvt_lat; + double _pvt_lng; + double _pvt_acc; + struct pvt_spd _pvt_spd; + bool _pvt_spd_present; + struct pvt_hdg _pvt_hdg; + bool _pvt_hdg_present; + struct pvt_alt _pvt_alt; + bool _pvt_alt_present; +}; + +struct message_out_ts { + uint64_t _message_out_ts; +}; + +struct message_out { + struct zcbor_string _message_out_appId; + union { + struct zcbor_string _message_out_data_tstr; + double _message_out_data_float; + int32_t _message_out_data_int; + struct pvt _message_out_data__pvt; + }; + enum { + _message_out_data_tstr, + _message_out_data_float, + _message_out_data_int, + _message_out_data__pvt, + } _message_out_data_choice; + struct message_out_ts _message_out_ts; + bool _message_out_ts_present; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* MSG_ENCODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/nrf_cloud_coap_transport.h b/subsys/net/lib/nrf_cloud/coap/include/nrf_cloud_coap_transport.h new file mode 100644 index 00000000000..a23d36ef204 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/nrf_cloud_coap_transport.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef NRF_CLOUD_COAP_TRANSPORT_H_ +#define NRF_CLOUD_COAP_TRANSPORT_H_ + +/** @file nrf_cloud_coap_transport.h + * @brief Module to provide nRF Cloud CoAP transport functionality + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/** + * @defgroup nrf_cloud_coap_transport nRF CoAP API + * @{ + */ + +/**@brief Check if device is connected and authorized to use nRF Cloud CoAP. + * + * A device is authorized if the JWT it POSTed to the /auth/jwt endpoint is valid + * and matches credentials for a device already provisioned and associated with nRF Cloud. + * + * @retval true Device is allowed to access nRF Cloud services. + * @retval false Device is disallowed from accessing nRF Cloud services. + */ +bool nrf_cloud_coap_is_connected(void); + +/**@brief Perform CoAP GET request. + * + * The function will block until the response or an error have been returned. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the request. + * @param len Length of payload or 0 if none. + * @param fmt_out CoAP content format for the Content-Format message option of the payload. + * @param fmt_in CoAP content format for the Accept message option of the returned payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_get(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt_out, + enum coap_content_format fmt_in, bool reliable, + coap_client_response_cb_t cb, void *user); + +/**@brief Perform CoAP POST request. + * + * The function will block until the response or an error have been returned. Use this + * function to send custom JSON or CBOR messages to nRF Cloud through the + * https://api.nrfcloud.com/v1#tag/Messages/operation/SendDeviceMessage API. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the request. + * @param len Length of payload or 0 if none. + * @param fmt CoAP content format for the Content-Format message option of the payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_post(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user); + +/**@brief Perform CoAP PUT request. + * + * The function will block until the response or an error have been returned. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the request. + * @param len Length of payload or 0 if none. + * @param fmt CoAP content format for the Content-Format message option of the payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_put(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user); + +/**@brief Perform CoAP DELETE request. + * + * The function will block until the response or an error have been returned. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the request. + * @param len Length of payload or 0 if none. + * @param fmt CoAP content format for the Content-Format message option of the payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_delete(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user); + +/**@brief Perform CoAP FETCH request. + * + * The function will block until the response or an error have been returned. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the request. + * @param len Length of payload or 0 if none. + * @param fmt_out CoAP content format for the Content-Format message option of the payload. + * @param fmt_in CoAP content format for the Accept message option of the returned payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_fetch(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt_out, + enum coap_content_format fmt_in, bool reliable, + coap_client_response_cb_t cb, void *user); + +/**@brief Perform CoAP PATCH request. + * + * The function will block until the response or an error have been returned. + * + * @param resource String containing the specific CoAP endpoint to access. + * @param query Optional string containing REST-style query parameters. + * @param buf Optional pointer to buffer containing a payload to include with the GET request. + * @param len Length of payload or 0 if none. + * @param fmt CoAP content format for the Content-Format message option of the payload. + * @param reliable True to use a Confirmable message, otherwise, a Non-confirmable message. + * @param cb Pointer to a callback function to receive the results. + * @param user Pointer to user-specific data to be passed back to the callback. + * @return 0 if the request succeeded, a positive value indicating a CoAP result code, + * or a negative error number. + */ +int nrf_cloud_coap_patch(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* NRF_CLOUD_COAP_TRANSPORT_H_ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/nrfc_dtls.h b/subsys/net/lib/nrf_cloud/coap/include/nrfc_dtls.h new file mode 100644 index 00000000000..a02a0c52685 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/nrfc_dtls.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef NRFC_DTLS_H_ +#define NRFC_DTLS_H_ + +/** @brief Set up DTLS on socket. + * + * @param sock - an open network socket. + * @return 0 if the operation succeeded, otherwise, a negative error code. + */ +int nrfc_dtls_setup(int sock); + +/** @brief Determine if CID is in use. + * + * If connection ID (CID) is available, an open LTE network socket with an active + * or sleeping (PSM, eDRX) LTE connection will be usuable for a long time, + * even if there is no network traffic for long periods. Only one full + * DTLS handshake will be required. Without CID, NAT translation changes + * between the device and the server will occur frequently (~30 seconds), + * resulting in no response from the server to a device's request. + * + * @param sock - the socket previously setup for DTLS using dtls_setup(). + * @retval true - CID is in use. + * @retval false - CID is not in use. + */ +bool nrfc_dtls_cid_is_active(int sock); + +/** @brief Save DTLS CID session. + * + * This function temporarily pauses a DTLS CID connection in the modem so that + * another socket can be opened and used. Once the new socket is no longer needed, + * close that one, then use dtls_session_load() to resume using the specified + * socket, sock. The socket sock must not be closed, and the LTE connection must + * not be shut down, or else the requisite data for the socket will be discarded, + * and the DTLS CID connection cannot be reused. In that case, close the socket, + * reopen it, and set it up again with dtls_setup(), resulting in a full + * DTLS handshake. + * + * @param sock - the socket to save. + * @return 0 if successful, a negative error code otherwise. + */ +int nrfc_dtls_session_save(int sock); + +/** @brief Load DTLS CID session. + * + * This resumes a previously saved session. + * + * @param sock - the socket to load. + * @return 0 if successful, a negative error code otherwise. + */ +int nrfc_dtls_session_load(int sock); + +#endif /* NRFC_DTLS_H_ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/pgps_decode.h b/subsys/net/lib/nrf_cloud/coap/include/pgps_decode.h new file mode 100644 index 00000000000..1aa72f74e09 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/pgps_decode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef PGPS_DECODE_H__ +#define PGPS_DECODE_H__ + +#include +#include +#include +#include +#include "pgps_decode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_decode_pgps_resp(const uint8_t *payload, size_t payload_len, struct pgps_resp *result, + size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* PGPS_DECODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/pgps_decode_types.h b/subsys/net/lib/nrf_cloud/coap/include/pgps_decode_types.h new file mode 100644 index 00000000000..f0c534c86dd --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/pgps_decode_types.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef PGPS_DECODE_TYPES_H__ +#define PGPS_DECODE_TYPES_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct pgps_resp { + struct zcbor_string _pgps_resp_host; + struct zcbor_string _pgps_resp_path; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PGPS_DECODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/pgps_encode.h b/subsys/net/lib/nrf_cloud/coap/include/pgps_encode.h new file mode 100644 index 00000000000..9bf71e6d5c2 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/pgps_encode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef PGPS_ENCODE_H__ +#define PGPS_ENCODE_H__ + +#include +#include +#include +#include +#include "pgps_encode_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +int cbor_encode_pgps_req(uint8_t *payload, size_t payload_len, const struct pgps_req *input, + size_t *payload_len_out); + +#ifdef __cplusplus +} +#endif + +#endif /* PGPS_ENCODE_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/include/pgps_encode_types.h b/subsys/net/lib/nrf_cloud/coap/include/pgps_encode_types.h new file mode 100644 index 00000000000..0bf684bb32e --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/include/pgps_encode_types.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#ifndef PGPS_ENCODE_TYPES_H__ +#define PGPS_ENCODE_TYPES_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Which value for --default-max-qty this file was created with. + * + * The define is used in the other generated file to do a build-time + * compatibility check. + * + * See `zcbor --help` for more information about --default-max-qty + */ +#define DEFAULT_MAX_QTY 10 + +struct pgps_req { + uint32_t _pgps_req_predictionCount; + uint32_t _pgps_req_predictionIntervalMinutes; + uint32_t _pgps_req_startGPSDay; + uint32_t _pgps_req_startGPSTimeOfDaySeconds; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PGPS_ENCODE_TYPES_H__ */ diff --git a/subsys/net/lib/nrf_cloud/coap/src/agps_encode.c b/subsys/net/lib/nrf_cloud/coap/src/agps_encode.c new file mode 100644 index 00000000000..81b679990f6 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/agps_encode.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_encode.h" +#include "agps_encode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool encode_repeated_agps_req_types(zcbor_state_t *state, + const struct agps_req_types_ *input); +static bool encode_repeated_agps_req_filtered(zcbor_state_t *state, + const struct agps_req_filtered *input); +static bool encode_repeated_agps_req_mask(zcbor_state_t *state, const struct agps_req_mask *input); +static bool encode_type(zcbor_state_t *state, const struct type_ *input); +static bool encode_repeated_agps_req_requestType(zcbor_state_t *state, + const struct agps_req_requestType *input); +static bool encode_repeated_agps_req_rsrp(zcbor_state_t *state, const struct agps_req_rsrp *input); +static bool encode_agps_req(zcbor_state_t *state, const struct agps_req *input); + +static bool encode_repeated_agps_req_types(zcbor_state_t *state, + const struct agps_req_types_ *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + ((((zcbor_uint32_put(state, (1)))) && + (zcbor_list_start_encode(state, 10) && + ((zcbor_multi_encode_minmax(1, 10, &(*input)._agps_req_types_int_count, + (zcbor_encoder_t *)zcbor_int32_encode, state, + (&(*input)._agps_req_types_int), sizeof(int32_t))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_list_end_encode(state, 10)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_agps_req_filtered(zcbor_state_t *state, + const struct agps_req_filtered *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (3)))) && + (zcbor_bool_encode(state, (&(*input)._agps_req_filtered))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_agps_req_mask(zcbor_state_t *state, const struct agps_req_mask *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (4)))) && + (zcbor_uint32_encode(state, (&(*input)._agps_req_mask))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_type(zcbor_state_t *state, const struct type_ *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = (((((*input)._type_choice == _type__rtAssistance) + ? ((zcbor_uint32_put(state, (10)))) + : (((*input)._type_choice == _type__custom) + ? ((zcbor_uint32_put(state, (11)))) + : false)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_agps_req_requestType(zcbor_state_t *state, + const struct agps_req_requestType *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (7)))) && + (encode_type(state, (&(*input)._agps_req_requestType))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_agps_req_rsrp(zcbor_state_t *state, const struct agps_req_rsrp *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (8)))) && + (zcbor_int32_encode(state, (&(*input)._agps_req_rsrp))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_agps_req(zcbor_state_t *state, const struct agps_req *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_map_start_encode(state, 9) && + ((zcbor_present_encode(&((*input)._agps_req_types_present), + (zcbor_encoder_t *)encode_repeated_agps_req_types, state, + (&(*input)._agps_req_types)) && + (((zcbor_uint32_put(state, (2)))) && + (zcbor_uint32_encode(state, (&(*input)._agps_req_eci)))) && + zcbor_present_encode(&((*input)._agps_req_filtered_present), + (zcbor_encoder_t *)encode_repeated_agps_req_filtered, + state, (&(*input)._agps_req_filtered)) && + zcbor_present_encode(&((*input)._agps_req_mask_present), + (zcbor_encoder_t *)encode_repeated_agps_req_mask, state, + (&(*input)._agps_req_mask)) && + (((zcbor_uint32_put(state, (5)))) && + (zcbor_uint32_encode(state, (&(*input)._agps_req_mcc)))) && + (((zcbor_uint32_put(state, (6)))) && + (zcbor_uint32_encode(state, (&(*input)._agps_req_mnc)))) && + zcbor_present_encode(&((*input)._agps_req_requestType_present), + (zcbor_encoder_t *)encode_repeated_agps_req_requestType, + state, (&(*input)._agps_req_requestType)) && + zcbor_present_encode(&((*input)._agps_req_rsrp_present), + (zcbor_encoder_t *)encode_repeated_agps_req_rsrp, state, + (&(*input)._agps_req_rsrp)) && + (((zcbor_uint32_put(state, (9)))) && + (zcbor_uint32_encode(state, (&(*input)._agps_req_tac))))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 9)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_encode_agps_req(uint8_t *payload, size_t payload_len, const struct agps_req *input, + size_t *payload_len_out) +{ + zcbor_state_t states[4]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = encode_agps_req(states, input); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/coap_codec.c b/subsys/net/lib/nrf_cloud/coap/src/coap_codec.c new file mode 100644 index 00000000000..113edfc1a37 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/coap_codec.c @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "nrf_cloud_codec_internal.h" +#include "ground_fix_encode_types.h" +#include "ground_fix_encode.h" +#include "ground_fix_decode_types.h" +#include "ground_fix_decode.h" +#include "agps_encode_types.h" +#include "agps_encode.h" +#include "pgps_encode_types.h" +#include "pgps_encode.h" +#include "pgps_decode_types.h" +#include "pgps_decode.h" +#include "msg_encode_types.h" +#include "msg_encode.h" +#include "coap_codec.h" + +#include +LOG_MODULE_REGISTER(coap_codec, CONFIG_NRF_CLOUD_COAP_LOG_LEVEL); + +/* Default elevation angle used by the modem to chose whether to include a given satellite + * in calculating a fix. Satellites whose elevation is below this angle are not included. + */ +#define DEFAULT_MASK_ANGLE 5 + +static int encode_message(const char *app_id, const char *str, + const struct nrf_cloud_gnss_pvt *pvt, + double float_val, int int_val, + int64_t ts, uint8_t *buf, size_t *len, + enum coap_content_format fmt) +{ + int err; + + if (fmt == COAP_CONTENT_FORMAT_APP_CBOR) { + struct message_out input; + size_t out_len; + + memset(&input, 0, sizeof(struct message_out)); + input._message_out_appId.value = app_id; + input._message_out_appId.len = strlen(app_id); + if (str) { + input._message_out_data_choice = _message_out_data_tstr; + input._message_out_data_tstr.value = str; + input._message_out_data_tstr.len = strlen(str); + } else if (pvt) { + input._message_out_data_choice = _message_out_data__pvt; + input._message_out_data__pvt._pvt_lat = pvt->lat; + input._message_out_data__pvt._pvt_lng = pvt->lon; + input._message_out_data__pvt._pvt_acc = pvt->accuracy; + if (pvt->has_speed) { + input._message_out_data__pvt._pvt_spd._pvt_spd = pvt->speed; + input._message_out_data__pvt._pvt_spd_present = true; + } + if (pvt->has_heading) { + input._message_out_data__pvt._pvt_hdg._pvt_hdg = pvt->heading; + input._message_out_data__pvt._pvt_hdg_present = true; + } + if (pvt->has_alt) { + input._message_out_data__pvt._pvt_alt._pvt_alt = pvt->alt; + input._message_out_data__pvt._pvt_alt_present = true; + } + } else if (!isnan(float_val)) { + input._message_out_data_choice = _message_out_data_float; + input._message_out_data_float = float_val; + } else { + input._message_out_data_choice = _message_out_data_int; + input._message_out_data_int = int_val; + } + input._message_out_ts._message_out_ts = ts; + input._message_out_ts_present = true; + err = cbor_encode_message_out(buf, *len, &input, &out_len); + if (err) { + LOG_ERR("Error %d encoding message", err); + err = -EINVAL; + *len = 0; + } else { + *len = out_len; + } + } else if (fmt == COAP_CONTENT_FORMAT_APP_JSON) { + struct nrf_cloud_data out; + + err = nrf_cloud_encode_message(app_id, float_val, str, NULL, ts, &out); + if (err) { + *len = 0; + } else if (*len <= out.len) { + *len = 0; + cJSON_free((void *)out.ptr); + err = -E2BIG; + } else { + *len = out.len; + memcpy(buf, out.ptr, *len); + buf[*len - 1] = '\0'; + cJSON_free((void *)out.ptr); + } + } else { + err = -EINVAL; + } + return err; +} + +int coap_codec_message_encode(const char *app_id, + const char *str_val, double float_val, int int_val, + int64_t ts, uint8_t *buf, size_t *len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(app_id != NULL); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + return encode_message(app_id, str_val, NULL, float_val, int_val, ts, buf, len, fmt); +} + +int coap_codec_sensor_encode(const char *app_id, double float_val, + int64_t ts, uint8_t *buf, size_t *len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(app_id != NULL); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + return encode_message(app_id, NULL, NULL, float_val, 0, ts, buf, len, fmt); +} + +int coap_codec_pvt_encode(const char *app_id, const struct nrf_cloud_gnss_pvt *pvt, + int64_t ts, uint8_t *buf, size_t *len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(app_id != NULL); + __ASSERT_NO_MSG(pvt != NULL); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + return encode_message(app_id, NULL, pvt, 0, 0, ts, buf, len, fmt); +} + +static void copy_cell(struct cell *dst, struct lte_lc_cell const *const src) +{ + dst->_cell_mcc = src->mcc; + dst->_cell_mnc = src->mnc; + dst->_cell_eci = src->id; + dst->_cell_tac = src->tac; + + dst->_cell_earfcn._cell_earfcn = src->earfcn; + dst->_cell_earfcn_present = (src->earfcn != NRF_CLOUD_LOCATION_CELL_OMIT_EARFCN); + + dst->_cell_adv._cell_adv = MIN(src->timing_advance, NRF_CLOUD_LOCATION_CELL_TIME_ADV_MAX); + dst->_cell_adv_present = (src->timing_advance != NRF_CLOUD_LOCATION_CELL_OMIT_TIME_ADV); + + dst->_cell_rsrp._cell_rsrp = RSRP_IDX_TO_DBM(src->rsrp); + dst->_cell_rsrp_present = (src->rsrp != NRF_CLOUD_LOCATION_CELL_OMIT_RSRP); + + dst->_cell_rsrq._cell_rsrq_float32 = RSRQ_IDX_TO_DB(src->rsrq); + dst->_cell_rsrq._cell_rsrq_choice = _cell_rsrq_float32; + dst->_cell_rsrq_present = (src->rsrq != NRF_CLOUD_LOCATION_CELL_OMIT_RSRQ); +} + +static void copy_ncells(struct ncell *dst, int num, struct lte_lc_ncell *src) +{ + for (int i = 0; i < num; i++) { + dst->_ncell_earfcn = src->earfcn; + dst->_ncell_pci = src->phys_cell_id; + if (src->rsrp != NRF_CLOUD_LOCATION_CELL_OMIT_RSRP) { + dst->_ncell_rsrp._ncell_rsrp = RSRP_IDX_TO_DBM(src->rsrp); + dst->_ncell_rsrp_present = true; + } else { + dst->_ncell_rsrp_present = false; + } + if (src->rsrq != NRF_CLOUD_LOCATION_CELL_OMIT_RSRQ) { + dst->_ncell_rsrq._ncell_rsrq_float32 = RSRQ_IDX_TO_DB(src->rsrq); + dst->_ncell_rsrq._ncell_rsrq_choice = _ncell_rsrq_float32; + dst->_ncell_rsrq_present = true; + } else { + dst->_ncell_rsrq_present = false; + } + if (src->time_diff != LTE_LC_CELL_TIME_DIFF_INVALID) { + dst->_ncell_timeDiff._ncell_timeDiff = src->time_diff; + dst->_ncell_timeDiff_present = true; + } else { + dst->_ncell_timeDiff_present = false; + } + src++; + dst++; + } +} + +static void copy_cell_info(struct lte_ar *lte_encode, + struct lte_lc_cells_info const *const cell_info) +{ + if (cell_info != NULL) { + struct cell *cell = lte_encode->_lte_ar__cell; + size_t num_cells = ARRAY_SIZE(lte_encode->_lte_ar__cell); + size_t num_neighbors = 0; + + if (cell_info->current_cell.id != LTE_LC_CELL_EUTRAN_ID_INVALID) { + lte_encode->_lte_ar__cell_count++; + copy_cell(cell, &cell_info->current_cell); + LOG_DBG("Copied serving cell"); + cell->_cell_nmr._cell_nmr_ncells_count = cell_info->ncells_count; + if (cell_info->ncells_count) { + num_neighbors = MIN(ARRAY_SIZE(cell->_cell_nmr._cell_nmr_ncells), + cell_info->ncells_count); + copy_ncells(cell->_cell_nmr._cell_nmr_ncells, + num_neighbors, + cell_info->neighbor_cells); + cell->_cell_nmr._cell_nmr_ncells_count = num_neighbors; + cell->_cell_nmr_present = true; + LOG_DBG("Copied %zd neighbor cells", num_neighbors); + } + cell++; + num_cells--; + } + if (cell_info->gci_cells != NULL) { + LOG_DBG("Copying %u GCI cells", cell_info->gci_cells_count); + for (int i = 0; i < cell_info->gci_cells_count; i++) { + lte_encode->_lte_ar__cell_count++; + copy_cell(cell, &cell_info->gci_cells[i]); + cell++; + num_cells--; + if (!num_cells) { + break; + } + } + } + LOG_DBG("Copied %zd total cells", + lte_encode->_lte_ar__cell_count + cell->_cell_nmr._cell_nmr_ncells_count); + } +} + +static void copy_wifi_info(struct wifi_ob *wifi_encode, + struct wifi_scan_info const *const wifi_info) +{ + struct ap *dst = wifi_encode->_wifi_ob_accessPoints__ap; + struct wifi_scan_result *src = wifi_info->ap_info; + size_t num_aps = MIN(ARRAY_SIZE(wifi_encode->_wifi_ob_accessPoints__ap), wifi_info->cnt); + + wifi_encode->_wifi_ob_accessPoints__ap_count = num_aps; + + for (int i = 0; i < num_aps; i++, src++, dst++) { + dst->_ap_macAddress.value = src->mac; + dst->_ap_macAddress.len = src->mac_length; + dst->_ap_age_present = false; + + dst->_ap_signalStrength._ap_signalStrength = src->rssi; + dst->_ap_signalStrength_present = (src->rssi != NRF_CLOUD_LOCATION_WIFI_OMIT_RSSI); + + dst->_ap_channel._ap_channel = src->channel; + dst->_ap_channel_present = (src->channel != NRF_CLOUD_LOCATION_WIFI_OMIT_CHAN); + + dst->_ap_frequency_present = false; + dst->_ap_ssid_present = (IS_ENABLED(CONFIG_NRF_CLOUD_COAP_SEND_SSIDS) && + (src->ssid_length && src->ssid[0])); + dst->_ap_ssid._ap_ssid.value = src->ssid; + dst->_ap_ssid._ap_ssid.len = src->ssid_length; + } +} + +int coap_codec_ground_fix_req_encode(struct lte_lc_cells_info const *const cell_info, + struct wifi_scan_info const *const wifi_info, + uint8_t *buf, size_t *len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG((cell_info != NULL) || (wifi_info != NULL)); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + if (fmt != COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for ground fix: %d", fmt); + return -ENOTSUP; + } + + int err = 0; + struct ground_fix_req input; + size_t out_len; + + memset(&input, 0, sizeof(struct ground_fix_req)); + input._ground_fix_req_lte_present = (cell_info != NULL); + if (cell_info) { + copy_cell_info(&input._ground_fix_req_lte._ground_fix_req_lte, cell_info); + } + input._ground_fix_req_wifi_present = (wifi_info != NULL); + if (wifi_info) { + copy_wifi_info(&input._ground_fix_req_wifi._ground_fix_req_wifi, wifi_info); + } + err = cbor_encode_ground_fix_req(buf, *len, &input, &out_len); + if (err) { + LOG_ERR("Error %d encoding ground fix", err); + err = -EINVAL; + *len = 0; + } else { + *len = out_len; + } + return err; +} + +int coap_codec_ground_fix_resp_decode(struct nrf_cloud_location_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(result != NULL); + __ASSERT_NO_MSG(buf != NULL); + int err; + + if (fmt == COAP_CONTENT_FORMAT_APP_JSON) { + enum nrf_cloud_error nrf_err; + + /* Check for a potential ground fix JSON error message from nRF Cloud */ + err = nrf_cloud_error_msg_decode(buf, NRF_CLOUD_JSON_APPID_VAL_LOCATION, + NRF_CLOUD_JSON_MSG_TYPE_VAL_DATA, &nrf_err); + if (!err) { + LOG_ERR("nRF Cloud returned ground fix error: %d", nrf_err); + return -EFAULT; + } + LOG_ERR("Invalid ground fix response format: %d", err); + return -EPROTO; + } + if (fmt != COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for ground fix: %d", fmt); + return -ENOTSUP; + } + + struct ground_fix_resp res; + size_t out_len; + + err = cbor_decode_ground_fix_resp(buf, len, &res, &out_len); + if (out_len != len) { + LOG_WRN("Different response length: expected:%zd, decoded:%zd", + len, out_len); + } + if (err) { + return err; + } + + const char *with = res._ground_fix_resp_fulfilledWith.value; + int rlen = res._ground_fix_resp_fulfilledWith.len; + + if (strncmp(with, NRF_CLOUD_LOCATION_TYPE_VAL_MCELL, rlen) == 0) { + result->type = LOCATION_TYPE_MULTI_CELL; + } else if (strncmp(with, NRF_CLOUD_LOCATION_TYPE_VAL_SCELL, rlen) == 0) { + result->type = LOCATION_TYPE_SINGLE_CELL; + } else if (strncmp(with, NRF_CLOUD_LOCATION_TYPE_VAL_WIFI, rlen) == 0) { + result->type = LOCATION_TYPE_WIFI; + } else { + result->type = LOCATION_TYPE__INVALID; + LOG_WRN("Unhandled location type: %*s", rlen, with); + } + + result->lat = res._ground_fix_resp_lat; + result->lon = res._ground_fix_resp_lon; + + if (res._ground_fix_resp_uncertainty_choice == _ground_fix_resp_uncertainty_int) { + result->unc = res._ground_fix_resp_uncertainty_int; + } else { + result->unc = (uint32_t)lround(res._ground_fix_resp_uncertainty_float); + } + return err; +} + +#if defined(CONFIG_NRF_CLOUD_AGPS) +int coap_codec_agps_encode(struct nrf_cloud_rest_agps_request const *const request, + uint8_t *buf, size_t *len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(request != NULL); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + if (fmt != COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for A-GPS: %d", fmt); + return -ENOTSUP; + } + + int ret; + struct agps_req input; + struct agps_req_types_ *t = &input._agps_req_types; + size_t out_len; + enum nrf_cloud_agps_type types[ARRAY_SIZE(t->_agps_req_types_int)]; + int cnt; + + memset(&input, 0, sizeof(struct agps_req)); + input._agps_req_eci = request->net_info->current_cell.id; + input._agps_req_mcc = request->net_info->current_cell.mcc; + input._agps_req_mnc = request->net_info->current_cell.mnc; + input._agps_req_tac = request->net_info->current_cell.tac; + if (request->type == NRF_CLOUD_REST_AGPS_REQ_CUSTOM) { + cnt = nrf_cloud_agps_type_array_get(request->agps_req, types, ARRAY_SIZE(types)); + t->_agps_req_types_int_count = cnt; + input._agps_req_types_present = true; + LOG_DBG("num elements: %d", cnt); + for (int i = 0; i < cnt; i++) { + LOG_DBG(" %d: %d", i, types[i]); + t->_agps_req_types_int[i] = (int)types[i]; + } + } else { + input._agps_req_requestType._agps_req_requestType._type_choice = + _type__rtAssistance; + input._agps_req_requestType_present = true; + } + if (request->filtered) { + input._agps_req_filtered_present = true; + input._agps_req_filtered._agps_req_filtered = true; + if (request->mask_angle != DEFAULT_MASK_ANGLE) { + input._agps_req_mask_present = true; + input._agps_req_mask._agps_req_mask = request->mask_angle; + } + } + if (request->net_info->current_cell.rsrp != NRF_CLOUD_LOCATION_CELL_OMIT_RSRP) { + input._agps_req_rsrp._agps_req_rsrp = request->net_info->current_cell.rsrp; + input._agps_req_rsrp_present = true; + } + ret = cbor_encode_agps_req(buf, *len, &input, &out_len); + if (ret) { + LOG_ERR("Error %d encoding A-GPS req", ret); + ret = -EINVAL; + *len = 0; + } else { + *len = out_len; + } + return ret; +} + +int coap_codec_agps_resp_decode(struct nrf_cloud_rest_agps_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(result != NULL); + __ASSERT_NO_MSG(buf != NULL); + + if (fmt == COAP_CONTENT_FORMAT_APP_JSON) { + enum nrf_cloud_error nrf_err; + int err; + + /* Check for a potential A-GPS JSON error message from nRF Cloud */ + err = nrf_cloud_error_msg_decode(buf, NRF_CLOUD_JSON_APPID_VAL_AGPS, + NRF_CLOUD_JSON_MSG_TYPE_VAL_DATA, &nrf_err); + if (!err) { + LOG_ERR("nRF Cloud returned A-GPS error: %d", nrf_err); + return -EFAULT; + } + LOG_ERR("Invalid A-GPS response format"); + return -EPROTO; + } + if (fmt != COAP_CONTENT_FORMAT_APP_OCTET_STREAM) { + LOG_ERR("Invalid format for A-GPS data: %d", fmt); + return -ENOTSUP; + } + if (!result) { + return -EINVAL; + } + + if (result->buf_sz < len) { + LOG_WRN("A-GPS buffer is too small; expected: %zd, got:%zd; truncated", + len, result->buf_sz); + } + result->agps_sz = MIN(result->buf_sz, len); + memcpy(result->buf, buf, result->agps_sz); + return 0; +} +#endif /* CONFIG_NRF_CLOUD_AGPS */ + +#if defined(CONFIG_NRF_CLOUD_PGPS) +int coap_codec_pgps_encode(struct nrf_cloud_rest_pgps_request const *const request, + uint8_t *buf, size_t *len, + enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(request != NULL); + __ASSERT_NO_MSG(request->pgps_req != NULL); + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(len != NULL); + + if (fmt != COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for P-GPS: %d", fmt); + return -ENOTSUP; + } + + int ret; + struct pgps_req input; + size_t out_len; + + memset(&input, 0, sizeof(input)); + + input._pgps_req_predictionCount = request->pgps_req->prediction_count; + input._pgps_req_predictionIntervalMinutes = request->pgps_req->prediction_period_min; + input._pgps_req_startGPSDay = request->pgps_req->gps_day; + input._pgps_req_startGPSTimeOfDaySeconds = request->pgps_req->gps_time_of_day; + + ret = cbor_encode_pgps_req(buf, *len, &input, &out_len); + if (ret) { + LOG_ERR("Error %d encoding P-GPS req", ret); + ret = -EINVAL; + *len = 0; + } else { + *len = out_len; + } + + return ret; +} + +int coap_codec_pgps_resp_decode(struct nrf_cloud_pgps_result *result, + const uint8_t *buf, size_t len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(result != NULL); + __ASSERT_NO_MSG(buf != NULL); + int err; + + if (fmt == COAP_CONTENT_FORMAT_APP_JSON) { + enum nrf_cloud_error nrf_err; + + /* Check for a potential P-GPS JSON error message from nRF Cloud */ + err = nrf_cloud_error_msg_decode(buf, NRF_CLOUD_JSON_APPID_VAL_PGPS, + NRF_CLOUD_JSON_MSG_TYPE_VAL_DATA, &nrf_err); + if (!err) { + LOG_ERR("nRF Cloud returned P-GPS error: %d", nrf_err); + return -EFAULT; + } + LOG_ERR("Invalid P-GPS response format"); + return -EPROTO; + } + if (fmt != COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for P-GPS: %d", fmt); + return -ENOTSUP; + } + + struct pgps_resp resp; + size_t resp_len; + + err = cbor_decode_pgps_resp(buf, len, &resp, &resp_len); + if (!err) { + strncpy(result->host, resp._pgps_resp_host.value, result->host_sz); + if (result->host_sz > resp._pgps_resp_host.len) { + result->host[resp._pgps_resp_host.len] = '\0'; + } + result->host_sz = resp._pgps_resp_host.len; + strncpy(result->path, resp._pgps_resp_path.value, result->path_sz); + if (result->path_sz > resp._pgps_resp_path.len) { + result->path[resp._pgps_resp_path.len] = '\0'; + } + result->path_sz = resp._pgps_resp_path.len; + } + return err; +} +#endif /* CONFIG_NRF_CLOUD_PGPS */ + +int coap_codec_fota_resp_decode(struct nrf_cloud_fota_job_info *job, + const uint8_t *buf, size_t len, enum coap_content_format fmt) +{ + __ASSERT_NO_MSG(job != NULL); + __ASSERT_NO_MSG(buf != NULL); + + if (fmt == COAP_CONTENT_FORMAT_APP_CBOR) { + LOG_ERR("Invalid format for FOTA: %d", fmt); + return -ENOTSUP; + } else { + return nrf_cloud_rest_fota_execution_decode(buf, job); + } +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/ground_fix_decode.c b/subsys/net/lib/nrf_cloud/coap/src/ground_fix_decode.c new file mode 100644 index 00000000000..4f313a55efd --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/ground_fix_decode.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_decode.h" +#include "ground_fix_decode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool decode_ground_fix_resp(zcbor_state_t *state, struct ground_fix_resp *result); + +static bool decode_ground_fix_resp(zcbor_state_t *state, struct ground_fix_resp *result) +{ + zcbor_print("%s\r\n", __func__); + bool int_res; + + bool tmp_result = (( + (zcbor_map_start_decode(state) && + (((((zcbor_uint32_expect(state, (1)))) && + (zcbor_float64_decode(state, (&(*result)._ground_fix_resp_lat)))) && + (((zcbor_uint32_expect(state, (2)))) && + (zcbor_float64_decode(state, (&(*result)._ground_fix_resp_lon)))) && + (((zcbor_uint32_expect(state, (3)))) && + (zcbor_union_start_code(state) && + (int_res = + ((((zcbor_int32_decode( + state, (&(*result)._ground_fix_resp_uncertainty_int)))) && + (((*result)._ground_fix_resp_uncertainty_choice = + _ground_fix_resp_uncertainty_int), + true)) || + (((zcbor_float_decode( + state, (&(*result)._ground_fix_resp_uncertainty_float)))) && + (((*result)._ground_fix_resp_uncertainty_choice = + _ground_fix_resp_uncertainty_float), + true))), + zcbor_union_end_code(state), int_res))) && + (((zcbor_uint32_expect(state, (4)))) && + (zcbor_tstr_decode(state, (&(*result)._ground_fix_resp_fulfilledWith))))) || + (zcbor_list_map_end_force_decode(state), false)) && + zcbor_map_end_decode(state)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_decode_ground_fix_resp(const uint8_t *payload, size_t payload_len, + struct ground_fix_resp *result, size_t *payload_len_out) +{ + zcbor_state_t states[4]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = decode_ground_fix_resp(states, result); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/ground_fix_encode.c b/subsys/net/lib/nrf_cloud/coap/src/ground_fix_encode.c new file mode 100644 index 00000000000..af7e87abaa0 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/ground_fix_encode.c @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_encode.h" +#include "ground_fix_encode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool encode_repeated_cell_earfcn(zcbor_state_t *state, const struct cell_earfcn *input); +static bool encode_repeated_cell_adv(zcbor_state_t *state, const struct cell_adv *input); +static bool encode_repeated_ncell_rsrp(zcbor_state_t *state, const struct ncell_rsrp *input); +static bool encode_repeated_ncell_rsrq(zcbor_state_t *state, const struct ncell_rsrq_ *input); +static bool encode_repeated_ncell_timeDiff(zcbor_state_t *state, + const struct ncell_timeDiff *input); +static bool encode_ncell(zcbor_state_t *state, const struct ncell *input); +static bool encode_repeated_cell_nmr(zcbor_state_t *state, const struct cell_nmr_ *input); +static bool encode_repeated_cell_rsrp(zcbor_state_t *state, const struct cell_rsrp *input); +static bool encode_repeated_cell_rsrq(zcbor_state_t *state, const struct cell_rsrq_ *input); +static bool encode_cell(zcbor_state_t *state, const struct cell *input); +static bool encode_lte_ar(zcbor_state_t *state, const struct lte_ar *input); +static bool encode_repeated_ground_fix_req_lte(zcbor_state_t *state, + const struct ground_fix_req_lte *input); +static bool encode_repeated_ap_age(zcbor_state_t *state, const struct ap_age *input); +static bool encode_repeated_ap_signalStrength(zcbor_state_t *state, + const struct ap_signalStrength *input); +static bool encode_repeated_ap_channel(zcbor_state_t *state, const struct ap_channel *input); +static bool encode_repeated_ap_frequency(zcbor_state_t *state, const struct ap_frequency *input); +static bool encode_repeated_ap_ssid(zcbor_state_t *state, const struct ap_ssid *input); +static bool encode_ap(zcbor_state_t *state, const struct ap *input); +static bool encode_wifi_ob(zcbor_state_t *state, const struct wifi_ob *input); +static bool encode_repeated_ground_fix_req_wifi(zcbor_state_t *state, + const struct ground_fix_req_wifi *input); +static bool encode_ground_fix_req(zcbor_state_t *state, const struct ground_fix_req *input); + +static bool encode_repeated_cell_earfcn(zcbor_state_t *state, const struct cell_earfcn *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (1)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_earfcn))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_cell_adv(zcbor_state_t *state, const struct cell_adv *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (10)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_adv))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ncell_rsrp(zcbor_state_t *state, const struct ncell_rsrp *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (3)))) && + (zcbor_int32_encode(state, (&(*input)._ncell_rsrp))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ncell_rsrq(zcbor_state_t *state, const struct ncell_rsrq_ *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + ((((zcbor_uint32_put(state, (4)))) && + (((*input)._ncell_rsrq_choice == _ncell_rsrq_float32) + ? ((zcbor_float32_encode(state, (&(*input)._ncell_rsrq_float32)))) + : (((*input)._ncell_rsrq_choice == _ncell_rsrq_int) + ? ((zcbor_int32_encode(state, (&(*input)._ncell_rsrq_int)))) + : false)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ncell_timeDiff(zcbor_state_t *state, const struct ncell_timeDiff *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (5)))) && + (zcbor_int32_encode(state, (&(*input)._ncell_timeDiff))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_ncell(zcbor_state_t *state, const struct ncell *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_map_start_encode(state, 5) && + (((((zcbor_uint32_put(state, (1)))) && + (zcbor_uint32_encode(state, (&(*input)._ncell_earfcn)))) && + (((zcbor_uint32_put(state, (2)))) && + (zcbor_uint32_encode(state, (&(*input)._ncell_pci)))) && + zcbor_present_encode(&((*input)._ncell_rsrp_present), + (zcbor_encoder_t *)encode_repeated_ncell_rsrp, state, + (&(*input)._ncell_rsrp)) && + zcbor_present_encode(&((*input)._ncell_rsrq_present), + (zcbor_encoder_t *)encode_repeated_ncell_rsrq, state, + (&(*input)._ncell_rsrq)) && + zcbor_present_encode(&((*input)._ncell_timeDiff_present), + (zcbor_encoder_t *)encode_repeated_ncell_timeDiff, state, + (&(*input)._ncell_timeDiff))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 5)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_cell_nmr(zcbor_state_t *state, const struct cell_nmr_ *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + ((((zcbor_uint32_put(state, (11)))) && + (zcbor_list_start_encode(state, 5) && + ((zcbor_multi_encode_minmax( + 0, 5, &(*input)._cell_nmr_ncells_count, (zcbor_encoder_t *)encode_ncell, + state, (&(*input)._cell_nmr_ncells), sizeof(struct ncell))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_list_end_encode(state, 5)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_cell_rsrp(zcbor_state_t *state, const struct cell_rsrp *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (3)))) && + (zcbor_int32_encode(state, (&(*input)._cell_rsrp))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_cell_rsrq(zcbor_state_t *state, const struct cell_rsrq_ *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + ((((zcbor_uint32_put(state, (4)))) && + (((*input)._cell_rsrq_choice == _cell_rsrq_float32) + ? ((zcbor_float32_encode(state, (&(*input)._cell_rsrq_float32)))) + : (((*input)._cell_rsrq_choice == _cell_rsrq_int) + ? ((zcbor_int32_encode(state, (&(*input)._cell_rsrq_int)))) + : false)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_cell(zcbor_state_t *state, const struct cell *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = (((zcbor_map_start_encode(state, 9) && + (((((zcbor_uint32_put(state, (6)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_mcc)))) && + (((zcbor_uint32_put(state, (7)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_mnc)))) && + (((zcbor_uint32_put(state, (8)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_eci)))) && + (((zcbor_uint32_put(state, (9)))) && + (zcbor_uint32_encode(state, (&(*input)._cell_tac)))) && + zcbor_present_encode(&((*input)._cell_earfcn_present), + (zcbor_encoder_t *)encode_repeated_cell_earfcn, + state, (&(*input)._cell_earfcn)) && + zcbor_present_encode(&((*input)._cell_adv_present), + (zcbor_encoder_t *)encode_repeated_cell_adv, + state, (&(*input)._cell_adv)) && + zcbor_present_encode(&((*input)._cell_nmr_present), + (zcbor_encoder_t *)encode_repeated_cell_nmr, + state, (&(*input)._cell_nmr)) && + zcbor_present_encode(&((*input)._cell_rsrp_present), + (zcbor_encoder_t *)encode_repeated_cell_rsrp, + state, (&(*input)._cell_rsrp)) && + zcbor_present_encode(&((*input)._cell_rsrq_present), + (zcbor_encoder_t *)encode_repeated_cell_rsrq, + state, (&(*input)._cell_rsrq))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 9)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_lte_ar(zcbor_state_t *state, const struct lte_ar *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_list_start_encode(state, 5) && + ((zcbor_multi_encode_minmax(1, 5, &(*input)._lte_ar__cell_count, + (zcbor_encoder_t *)encode_cell, state, + (&(*input)._lte_ar__cell), sizeof(struct cell))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_list_end_encode(state, 5)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ground_fix_req_lte(zcbor_state_t *state, + const struct ground_fix_req_lte *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (19)))) && + (encode_lte_ar(state, (&(*input)._ground_fix_req_lte))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ap_age(zcbor_state_t *state, const struct ap_age *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (13)))) && + (zcbor_uint32_encode(state, (&(*input)._ap_age))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ap_signalStrength(zcbor_state_t *state, + const struct ap_signalStrength *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (14)))) && + (zcbor_int32_encode(state, (&(*input)._ap_signalStrength))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ap_channel(zcbor_state_t *state, const struct ap_channel *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (15)))) && + (zcbor_uint32_encode(state, (&(*input)._ap_channel))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ap_frequency(zcbor_state_t *state, const struct ap_frequency *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (16)))) && + (zcbor_uint32_encode(state, (&(*input)._ap_frequency))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ap_ssid(zcbor_state_t *state, const struct ap_ssid *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (17)))) && + (zcbor_tstr_encode(state, (&(*input)._ap_ssid))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_ap(zcbor_state_t *state, const struct ap *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_map_start_encode(state, 6) && + (((((zcbor_uint32_put(state, (12)))) && + (zcbor_bstr_encode(state, (&(*input)._ap_macAddress)))) && + zcbor_present_encode(&((*input)._ap_age_present), + (zcbor_encoder_t *)encode_repeated_ap_age, state, + (&(*input)._ap_age)) && + zcbor_present_encode(&((*input)._ap_signalStrength_present), + (zcbor_encoder_t *)encode_repeated_ap_signalStrength, + state, (&(*input)._ap_signalStrength)) && + zcbor_present_encode(&((*input)._ap_channel_present), + (zcbor_encoder_t *)encode_repeated_ap_channel, state, + (&(*input)._ap_channel)) && + zcbor_present_encode(&((*input)._ap_frequency_present), + (zcbor_encoder_t *)encode_repeated_ap_frequency, state, + (&(*input)._ap_frequency)) && + zcbor_present_encode(&((*input)._ap_ssid_present), + (zcbor_encoder_t *)encode_repeated_ap_ssid, state, + (&(*input)._ap_ssid))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 6)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_wifi_ob(zcbor_state_t *state, const struct wifi_ob *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_map_start_encode(state, 1) && + (((((zcbor_uint32_put(state, (18)))) && + (zcbor_list_start_encode(state, 20) && + ((zcbor_multi_encode_minmax(2, 20, &(*input)._wifi_ob_accessPoints__ap_count, + (zcbor_encoder_t *)encode_ap, state, + (&(*input)._wifi_ob_accessPoints__ap), + sizeof(struct ap))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_list_end_encode(state, 20)))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 1)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_ground_fix_req_wifi(zcbor_state_t *state, + const struct ground_fix_req_wifi *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (20)))) && + (encode_wifi_ob(state, (&(*input)._ground_fix_req_wifi))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_ground_fix_req(zcbor_state_t *state, const struct ground_fix_req *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = + (((zcbor_map_start_encode(state, 2) && + ((zcbor_present_encode(&((*input)._ground_fix_req_lte_present), + (zcbor_encoder_t *)encode_repeated_ground_fix_req_lte, + state, (&(*input)._ground_fix_req_lte)) && + zcbor_present_encode(&((*input)._ground_fix_req_wifi_present), + (zcbor_encoder_t *)encode_repeated_ground_fix_req_wifi, + state, (&(*input)._ground_fix_req_wifi))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 2)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_encode_ground_fix_req(uint8_t *payload, size_t payload_len, + const struct ground_fix_req *input, size_t *payload_len_out) +{ + zcbor_state_t states[8]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = encode_ground_fix_req(states, input); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/msg_encode.c b/subsys/net/lib/nrf_cloud/coap/src/msg_encode.c new file mode 100644 index 00000000000..7ccf3929660 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/msg_encode.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_encode.h" +#include "msg_encode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool encode_repeated_pvt_spd(zcbor_state_t *state, const struct pvt_spd *input); +static bool encode_repeated_pvt_hdg(zcbor_state_t *state, const struct pvt_hdg *input); +static bool encode_repeated_pvt_alt(zcbor_state_t *state, const struct pvt_alt *input); +static bool encode_pvt(zcbor_state_t *state, const struct pvt *input); +static bool encode_repeated_message_out_ts(zcbor_state_t *state, + const struct message_out_ts *input); +static bool encode_message_out(zcbor_state_t *state, const struct message_out *input); + +static bool encode_repeated_pvt_spd(zcbor_state_t *state, const struct pvt_spd *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (7)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_spd))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_pvt_hdg(zcbor_state_t *state, const struct pvt_hdg *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (8)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_hdg))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_pvt_alt(zcbor_state_t *state, const struct pvt_alt *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (9)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_alt))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_pvt(zcbor_state_t *state, const struct pvt *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = (((zcbor_map_start_encode(state, 6) && + (((((zcbor_uint32_put(state, (4)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_lat)))) && + (((zcbor_uint32_put(state, (5)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_lng)))) && + (((zcbor_uint32_put(state, (6)))) && + (zcbor_float64_encode(state, (&(*input)._pvt_acc)))) && + zcbor_present_encode(&((*input)._pvt_spd_present), + (zcbor_encoder_t *)encode_repeated_pvt_spd, + state, (&(*input)._pvt_spd)) && + zcbor_present_encode(&((*input)._pvt_hdg_present), + (zcbor_encoder_t *)encode_repeated_pvt_hdg, + state, (&(*input)._pvt_hdg)) && + zcbor_present_encode(&((*input)._pvt_alt_present), + (zcbor_encoder_t *)encode_repeated_pvt_alt, + state, (&(*input)._pvt_alt))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 6)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_repeated_message_out_ts(zcbor_state_t *state, const struct message_out_ts *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((((zcbor_uint32_put(state, (3)))) && + ((((*input)._message_out_ts <= UINT64_MAX)) || + (zcbor_error(state, ZCBOR_ERR_WRONG_RANGE), false)) && + (zcbor_uint64_encode(state, (&(*input)._message_out_ts))))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +static bool encode_message_out(zcbor_state_t *state, const struct message_out *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((( + zcbor_map_start_encode(state, 3) && + (((((zcbor_uint32_put(state, (1)))) && + (zcbor_tstr_encode(state, (&(*input)._message_out_appId)))) && + (((zcbor_uint32_put(state, (2)))) && + (((*input)._message_out_data_choice == _message_out_data_tstr) + ? ((zcbor_tstr_encode(state, (&(*input)._message_out_data_tstr)))) + : (((*input)._message_out_data_choice == _message_out_data_float) + ? ((zcbor_float64_encode( + state, (&(*input)._message_out_data_float)))) + : (((*input)._message_out_data_choice == + _message_out_data_int) + ? ((zcbor_int32_encode( + state, + (&(*input)._message_out_data_int)))) + : (((*input)._message_out_data_choice == + _message_out_data__pvt) + ? ((encode_pvt( + state, + (&(*input)._message_out_data__pvt)))) + : false))))) && + zcbor_present_encode(&((*input)._message_out_ts_present), + (zcbor_encoder_t *)encode_repeated_message_out_ts, state, + (&(*input)._message_out_ts))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 3)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_encode_message_out(uint8_t *payload, size_t payload_len, const struct message_out *input, + size_t *payload_len_out) +{ + zcbor_state_t states[5]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = encode_message_out(states, input); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap.c b/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap.c new file mode 100644 index 00000000000..abcd3c05323 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nrf_cloud_coap_transport.h" +#include "nrf_cloud_codec_internal.h" +#include "nrf_cloud_mem.h" +#include "coap_codec.h" + +#include +LOG_MODULE_REGISTER(nrf_cloud_coap, CONFIG_NRF_CLOUD_COAP_LOG_LEVEL); + +#define MAX_COAP_PAYLOAD_SIZE (CONFIG_COAP_CLIENT_BLOCK_SIZE - \ + CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE) + +static int64_t get_ts(void) +{ + int64_t ts; + int err; + + err = date_time_now(&ts); + if (err) { + LOG_ERR("Error getting time: %d", err); + ts = 0; + } + return ts; +} + +#if defined(CONFIG_NRF_CLOUD_AGPS) +static int agps_err; + +static void get_agps_callback(int16_t result_code, + size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user_data) +{ + struct nrf_cloud_rest_agps_result *result = user_data; + + if (!result) { + LOG_ERR("Cannot process result"); + agps_err = -EINVAL; + return; + } + if (result_code != COAP_RESPONSE_CODE_CONTENT) { + agps_err = result_code; + return; + } + if (((offset + len) <= result->buf_sz) && result->buf && payload) { + memcpy(&result->buf[offset], payload, len); + result->agps_sz += len; + } else { + agps_err = -EOVERFLOW; + return; + } + if (last_block) { + agps_err = 0; + } +} + +int nrf_cloud_coap_agps_data_get(struct nrf_cloud_rest_agps_request const *const request, + struct nrf_cloud_rest_agps_result *result) +{ + __ASSERT_NO_MSG(request != NULL); + __ASSERT_NO_MSG(result != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + static uint8_t buffer[64]; + size_t len = sizeof(buffer); + int err; + + err = coap_codec_agps_encode(request, buffer, &len, + COAP_CONTENT_FORMAT_APP_CBOR); + if (err) { + LOG_ERR("Unable to encode A-GPS request: %d", err); + return err; + } + + result->agps_sz = 0; + err = nrf_cloud_coap_fetch("loc/agps", NULL, + buffer, len, COAP_CONTENT_FORMAT_APP_CBOR, + COAP_CONTENT_FORMAT_APP_CBOR, true, get_agps_callback, + result); + if (!err && !agps_err) { + LOG_INF("Got A-GPS data"); + } else if (err == -EAGAIN) { + LOG_ERR("Timeout waiting for A-GPS data"); + } else { + LOG_ERR("Error getting A-GPS; agps_err:%d, err:%d", agps_err, err); + err = agps_err; + } + + return err; +} +#endif /* CONFIG_NRF_CLOUD_AGPS */ + +#if defined(CONFIG_NRF_CLOUD_PGPS) +static int pgps_err; + +static void get_pgps_callback(int16_t result_code, + size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user) +{ + if (result_code != COAP_RESPONSE_CODE_CONTENT) { + pgps_err = result_code; + } else { + pgps_err = coap_codec_pgps_resp_decode(user, payload, len, + COAP_CONTENT_FORMAT_APP_CBOR); + } +} + +int nrf_cloud_coap_pgps_url_get(struct nrf_cloud_rest_pgps_request const *const request, + struct nrf_cloud_pgps_result *file_location) +{ + __ASSERT_NO_MSG(request != NULL); + __ASSERT_NO_MSG(file_location != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + static uint8_t buffer[64]; + size_t len = sizeof(buffer); + int err; + + err = coap_codec_pgps_encode(request, buffer, &len, + COAP_CONTENT_FORMAT_APP_CBOR); + if (err) { + LOG_ERR("Unable to encode P-GPS request: %d", err); + return err; + } + + err = nrf_cloud_coap_fetch("loc/pgps", NULL, + buffer, len, COAP_CONTENT_FORMAT_APP_CBOR, + COAP_CONTENT_FORMAT_APP_CBOR, true, get_pgps_callback, + file_location); + if (!err && !pgps_err) { + LOG_INF("Got P-GPS file location"); + } else if (err == -EAGAIN) { + LOG_ERR("Timeout waiting for P-GPS file location"); + } else { + LOG_ERR("Error getting P-GPS file location; pgps_err:%d, err:%d", pgps_err, err); + err = pgps_err; + } + + return err; +} +#endif /* CONFIG_NRF_CLOUD_PGPS */ + +int nrf_cloud_coap_sensor_send(const char *app_id, double value, int64_t ts_ms) +{ + __ASSERT_NO_MSG(app_id != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + int64_t ts = (ts_ms == NRF_CLOUD_NO_TIMESTAMP) ? get_ts() : ts_ms; + static uint8_t buffer[32]; + size_t len = sizeof(buffer); + int err; + + err = coap_codec_sensor_encode(app_id, value, ts, buffer, &len, + COAP_CONTENT_FORMAT_APP_CBOR); + if (err) { + LOG_ERR("Unable to encode sensor data: %d", err); + return err; + } + err = nrf_cloud_coap_post("msg/d2c", NULL, buffer, len, + COAP_CONTENT_FORMAT_APP_CBOR, false, NULL, NULL); + if (err) { + LOG_ERR("Failed to send POST request: %d", err); + } + return err; +} + +int nrf_cloud_coap_location_send(const struct nrf_cloud_gnss_data *gnss) +{ + __ASSERT_NO_MSG(gnss != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + int64_t ts = (gnss->ts_ms == NRF_CLOUD_NO_TIMESTAMP) ? get_ts() : gnss->ts_ms; + static uint8_t buffer[64]; + size_t len = sizeof(buffer); + int err; + + if (gnss->type != NRF_CLOUD_GNSS_TYPE_PVT) { + LOG_ERR("Only PVT format is supported"); + return -ENOTSUP; + } + err = coap_codec_pvt_encode("GNSS", &gnss->pvt, ts, buffer, &len, + COAP_CONTENT_FORMAT_APP_CBOR); + if (err) { + LOG_ERR("Unable to encode GNSS PVT data: %d", err); + return err; + } + err = nrf_cloud_coap_post("msg/d2c", NULL, buffer, len, + COAP_CONTENT_FORMAT_APP_CBOR, false, NULL, NULL); + if (err) { + LOG_ERR("Failed to send POST request: %d", err); + } + return err; +} + +static int loc_err; + +static void get_location_callback(int16_t result_code, + size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user) +{ + if (result_code != COAP_RESPONSE_CODE_CONTENT) { + loc_err = result_code; + } else { + loc_err = coap_codec_ground_fix_resp_decode(user, payload, len, + COAP_CONTENT_FORMAT_APP_CBOR); + } +} + +int nrf_cloud_coap_location_get(struct nrf_cloud_rest_location_request const *const request, + struct nrf_cloud_location_result *const result) +{ + __ASSERT_NO_MSG(request != NULL); + __ASSERT_NO_MSG((request->cell_info != NULL) || (request->wifi_info != NULL)); + __ASSERT_NO_MSG(result != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + static uint8_t buffer[256]; + size_t len = sizeof(buffer); + int err; + + err = coap_codec_ground_fix_req_encode(request->cell_info, request->wifi_info, buffer, &len, + COAP_CONTENT_FORMAT_APP_CBOR); + if (err) { + LOG_ERR("Unable to encode cell pos data: %d", err); + return err; + } + err = nrf_cloud_coap_fetch("loc/ground-fix", NULL, buffer, len, + COAP_CONTENT_FORMAT_APP_CBOR, + COAP_CONTENT_FORMAT_APP_CBOR, true, + get_location_callback, result); + + if (!err && !loc_err) { + LOG_DBG("Location: %d, %.12g, %.12g, %d", result->type, + result->lat, result->lon, result->unc); + } else if (err == -EAGAIN) { + LOG_ERR("Timeout waiting for location"); + } else { + LOG_ERR("Error getting location; loc_err:%d, err:%d", loc_err, err); + err = loc_err; + } + + return err; +} + +static int fota_err; + +static void get_fota_callback(int16_t result_code, + size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user) +{ + if (result_code != COAP_RESPONSE_CODE_CONTENT) { + fota_err = result_code; + } else if (payload && len) { + LOG_INF("Got FOTA response: %.*s", len, (const char *)payload); + fota_err = coap_codec_fota_resp_decode(user, payload, len, + COAP_CONTENT_FORMAT_APP_JSON); + } else { + fota_err = -ENOMSG; + } +} + +int nrf_cloud_coap_fota_job_get(struct nrf_cloud_fota_job_info *const job) +{ + __ASSERT_NO_MSG(job != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + int err; + + job->type = NRF_CLOUD_FOTA_TYPE__INVALID; + + err = nrf_cloud_coap_get("fota/exec/current", NULL, NULL, 0, + COAP_CONTENT_FORMAT_APP_CBOR, + COAP_CONTENT_FORMAT_APP_JSON, true, get_fota_callback, job); + + if (!err && !fota_err) { + LOG_INF("FOTA job received; type:%d, id:%s, host:%s, path:%s, size:%d", + job->type, job->id, job->host, job->path, job->file_size); + } else if (!err && (fota_err == COAP_RESPONSE_CODE_NOT_FOUND)) { + LOG_INF("No pending FOTA job"); + err = -ENOMSG; + } else if (err == -EAGAIN) { + LOG_ERR("Timeout waiting for FOTA job"); + } else if (err < 0) { + LOG_ERR("Error getting current FOTA job:%d", err); + } else { + LOG_RESULT_CODE_ERR("Unexpected CoAP response code getting current FOTA job; rc:", + fota_err); + err = fota_err; + } + return err; +} + +void nrf_cloud_coap_fota_job_free(struct nrf_cloud_fota_job_info *const job) +{ + nrf_cloud_fota_job_free(job); +} + +int nrf_cloud_coap_fota_job_update(const char *const job_id, + const enum nrf_cloud_fota_status status, const char * const details) +{ + __ASSERT_NO_MSG(job_id != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + int ret; + struct nrf_cloud_fota_job_update update; + + ret = nrf_cloud_fota_job_update_create(NULL, job_id, status, details, &update); + if (ret) { + LOG_ERR("Error creating FOTA job update structure: %d", ret); + return ret; + } + ret = nrf_cloud_coap_patch(update.url, NULL, update.payload, strlen(update.payload), + COAP_CONTENT_FORMAT_APP_JSON, true, NULL, NULL); + + nrf_cloud_fota_job_update_free(&update); + + return ret; +} + +struct get_shadow_data { + char *buf; + size_t buf_len; +} get_shadow_data; +static int shadow_err; + +static void get_shadow_callback(int16_t result_code, + size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user) +{ + struct get_shadow_data *data = (struct get_shadow_data *)user; + + if (result_code != COAP_RESPONSE_CODE_CONTENT) { + shadow_err = result_code; + } else { + int cpy_len = MIN(data->buf_len - 1, len); + + if (cpy_len < len) { + LOG_WRN("Shadow truncated from %zd to %zd characters.", + len, cpy_len); + } + shadow_err = 0; + if (cpy_len) { + memcpy(data->buf, payload, cpy_len); + } + data->buf[cpy_len] = '\0'; + } +} + +int nrf_cloud_coap_shadow_get(char *buf, size_t buf_len, bool delta) +{ + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(buf_len != 0); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + get_shadow_data.buf = buf; + get_shadow_data.buf_len = buf_len; + + return nrf_cloud_coap_get("state", delta ? NULL : "delta=false", NULL, 0, + 0, COAP_CONTENT_FORMAT_APP_JSON, true, get_shadow_callback, + &get_shadow_data); +} + +int nrf_cloud_coap_shadow_state_update(const char * const shadow_json) +{ + __ASSERT_NO_MSG(shadow_json != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + return nrf_cloud_coap_patch("state", NULL, (uint8_t *)shadow_json, + strlen(shadow_json), + COAP_CONTENT_FORMAT_APP_JSON, true, NULL, NULL); +} + +int nrf_cloud_coap_shadow_device_status_update(const struct nrf_cloud_device_status + *const dev_status) +{ + __ASSERT_NO_MSG(dev_status != NULL); + if (!nrf_cloud_coap_is_connected()) { + return -EACCES; + } + + int ret; + struct nrf_cloud_data data_out; + + ret = nrf_cloud_shadow_dev_status_encode(dev_status, &data_out, false, false); + if (ret) { + LOG_ERR("Failed to encode device status, error: %d", ret); + return ret; + } + + ret = nrf_cloud_coap_shadow_state_update(data_out.ptr); + if (ret) { + LOG_ERR("Failed to update device shadow, error: %d", ret); + } + + nrf_cloud_device_status_free(&data_out); + + return ret; +} + +int nrf_cloud_coap_shadow_service_info_update(const struct nrf_cloud_svc_info * const svc_inf) +{ + if (svc_inf == NULL) { + return -EINVAL; + } + + const struct nrf_cloud_device_status dev_status = { + .modem = NULL, + .svc = (struct nrf_cloud_svc_info *)svc_inf + }; + + return nrf_cloud_coap_shadow_device_status_update(&dev_status); +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap_transport.c b/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap_transport.c new file mode 100644 index 00000000000..55863719d70 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/nrf_cloud_coap_transport.c @@ -0,0 +1,531 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#if defined(CONFIG_POSIX_API) +#include +#include +#include +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nrf_cloud_codec_internal.h" +#include "nrfc_dtls.h" +#include "coap_codec.h" +#include "nrf_cloud_coap_transport.h" + +#include +LOG_MODULE_REGISTER(nrf_cloud_coap_transport, CONFIG_NRF_CLOUD_COAP_LOG_LEVEL); + +/** @TODO: figure out whether to make this a Kconfig value or place in a header */ +#define CDDL_VERSION "1" +#define MAX_COAP_PATH 256 +#define MAX_RETRIES 10 +#define JWT_BUF_SZ 700 +#define VER_STRING_FMT "mver=%s&cver=%s&dver=%s" +#define VER_STRING_FMT2 "cver=" CDDL_VERSION "&dver=" BUILD_VERSION_STR +#define BUILD_VERSION_STR STRINGIFY(BUILD_VERSION) + +static int sock = -1; +static bool authenticated; +static bool cid_saved; + +static struct coap_client coap_client; +static int nrf_cloud_coap_authenticate(void); + +#if defined(CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG) +static const char *const coap_method_str[] = { + NULL, /* 0 */ + "GET", /* COAP_METHOD_GET = 1 */ + "POST", /* COAP_METHOD_POST = 2 */ + "PUT", /* COAP_METHOD_PUT = 3 */ + "DELETE", /* COAP_METHOD_DELETE = 4 */ + "FETCH", /* COAP_METHOD_FETCH = 5 */ + "PATCH", /* COAP_METHOD_PATCH = 6 */ + "IPATCH", /* COAP_METHOD_IPATCH = 7 */ + NULL +}; + +#define METHOD_NAME(method) \ + (((method >= COAP_METHOD_GET) && (method <= COAP_METHOD_IPATCH)) ?\ + coap_method_str[method] : "N/A") + +struct format { + enum coap_content_format fmt; + const char *name; +}; + +const struct format formats[] = { + {COAP_CONTENT_FORMAT_TEXT_PLAIN, "PLAIN TEXT"}, + {COAP_CONTENT_FORMAT_APP_LINK_FORMAT, "APP LINK"}, + {COAP_CONTENT_FORMAT_APP_XML, "XML"}, + {COAP_CONTENT_FORMAT_APP_OCTET_STREAM, "OCTET STRM"}, + {COAP_CONTENT_FORMAT_APP_EXI, "EXI"}, + {COAP_CONTENT_FORMAT_APP_JSON, "JSON"}, + {COAP_CONTENT_FORMAT_APP_JSON_PATCH_JSON, "JSON PATCH"}, + {COAP_CONTENT_FORMAT_APP_MERGE_PATCH_JSON, "JSON MRG PATCH"}, + {COAP_CONTENT_FORMAT_APP_CBOR, "CBOR"} +}; + +static const char *fmt_name(enum coap_content_format fmt) +{ + for (int i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].fmt == fmt) { + return formats[i].name; + } + } + return "N/A"; +} +#endif /* CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG */ + +bool nrf_cloud_coap_is_connected(void) +{ + if (!authenticated) { + LOG_DBG("Not connected and authenticated"); + } + return authenticated; +} + +/**@brief Resolves the configured hostname. */ +static int server_resolve(struct sockaddr_in *server4) +{ + int err; + struct addrinfo *result; + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_DGRAM + }; + char ipv4_addr[NET_IPV4_ADDR_LEN]; + + LOG_DBG("Looking up server %s", CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME); + err = getaddrinfo(CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME, NULL, &hints, &result); + if (err != 0) { + LOG_ERR("ERROR: getaddrinfo for %s failed: %d, errno:%d", + CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME, err, -errno); + return -EIO; + } + + if (result == NULL) { + LOG_ERR("ERROR: Address not found"); + return -ENOENT; + } + + /* IPv4 Address. */ + server4->sin_addr.s_addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr.s_addr; + server4->sin_family = AF_INET; + server4->sin_port = htons(CONFIG_NRF_CLOUD_COAP_SERVER_PORT); + + inet_ntop(AF_INET, &server4->sin_addr.s_addr, ipv4_addr, + sizeof(ipv4_addr)); + LOG_DBG("Server %s IP address: %s, port: %u", + CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME, ipv4_addr, + CONFIG_NRF_CLOUD_COAP_SERVER_PORT); + + /* Free the address. */ + freeaddrinfo(result); + + return 0; +} + +/**@brief Initialize the CoAP client */ +int nrf_cloud_coap_init(void) +{ + static bool initialized; + int err; + + authenticated = false; + + if (!initialized) { + /* Only initialize one time; not idempotent. */ + LOG_DBG("Initializing async CoAP client"); + err = coap_client_init(&coap_client, NULL); + if (err) { + LOG_ERR("Failed to initialize CoAP client: %d", err); + return err; + } + (void)nrf_cloud_codec_init(NULL); +#if defined(CONFIG_MODEM_INFO) + err = modem_info_init(); + if (err) { + return err; + } +#endif + initialized = true; + } + + return 0; +} + +int nrf_cloud_coap_connect(void) +{ + struct sockaddr_storage server; + int err; + + err = server_resolve((struct sockaddr_in *)&server); + if (err) { + LOG_ERR("Failed to resolve server name: %d", err); + return err; + } + + LOG_DBG("Creating socket type IPPROTO_DTLS_1_2"); + if (sock == -1) { + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_DTLS_1_2); + } + LOG_DBG("sock = %d", sock); + if (sock < 0) { + LOG_ERR("Failed to create CoAP socket: %d.", -errno); + return -errno; + } + + err = nrfc_dtls_setup(sock); + if (err < 0) { + LOG_ERR("Failed to initialize the DTLS client: %d", err); + goto fail; + } + authenticated = false; + + err = connect(sock, (struct sockaddr *)&server, + sizeof(struct sockaddr_in)); + if (err < 0) { + LOG_ERR("Connect failed : %d", -errno); + goto fail; + } + + err = nrf_cloud_coap_authenticate(); + if (err < 0) { + goto fail; + } + return 0; + +fail: + (void)nrf_cloud_coap_disconnect(); + return err; +} + +static void auth_cb(int16_t result_code, size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user_data) +{ + LOG_RESULT_CODE_INF("Authorization result_code:", result_code); + if (result_code < COAP_RESPONSE_CODE_BAD_REQUEST) { + authenticated = true; + } +} + +static int nrf_cloud_coap_authenticate(void) +{ + int err; + char *jwt; + + if (authenticated) { + LOG_DBG("Already authenticated"); + return 0; + } + +#if defined(CONFIG_MODEM_INFO) + char mfw_string[MODEM_INFO_FWVER_SIZE]; + char ver_string[strlen(VER_STRING_FMT) + + MODEM_INFO_FWVER_SIZE + + strlen(BUILD_VERSION_STR) + + strlen(CDDL_VERSION)]; + + err = modem_info_get_fw_version(mfw_string, sizeof(mfw_string)); + if (!err) { + err = snprintf(ver_string, sizeof(ver_string), VER_STRING_FMT, + mfw_string, BUILD_VERSION_STR, CDDL_VERSION); + if ((err < 0) || (err >= sizeof(ver_string))) { + LOG_ERR("Could not format string"); + return -ETXTBSY; + } + } else { + LOG_ERR("Unable to obtain the modem firmware version: %d", err); + } +#else + char *ver_string = VER_STRING_FMT2; +#endif + + LOG_DBG("Generate JWT"); + jwt = k_malloc(JWT_BUF_SZ); + if (!jwt) { + return -ENOMEM; + } + err = nrf_cloud_jwt_generate(NRF_CLOUD_JWT_VALID_TIME_S_MAX, jwt, JWT_BUF_SZ); + if (err) { + LOG_ERR("Error generating JWT with modem: %d", err); + k_free(jwt); + return err; + } + + LOG_INF("Request authorization with JWT"); + err = nrf_cloud_coap_post("auth/jwt", err ? NULL : ver_string, + (uint8_t *)jwt, strlen(jwt), + COAP_CONTENT_FORMAT_TEXT_PLAIN, true, auth_cb, NULL); + k_free(jwt); + + if (err) { + return err; + } + if (!authenticated) { + return -EACCES; + } + + LOG_INF("Authorized"); + + if (nrfc_dtls_cid_is_active(sock)) { + LOG_INF("DTLS CID is active"); + } + return err; +} + +int nrf_cloud_coap_pause(void) +{ + int err = 0; + + cid_saved = false; + + if (nrfc_dtls_cid_is_active(sock) && authenticated) { + err = nrfc_dtls_session_save(sock); + if (!err) { + LOG_DBG("DTLS CID session saved."); + cid_saved = true; + } else { + LOG_ERR("Unable to save DTLS CID session: %d", err); + } + } else { + LOG_WRN("Unable to pause CoAP connection."); + err = -EACCES; + } + return err; +} + +int nrf_cloud_coap_resume(void) +{ + int err = 0; + + if (cid_saved) { + err = nrfc_dtls_session_load(sock); + cid_saved = false; + if (!err) { + LOG_DBG("Loaded DTLS CID session"); + authenticated = true; + } else if (err == -EAGAIN) { + LOG_INF("Failed to load DTLS CID session"); + } else if (err == -EINVAL) { + LOG_INF("DLTS CID sessions not supported with current modem firmware"); + } else { + LOG_ERR("Error on DTLS CID session load: %d", err); + } + } else { + LOG_WRN("Unable to resume CoAP connection."); + err = -EACCES; /* Cannot resume. */ + } + + return 0; +} + +static K_SEM_DEFINE(serial_sem, 1, 1); +static K_SEM_DEFINE(cb_sem, 0, 1); + +struct user_cb { + coap_client_response_cb_t cb; + void *user_data; +}; + +static void client_callback(int16_t result_code, size_t offset, const uint8_t *payload, size_t len, + bool last_block, void *user_data) +{ + struct user_cb *user_cb = (struct user_cb *)user_data; + + LOG_CB_DBG(result_code, offset, len, last_block); + if (payload && len) { + LOG_HEXDUMP_DBG(payload, MIN(len, 96), "payload received"); + } + if (result_code == COAP_RESPONSE_CODE_UNAUTHORIZED) { + LOG_ERR("Device not authenticated; reconnection required."); + authenticated = false; /* Lost authorization; need to reconnect. */ + } + if ((user_cb != NULL) && (user_cb->cb != NULL)) { + LOG_DBG("Calling user's callback %p", user_cb->cb); + user_cb->cb(result_code, offset, payload, len, last_block, user_cb->user_data); + } + if (last_block || (result_code >= COAP_RESPONSE_CODE_BAD_REQUEST)) { + LOG_DBG("Giving sem"); + k_sem_give(&cb_sem); + } +} + +static int client_transfer(enum coap_method method, + const char *resource, const char *query, + uint8_t *buf, size_t buf_len, + enum coap_content_format fmt_out, + enum coap_content_format fmt_in, + bool response_expected, + bool reliable, + coap_client_response_cb_t cb, void *user) +{ + __ASSERT_NO_MSG(resource != NULL); + + k_sem_take(&serial_sem, K_FOREVER); + + int err; + int retry; + char path[MAX_COAP_PATH + 1]; + struct user_cb user_cb = { + .cb = cb, + .user_data = user + }; + struct coap_client_option options[1] = {{ + .code = COAP_OPTION_ACCEPT, + .len = 1, + .value[0] = fmt_in + }}; + struct coap_client_request request = { + .method = method, + .confirmable = reliable, + .path = path, + .fmt = fmt_out, + .payload = buf, + .len = buf_len, + .cb = client_callback, + .user_data = &user_cb + }; + + if (response_expected) { + request.options = options; + request.num_options = ARRAY_SIZE(options); + } else { + request.options = NULL; + request.num_options = 0; + } + + if (!query) { + strncpy(path, resource, MAX_COAP_PATH); + path[MAX_COAP_PATH] = '\0'; + } else { + err = snprintf(path, MAX_COAP_PATH, "%s?%s", resource, query); + if ((err < 0) || (err >= MAX_COAP_PATH)) { + LOG_ERR("Could not format string"); + return -ETXTBSY; + } + } + +#if defined(CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG) + LOG_DBG("%s %s %s Content-Format:%s, %zd bytes out, Accept:%s", reliable ? "CON" : "NON", + METHOD_NAME(method), path, fmt_name(fmt_out), buf_len, + response_expected ? fmt_name(fmt_in) : "none"); +#endif /* CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG */ + + retry = 0; + k_sem_reset(&cb_sem); + while ((err = coap_client_req(&coap_client, sock, NULL, &request, + reliable ? -1 : CONFIG_NON_RESP_RETRIES)) == -EAGAIN) { + if (retry++ > MAX_RETRIES) { + LOG_ERR("Timeout waiting for CoAP client to be available"); + return -ETIMEDOUT; + } + LOG_DBG("CoAP client busy"); + k_sleep(K_MSEC(500)); + } + + if (err < 0) { + LOG_ERR("Error sending CoAP request: %d", err); + } else { + if (buf_len) { + LOG_HEXDUMP_DBG(buf, MIN(64, buf_len), "Sent"); + } + err = k_sem_take(&cb_sem, K_FOREVER); /* Wait for coap_client to exhaust retries */ + LOG_DBG("Received sem"); + } + + k_sem_give(&serial_sem); + return err; +} + +int nrf_cloud_coap_get(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt_out, + enum coap_content_format fmt_in, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_GET, resource, query, + buf, len, fmt_out, fmt_in, true, reliable, cb, user); +} + +int nrf_cloud_coap_post(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_POST, resource, query, + buf, len, fmt, fmt, false, reliable, cb, user); +} + +int nrf_cloud_coap_put(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_PUT, resource, query, + buf, len, fmt, fmt, false, reliable, cb, user); +} + +int nrf_cloud_coap_delete(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_DELETE, resource, query, + buf, len, fmt, fmt, false, reliable, cb, user); +} + +int nrf_cloud_coap_fetch(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt_out, + enum coap_content_format fmt_in, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_FETCH, resource, query, + buf, len, fmt_out, fmt_in, true, reliable, cb, user); +} + +int nrf_cloud_coap_patch(const char *resource, const char *query, + uint8_t *buf, size_t len, + enum coap_content_format fmt, bool reliable, + coap_client_response_cb_t cb, void *user) +{ + return client_transfer(COAP_METHOD_PATCH, resource, query, + buf, len, fmt, fmt, false, reliable, cb, user); +} + +int nrf_cloud_coap_disconnect(void) +{ + int err; + + if (sock < 0) { + return -ENOTCONN; + } + + authenticated = false; + err = close(sock); + sock = -1; + + return err; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/nrfc_dtls.c b/subsys/net/lib/nrf_cloud/coap/src/nrfc_dtls.c new file mode 100644 index 00000000000..d20e8fb69b9 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/nrfc_dtls.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#if defined(CONFIG_POSIX_API) +#include +#include +#else +#include +#endif +#include +#include +#if defined(CONFIG_MODEM_INFO) +#include +#endif +#include +#include + +#include "nrfc_dtls.h" + +#include +LOG_MODULE_REGISTER(dtls, CONFIG_NRF_CLOUD_COAP_LOG_LEVEL); + +static int sectag = CONFIG_NRF_CLOUD_COAP_SEC_TAG; +static bool dtls_cid_active; +static bool cid_supported = true; + +#if defined(CONFIG_MODEM_INFO) +static struct modem_param_info mdm_param; + +static int get_modem_info(void) +{ + int err; + + err = modem_info_string_get(MODEM_INFO_IMEI, + mdm_param.device.imei.value_string, + MODEM_INFO_MAX_RESPONSE_SIZE); + if (err <= 0) { + LOG_ERR("Could not get IMEI: %d", err); + return err; + } + + err = modem_info_string_get(MODEM_INFO_FW_VERSION, + mdm_param.device.modem_fw.value_string, + MODEM_INFO_MAX_RESPONSE_SIZE); + if (err <= 0) { + LOG_ERR("Could not get mfw ver: %d", err); + return err; + } + + LOG_INF("IMEI: %s", mdm_param.device.imei.value_string); + LOG_INF("Modem FW version: %s", mdm_param.device.modem_fw.value_string); + + return 0; +} + +#endif /* CONFIG_MODEM_INFO */ + +static int get_device_ip_address(uint8_t *d4_addr) +{ +#if defined(CONFIG_MODEM_INFO) + int err; + + err = modem_info_string_get(MODEM_INFO_IP_ADDRESS, + mdm_param.network.ip_address.value_string, + MODEM_INFO_MAX_RESPONSE_SIZE); + if (err <= 0) { + LOG_ERR("Could not get IP addr: %d", err); + return err; + } + err = inet_pton(AF_INET, mdm_param.network.ip_address.value_string, d4_addr); + if (err == 1) { + return 0; + } + return -errno; +#else + d4_addr[0] = 0; + d4_addr[1] = 0; + d4_addr[2] = 0; + d4_addr[3] = 0; + return 0; +#endif + +} + +int nrfc_dtls_setup(int sock) +{ + int err; + uint8_t d4_addr[4]; + + /* once connected, cache the connection info */ + dtls_cid_active = false; + +#if defined(CONFIG_MODEM_INFO) + err = get_modem_info(); + if (err) { + LOG_INF("Modem firmware version not known"); + } +#endif + + err = get_device_ip_address(d4_addr); + if (!err) { + LOG_DBG("Client IP address: %u.%u.%u.%u", + d4_addr[0], d4_addr[1], d4_addr[2], d4_addr[3]); + } + + LOG_DBG("Setting socket options:"); + + LOG_DBG(" hostname: %s", CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME); + err = setsockopt(sock, SOL_TLS, TLS_HOSTNAME, CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME, + sizeof(CONFIG_NRF_CLOUD_COAP_SERVER_HOSTNAME)); + if (err) { + LOG_ERR("Error setting hostname: %d", -errno); + return -errno; + } + + LOG_DBG(" sectag: %d", sectag); + err = setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, §ag, sizeof(sectag)); + if (err) { + LOG_ERR("Error setting sectag list: %d", -errno); + return -errno; + } + + int cid_option = TLS_DTLS_CID_SUPPORTED; + + cid_supported = true; + LOG_DBG(" Enable connection id"); + err = setsockopt(sock, SOL_TLS, TLS_DTLS_CID, &cid_option, sizeof(cid_option)); + if (!err) { + } else if ((err != EOPNOTSUPP) && (err != EINVAL)) { + LOG_ERR("Error enabling connection ID: %d", -errno); + cid_supported = false; + } else { + LOG_INF("Connection ID not supported by the installed modem firmware"); + cid_supported = false; + } + + int timeout = TLS_DTLS_HANDSHAKE_TIMEO_123S; + + LOG_DBG(" Set handshake timeout %d", timeout); + err = setsockopt(sock, SOL_TLS, TLS_DTLS_HANDSHAKE_TIMEO, + &timeout, sizeof(timeout)); + if (!err) { + } else if ((err != EOPNOTSUPP) || (err != EINVAL)) { + LOG_ERR("Error setting handshake timeout: %d", -errno); + } + + int verify = TLS_PEER_VERIFY_REQUIRED; + + LOG_DBG(" Peer verify: %d", verify); + err = setsockopt(sock, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify)); + if (err) { + LOG_ERR("Failed to setup peer verification, errno %d", -errno); + return -errno; + } + + int session_cache = TLS_SESSION_CACHE_ENABLED; + + err = setsockopt(sock, SOL_TLS, TLS_SESSION_CACHE, &session_cache, sizeof(session_cache)); + if (err) { + LOG_ERR("Failed to enable session cache, errno: %d", -errno); + err = -errno; + } + return err; +} + +int nrfc_dtls_session_save(int sock) +{ + int dummy = 0; + int err; + + LOG_DBG("Save DTLS CID session"); + err = setsockopt(sock, SOL_TLS, TLS_DTLS_CONN_SAVE, &dummy, sizeof(dummy)); + if (err) { + LOG_DBG("Failed to save DTLS CID session, errno %d", -errno); + err = -errno; + } + return err; +} + +int nrfc_dtls_session_load(int sock) +{ + int dummy = 1; + int err; + + LOG_DBG("Load DTLS CID session"); + err = setsockopt(sock, SOL_TLS, TLS_DTLS_CONN_LOAD, &dummy, sizeof(dummy)); + if (err) { + LOG_DBG("Failed to load DTLS CID session, errno %d", -errno); + err = -errno; + } + return err; +} + +bool nrfc_dtls_cid_is_active(int sock) +{ + int err = 0; + + if (dtls_cid_active) { + return true; + } + + int status = 0; + int len = sizeof(status); + + err = getsockopt(sock, SOL_TLS, TLS_DTLS_HANDSHAKE_STATUS, &status, &len); + if (!err) { + if (len > 0) { + if (status == TLS_DTLS_HANDSHAKE_STATUS_FULL) { + LOG_DBG("Full DTLS handshake performed"); + } else if (status == TLS_DTLS_HANDSHAKE_STATUS_CACHED) { + LOG_DBG("Cached DTLS handshake performed"); + } else { + LOG_WRN("Unknown DTLS handshake status: %d", status); + } + } else { + LOG_WRN("No DTLS status provided"); + } + } else if ((errno != EOPNOTSUPP) && (errno != EINVAL)) { + LOG_ERR("Error retrieving handshake status: %d", -errno); + } /* else the current modem firmware does not support this feature */ + + if (!cid_supported) { + return false; + } + + len = sizeof(status); + err = getsockopt(sock, SOL_TLS, TLS_DTLS_CID_STATUS, &status, &len); + if (!err) { + if (len > 0) { + switch (status) { + case TLS_DTLS_CID_STATUS_DISABLED: + dtls_cid_active = false; + LOG_DBG("No DTLS CID used"); + break; + case TLS_DTLS_CID_STATUS_DOWNLINK: + dtls_cid_active = false; + LOG_DBG("DTLS CID downlink"); + break; + case TLS_DTLS_CID_STATUS_UPLINK: + dtls_cid_active = true; + LOG_DBG("DTLS CID uplink"); + break; + case TLS_DTLS_CID_STATUS_BIDIRECTIONAL: + dtls_cid_active = true; + LOG_DBG("DTLS CID bidirectional"); + break; + default: + LOG_WRN("Unknown DTLS CID status: %d", status); + break; + } + } else { + LOG_WRN("No DTLS CID status provided"); + } + } else { + LOG_ERR("Error retrieving DTLS CID status: %d", -errno); + } + + return dtls_cid_active; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/pgps_decode.c b/subsys/net/lib/nrf_cloud/coap/src/pgps_decode.c new file mode 100644 index 00000000000..ce5edcdd0e6 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/pgps_decode.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_decode.h" +#include "pgps_decode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool decode_pgps_resp(zcbor_state_t *state, struct pgps_resp *result); + +static bool decode_pgps_resp(zcbor_state_t *state, struct pgps_resp *result) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = (((zcbor_map_start_decode(state) && + (((((zcbor_uint32_expect(state, (1)))) && + (zcbor_tstr_decode(state, (&(*result)._pgps_resp_host)))) && + (((zcbor_uint32_expect(state, (2)))) && + (zcbor_tstr_decode(state, (&(*result)._pgps_resp_path))))) || + (zcbor_list_map_end_force_decode(state), false)) && + zcbor_map_end_decode(state)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_decode_pgps_resp(const uint8_t *payload, size_t payload_len, struct pgps_resp *result, + size_t *payload_len_out) +{ + zcbor_state_t states[3]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = decode_pgps_resp(states, result); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/src/pgps_encode.c b/subsys/net/lib/nrf_cloud/coap/src/pgps_encode.c new file mode 100644 index 00000000000..5e54c6a8a88 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/src/pgps_encode.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + * + * Generated using zcbor version 0.7.0 + * https://github.com/NordicSemiconductor/zcbor + * Generated with a --default-max-qty of 10 + */ + +#include +#include +#include +#include +#include "zcbor_encode.h" +#include "pgps_encode.h" + +#if DEFAULT_MAX_QTY != 10 +#error "The type file was generated with a different default_max_qty than this file" +#endif + +static bool encode_pgps_req(zcbor_state_t *state, const struct pgps_req *input); + +static bool encode_pgps_req(zcbor_state_t *state, const struct pgps_req *input) +{ + zcbor_print("%s\r\n", __func__); + + bool tmp_result = ((( + zcbor_map_start_encode(state, 4) && + (((((zcbor_uint32_put(state, (1)))) && + (zcbor_uint32_encode(state, (&(*input)._pgps_req_predictionCount)))) && + (((zcbor_uint32_put(state, (2)))) && + (zcbor_uint32_encode(state, (&(*input)._pgps_req_predictionIntervalMinutes)))) && + (((zcbor_uint32_put(state, (3)))) && + (zcbor_uint32_encode(state, (&(*input)._pgps_req_startGPSDay)))) && + (((zcbor_uint32_put(state, (4)))) && + (zcbor_uint32_encode(state, (&(*input)._pgps_req_startGPSTimeOfDaySeconds))))) || + (zcbor_list_map_end_force_encode(state), false)) && + zcbor_map_end_encode(state, 4)))); + + if (!tmp_result) { + zcbor_trace(); + } + + return tmp_result; +} + +int cbor_encode_pgps_req(uint8_t *payload, size_t payload_len, const struct pgps_req *input, + size_t *payload_len_out) +{ + zcbor_state_t states[3]; + + zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1); + + bool ret = encode_pgps_req(states, input); + + if (ret && (payload_len_out != NULL)) { + *payload_len_out = MIN(payload_len, (size_t)states[0].payload - (size_t)payload); + } + + if (!ret) { + int err = zcbor_pop_error(states); + + zcbor_print("Return error: %d\r\n", err); + return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err; + } + return ZCBOR_SUCCESS; +} diff --git a/subsys/net/lib/nrf_cloud/coap/update_codec.bat b/subsys/net/lib/nrf_cloud/coap/update_codec.bat new file mode 100644 index 00000000000..4a482eb2f1b --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/update_codec.bat @@ -0,0 +1,12 @@ +set HDR="Copyright (c) 2023 Nordic Semiconductor ASA SPDX-License-Identifier: LicenseRef-Nordic-5-Clause" +zcbor code -c cddl\nrf_cloud_coap_ground_fix.cddl --default-max-qty 10 -e -t ground_fix_req --oc src\ground_fix_encode.c --oh include\ground_fix_encode.h --oht include\ground_fix_encode_types.h --file-header %HDR% +zcbor code -c cddl\nrf_cloud_coap_ground_fix.cddl --default-max-qty 10 -d -t ground_fix_resp --oc src\ground_fix_decode.c --oh include\ground_fix_decode.h --oht include\ground_fix_decode_types.h --file-header %HDR% +zcbor code -c cddl\nrf_cloud_coap_device_msg.cddl --default-max-qty 10 -e -t message_out --oc src\msg_encode.c --oh include\msg_encode.h --oht include\msg_encode_types.h --file-header %HDR% +zcbor code -c cddl\nrf_cloud_coap_agps.cddl --default-max-qty 10 -e -t agps_req --oc src\agps_encode.c --oh include\agps_encode.h --oht include\agps_encode_types.h --file-header %HDR% +zcbor code -c cddl\nrf_cloud_coap_pgps.cddl --default-max-qty 10 -e -t pgps_req --oc src\pgps_encode.c --oh include\pgps_encode.h --oht include\pgps_encode_types.h --file-header %HDR% +zcbor code -c cddl\nrf_cloud_coap_pgps.cddl --default-max-qty 10 -d -t pgps_resp --oc src\pgps_decode.c --oh include\pgps_decode.h --oht include\pgps_decode_types.h --file-header %HDR% +clang-format -i src/*code.c +clang-format -i include/*encode*.h +clang-format -i include/*decode*.h +git add src/*code.c include/*encode*.h include/*decode*.h +git add cddl/*.cddl diff --git a/subsys/net/lib/nrf_cloud/coap/update_codec.sh b/subsys/net/lib/nrf_cloud/coap/update_codec.sh new file mode 100644 index 00000000000..3182105f710 --- /dev/null +++ b/subsys/net/lib/nrf_cloud/coap/update_codec.sh @@ -0,0 +1,107 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +export HDR=" +Copyright (c) 2023 Nordic Semiconductor ASA +SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +" + +zcbor code -c cddl/nrf_cloud_coap_ground_fix.cddl --default-max-qty 10 -e \ + -t ground_fix_req \ + --oc src/ground_fix_encode.c \ + --oh include/ground_fix_encode.h \ + --oht include/ground_fix_encode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Encoder generation failed!" + exit +fi + +zcbor code -c cddl/nrf_cloud_coap_ground_fix.cddl --default-max-qty 10 -d \ + -t ground_fix_resp \ + --oc src/ground_fix_decode.c \ + --oh include/ground_fix_decode.h \ + --oht include/ground_fix_decode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Decoder generation failed!" + exit +fi + +zcbor code -c cddl/nrf_cloud_coap_device_msg.cddl --default-max-qty 10 -e \ + -t message_out \ + --oc src/msg_encode.c \ + --oh include/msg_encode.h \ + --oht include/msg_encode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Encoder generation failed!" + exit +fi + +zcbor code -c cddl/nrf_cloud_coap_agps.cddl --default-max-qty 10 -e \ + -t agps_req \ + --oc src/agps_encode.c \ + --oh include/agps_encode.h \ + --oht include/agps_encode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Encoder generation failed!" + exit +fi + +zcbor code -c cddl/nrf_cloud_coap_pgps.cddl --default-max-qty 10 -e \ + -t pgps_req \ + --oc src/pgps_encode.c \ + --oh include/pgps_encode.h \ + --oht include/pgps_encode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Encoder generation failed!" + exit +fi + +zcbor code -c cddl/nrf_cloud_coap_pgps.cddl --default-max-qty 10 -d \ + -t pgps_resp \ + --oc src/pgps_decode.c \ + --oh include/pgps_decode.h \ + --oht include/pgps_decode_types.h \ + --file-header "$HDR" + +if [ $? -ne 0 ]; then + echo "Decoder generation failed!" + exit +fi + +# Format the generated files +clang-format -i src/*code.c +clang-format -i include/*encode*.h +clang-format -i include/*decode*.h + +# Commit the generated files and the matching CDDL file +git add src/*code.c include/*encode*.h include/*decode*.h +git add cddl/*.cddl + +# alternate design -- combine all encoders into one file and all decoders into another +# zcbor code -c cddl/nrf_cloud_coap_ground_fix.cddl \ +# -c cddl/nrf_cloud_coap_device_msg.cddl \ +# -c cddl/nrf_cloud_coap_agps.cddl \ +# -c cddl/nrf_cloud_coap_pgps.cddl \ +# --default-max-qty 10 -e \ +# -t ground_fix_req message_out agps_req pgps_req \ +# --oc src/coap_encode.c \ +# --oh include/coap_encode.h \ +# --oht include/coap_encode_types.h +# zcbor code -c cddl/nrf_cloud_coap_ground_fix.cddl \ +# -c cddl/nrf_cloud_coap_pgps.cddl \ +# --default-max-qty 10 -d \ +# -t ground_fix_resp pgps_resp \ +# --oc src/coap_decode.c \ +# --oh include/coap_decode.h \ +# --oht include/coap_decode_types.h diff --git a/subsys/net/lib/nrf_cloud/include/nrf_cloud_codec_internal.h b/subsys/net/lib/nrf_cloud/include/nrf_cloud_codec_internal.h index 780649122e5..84387cd3c4a 100644 --- a/subsys/net/lib/nrf_cloud/include/nrf_cloud_codec_internal.h +++ b/subsys/net/lib/nrf_cloud/include/nrf_cloud_codec_internal.h @@ -66,6 +66,16 @@ struct nrf_cloud_bin_hdr { uint32_t sequence; } __packed; +/** @brief Structure to receive dynamically allocated strings containing + * details for FOTA job update. + */ +struct nrf_cloud_fota_job_update { + /** REST path or CoAP resource */ + char *url; + /** Update message to send to the url */ + char *payload; +}; + /** @brief Initialize the codec used encoding the data to the cloud. */ int nrf_cloud_codec_init(struct nrf_cloud_os_mem_hooks *hooks); @@ -151,6 +161,16 @@ void nrf_cloud_device_status_free(struct nrf_cloud_data *status); */ void nrf_cloud_fota_job_free(struct nrf_cloud_fota_job_info *const job); +/** @brief Free memory allocated by @ref nrf_cloud_fota_job_update_create */ +void nrf_cloud_fota_job_update_free(struct nrf_cloud_fota_job_update *update); + +/** @brief Create an nF Cloud REST or CoAP FOTA job update url and payload. */ +int nrf_cloud_fota_job_update_create(const char *const device_id, + const char *const job_id, + const enum nrf_cloud_fota_status status, + const char * const details, + struct nrf_cloud_fota_job_update *update); + /** @brief Create an nRF Cloud MQTT FOTA job update payload which is to be sent * on the FOTA jobs update topic. * If successful, memory is allocated for the provided object. @@ -341,6 +361,9 @@ void nrf_cloud_register_gateway_state_handler(gateway_state_handler_t handler); int nrf_cloud_log_json_encode(struct nrf_cloud_log_context *ctx, uint8_t *buf, size_t size, struct nrf_cloud_data *output); +/** @brief Return the appId string equivalent to the specified sensor type, otherwise NULL. */ +const char *nrf_cloud_sensor_app_id_lookup(enum nrf_cloud_sensor type); + #ifdef __cplusplus } #endif diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_client_id.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_client_id.c index 21903678cf6..ad8d7986edd 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_client_id.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_client_id.c @@ -55,9 +55,9 @@ int nrf_cloud_client_id_get(char *id_buf, size_t id_len) ret = -ENODEV; #endif -#if defined(CONFIG_NRF_CLOUD_REST) +#if defined(CONFIG_NRF_CLOUD_REST) || defined(CONFIG_NRF_CLOUD_COAP) if (ret != 0) { - /* For REST, the client ID is generated on demand. */ + /* For REST and CoAP, the client ID is generated on demand. */ ret = nrf_cloud_configured_client_id_get(id_buf, id_len); } #endif diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c index 6eb95db7b38..35ebac23323 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c @@ -69,6 +69,35 @@ static const char *const sensor_type_str[] = { /* Max length of a NRF_CLOUD_JSON_MSG_TYPE_VAL_DISCONNECT message */ #define NRF_CLOUD_JSON_MSG_MAX_LEN_DISCONNECT 200 +#if defined(CONFIG_NRF_CLOUD_REST) +#define API_VER "/v1" +#define API_FOTA_JOB_EXEC "/fota-job-executions" +#define API_UPDATE_FOTA_URL_TEMPLATE (API_VER API_FOTA_JOB_EXEC "/%s/%s") +#elif defined(CONFIG_NRF_CLOUD_COAP) +#define API_FOTA_JOB_EXEC "fota/exec" +#define API_UPDATE_FOTA_URL_TEMPLATE (API_FOTA_JOB_EXEC "/%s") +#else +#define API_FOTA_JOB_EXEC "" +#define API_UPDATE_FOTA_URL_TEMPLATE "" +#endif + +#define API_UPDATE_FOTA_BODY_TEMPLATE "{\"status\":\"%s\"}" +#define API_UPDATE_FOTA_DETAILS_TMPLT "{\"status\":\"%s\", \"details\":\"%s\"}" + +/* Mapping of enum to strings for Job Execution Status. */ +static const char *const job_status_strings[] = { + [NRF_CLOUD_FOTA_QUEUED] = "QUEUED", + [NRF_CLOUD_FOTA_IN_PROGRESS] = "IN_PROGRESS", + [NRF_CLOUD_FOTA_FAILED] = "FAILED", + [NRF_CLOUD_FOTA_SUCCEEDED] = "SUCCEEDED", + [NRF_CLOUD_FOTA_TIMED_OUT] = "TIMED_OUT", + [NRF_CLOUD_FOTA_REJECTED] = "REJECTED", + [NRF_CLOUD_FOTA_CANCELED] = "CANCELLED", + [NRF_CLOUD_FOTA_DOWNLOADING] = "DOWNLOADING", +}; +#define JOB_STATUS_STRING_COUNT (sizeof(job_status_strings) / \ + sizeof(*job_status_strings)) + int nrf_cloud_codec_init(struct nrf_cloud_os_mem_hooks *hooks) { if (!initialized) { @@ -91,6 +120,14 @@ int nrf_cloud_codec_init(struct nrf_cloud_os_mem_hooks *hooks) return 0; } +const char *nrf_cloud_sensor_app_id_lookup(enum nrf_cloud_sensor type) +{ + if ((type >= 0) && (type < SENSOR_TYPE_ARRAY_SIZE)) { + return sensor_type_str[type]; + } + return NULL; +} + /* remove static to eliminate warning if not using CONFIG_NRF_CLOUD_MQTT */ int json_add_bool_cs(cJSON *parent, const char *str, bool item) { @@ -1659,19 +1696,19 @@ int nrf_cloud_shadow_dev_status_encode(const struct nrf_cloud_device_status *con int err = 0; cJSON *state_obj = NULL; - cJSON *reported_obj = NULL; + cJSON *parent_obj = NULL; cJSON *root_obj = cJSON_CreateObject(); if (!include_reported) { - reported_obj = root_obj; + parent_obj = root_obj; } else if (include_state) { state_obj = cJSON_AddObjectToObjectCS(root_obj, NRF_CLOUD_JSON_KEY_STATE); - reported_obj = cJSON_AddObjectToObjectCS(state_obj, NRF_CLOUD_JSON_KEY_REP); + parent_obj = cJSON_AddObjectToObjectCS(state_obj, NRF_CLOUD_JSON_KEY_REP); } else { - reported_obj = cJSON_AddObjectToObjectCS(root_obj, NRF_CLOUD_JSON_KEY_REP); + parent_obj = cJSON_AddObjectToObjectCS(root_obj, NRF_CLOUD_JSON_KEY_REP); } - cJSON *device_obj = cJSON_AddObjectToObjectCS(reported_obj, NRF_CLOUD_JSON_KEY_DEVICE); + cJSON *device_obj = cJSON_AddObjectToObjectCS(parent_obj, NRF_CLOUD_JSON_KEY_DEVICE); if (device_obj == NULL) { err = -ENOMEM; @@ -1803,6 +1840,91 @@ enum rcv_item_idx { RCV_ITEM_IDX__SIZE, }; +void nrf_cloud_fota_job_update_free(struct nrf_cloud_fota_job_update *update) +{ + if (!update) { + return; + } + + nrf_cloud_free(update->payload); + update->payload = NULL; + + nrf_cloud_free(update->url); + update->url = NULL; +} + +int nrf_cloud_fota_job_update_create(const char *const device_id, + const char *const job_id, + const enum nrf_cloud_fota_status status, + const char * const details, + struct nrf_cloud_fota_job_update *update) +{ + __ASSERT_NO_MSG(job_id != NULL); + __ASSERT_NO_MSG(status < JOB_STATUS_STRING_COUNT); + __ASSERT_NO_MSG(update != NULL); + int ret; + size_t buff_sz; + + update->payload = NULL; + + /* Format API URL with device and job ID */ + buff_sz = sizeof(API_UPDATE_FOTA_URL_TEMPLATE) + +#if defined(CONFIG_NRF_CLOUD_REST) + strlen(device_id) + +#endif + strlen(job_id); + update->url = nrf_cloud_malloc(buff_sz); + if (!update->url) { + ret = -ENOMEM; + goto clean_up; + } + + ret = snprintk(update->url, buff_sz, API_UPDATE_FOTA_URL_TEMPLATE, +#if defined(CONFIG_NRF_CLOUD_REST) + device_id, /* CoAP does not need this parameter */ +#endif + job_id); + if ((ret < 0) || (ret >= buff_sz)) { + LOG_ERR("Could not format URL"); + ret = -ETXTBSY; + goto clean_up; + } + + /* Format payload */ + if (details) { + buff_sz = sizeof(API_UPDATE_FOTA_DETAILS_TMPLT) + + strlen(job_status_strings[status]) + + strlen(details); + } else { + buff_sz = sizeof(API_UPDATE_FOTA_BODY_TEMPLATE) + + strlen(job_status_strings[status]); + } + + update->payload = nrf_cloud_malloc(buff_sz); + if (!update->payload) { + ret = -ENOMEM; + goto clean_up; + } + + if (details) { + ret = snprintk(update->payload, buff_sz, API_UPDATE_FOTA_DETAILS_TMPLT, + job_status_strings[status], details); + } else { + ret = snprintk(update->payload, buff_sz, API_UPDATE_FOTA_BODY_TEMPLATE, + job_status_strings[status]); + } + if ((ret < 0) || (ret >= buff_sz)) { + LOG_ERR("Could not format payload"); + ret = -ETXTBSY; + goto clean_up; + } + return 0; + +clean_up: + nrf_cloud_fota_job_update_free(update); + return ret; +} + int nrf_cloud_fota_job_decode(struct nrf_cloud_fota_job_info *const job_info, bt_addr_t *const ble_id, const struct nrf_cloud_data *const input) diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_jwt.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_jwt.c index 41808ad58a9..81d0f73c4b3 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_jwt.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_jwt.c @@ -24,7 +24,11 @@ int nrf_cloud_jwt_generate(uint32_t time_valid_s, char *const jwt_buf, size_t jw char buf[NRF_CLOUD_CLIENT_ID_MAX_LEN + 1]; struct jwt_data jwt = { .audience = NULL, +#if defined(CONFIG_NRF_CLOUD_COAP) + .sec_tag = CONFIG_NRF_CLOUD_COAP_SEC_TAG, +#else .sec_tag = CONFIG_NRF_CLOUD_SEC_TAG, +#endif .key = JWT_KEY_TYPE_CLIENT_PRIV, .alg = JWT_ALG_TYPE_ES256, .jwt_buf = jwt_buf, diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_rest.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_rest.c index f0a2255afe2..2ad1a732af2 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_rest.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_rest.c @@ -66,9 +66,6 @@ LOG_MODULE_REGISTER(nrf_cloud_rest, CONFIG_NRF_CLOUD_REST_LOG_LEVEL); #define API_FOTA_JOB_EXEC "/fota-job-executions" #define API_GET_FOTA_URL_TEMPLATE (API_VER API_FOTA_JOB_EXEC "/%s/current") -#define API_UPDATE_FOTA_URL_TEMPLATE (API_VER API_FOTA_JOB_EXEC "/%s/%s") -#define API_UPDATE_FOTA_BODY_TEMPLATE "{\"status\":\"%s\"}" -#define API_UPDATE_FOTA_DETAILS_TMPLT "{\"status\":\"%s\", \"details\":\"%s\"}" #define API_LOCATION "/location" #define API_GET_LOCATION API_VER API_LOCATION "/ground-fix" @@ -91,20 +88,6 @@ LOG_MODULE_REGISTER(nrf_cloud_rest, CONFIG_NRF_CLOUD_REST_LOG_LEVEL); #define JITP_HTTP_TIMEOUT_MS (15000) #define JITP_RX_BUF_SZ (400) -/* Mapping of enum to strings for Job Execution Status. */ -static const char *const job_status_strings[] = { - [NRF_CLOUD_FOTA_QUEUED] = "QUEUED", - [NRF_CLOUD_FOTA_IN_PROGRESS] = "IN_PROGRESS", - [NRF_CLOUD_FOTA_FAILED] = "FAILED", - [NRF_CLOUD_FOTA_SUCCEEDED] = "SUCCEEDED", - [NRF_CLOUD_FOTA_TIMED_OUT] = "TIMED_OUT", - [NRF_CLOUD_FOTA_REJECTED] = "REJECTED", - [NRF_CLOUD_FOTA_CANCELED] = "CANCELLED", - [NRF_CLOUD_FOTA_DOWNLOADING] = "DOWNLOADING", -}; -#define JOB_STATUS_STRING_COUNT (sizeof(job_status_strings) / \ - sizeof(*job_status_strings)) - /* Generate an authorization header value string in the form: * "Authorization: Bearer JWT \r\n" */ @@ -378,38 +361,16 @@ int nrf_cloud_rest_fota_job_update(struct nrf_cloud_rest_context *const rest_ctx __ASSERT_NO_MSG(rest_ctx != NULL); __ASSERT_NO_MSG(device_id != NULL); __ASSERT_NO_MSG(job_id != NULL); - __ASSERT_NO_MSG(status < JOB_STATUS_STRING_COUNT); int ret; - size_t buff_sz; char *auth_hdr = NULL; - char *url = NULL; - char *payload = NULL; struct rest_client_req_context req; struct rest_client_resp_context resp; + struct nrf_cloud_fota_job_update update; memset(&resp, 0, sizeof(resp)); init_rest_client_request(rest_ctx, &req, HTTP_PATCH); - /* Format API URL with device and job ID */ - buff_sz = sizeof(API_UPDATE_FOTA_URL_TEMPLATE) + - strlen(device_id) + - strlen(job_id); - url = nrf_cloud_malloc(buff_sz); - if (!url) { - ret = -ENOMEM; - goto clean_up; - } - req.url = url; - - ret = snprintk(url, buff_sz, API_UPDATE_FOTA_URL_TEMPLATE, - device_id, job_id); - if ((ret < 0) || (ret >= buff_sz)) { - LOG_ERR("Could not format URL"); - ret = -ETXTBSY; - goto clean_up; - } - /* Format auth header */ ret = generate_auth_header(rest_ctx->auth, &auth_hdr); if (ret) { @@ -423,45 +384,23 @@ int nrf_cloud_rest_fota_job_update(struct nrf_cloud_rest_context *const rest_ctx NULL }; - req.header_fields = (const char **)headers; - - /* Format payload */ - if (details) { - buff_sz = sizeof(API_UPDATE_FOTA_DETAILS_TMPLT) + - strlen(job_status_strings[status]) + - strlen(details); - } else { - buff_sz = sizeof(API_UPDATE_FOTA_BODY_TEMPLATE) + - strlen(job_status_strings[status]); - } - - payload = nrf_cloud_malloc(buff_sz); - if (!payload) { - ret = -ENOMEM; + ret = nrf_cloud_fota_job_update_create(device_id, job_id, status, details, &update); + if (ret) { + LOG_ERR("Error creating FOTA job update structure: %d", ret); goto clean_up; } - if (details) { - ret = snprintk(payload, buff_sz, API_UPDATE_FOTA_DETAILS_TMPLT, - job_status_strings[status], details); - } else { - ret = snprintk(payload, buff_sz, API_UPDATE_FOTA_BODY_TEMPLATE, - job_status_strings[status]); - } - if ((ret < 0) || (ret >= buff_sz)) { - LOG_ERR("Could not format payload"); - ret = -ETXTBSY; - goto clean_up; - } - req.body = payload; + req.header_fields = (const char **)headers; + req.url = update.url; + req.body = update.payload; /* Make REST call */ ret = do_rest_client_request(rest_ctx, &req, &resp, true, false); + nrf_cloud_fota_job_update_free(&update); + clean_up: - nrf_cloud_free(url); nrf_cloud_free(auth_hdr); - nrf_cloud_free(payload); close_connection(rest_ctx);