Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use coap_client + download_client for nrf cloud coap downloads #9

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/net/download_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/net/coap.h>
#include <zephyr/net/coap_client.h>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -144,6 +145,24 @@ struct download_client_cfg {
typedef int (*download_client_callback_t)(
const struct download_client_evt *event);

struct download_client_coap_options {
struct coap_client_option *opts;
size_t opt_cnt;
};

struct download_client_coap_client {
/* User provided coap_client and options */
struct coap_client *cc;
struct coap_client_option *opts;
size_t opt_cnt;

/* CoAP transfer result code */
int xfer_res;

/* Internal transfer semaphore */
struct k_sem *xfer_sem;
};

/**
* @brief Download client instance.
*/
Expand Down Expand Up @@ -193,6 +212,9 @@ struct download_client {
} http;

struct {
#if defined(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)
struct download_client_coap_client dlc_cc;
#endif
/** CoAP block context. */
struct coap_block_context block_ctx;

Expand Down Expand Up @@ -243,6 +265,9 @@ struct download_client {
int download_client_init(struct download_client *client,
download_client_callback_t callback);

int download_client_init_coap(struct download_client *client,
download_client_callback_t callback);

/**
* @brief Set a target hostname.
*
Expand Down
2 changes: 2 additions & 0 deletions include/net/fota_download.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ int fota_download_target(void);
*/
int fota_download_s0_active_get(bool *const s0_active);

int fota_download_set_coap_client(struct download_client_coap_client *const dlc_cc);

#ifdef __cplusplus
}
#endif
Expand Down
1 change: 1 addition & 0 deletions include/net/nrf_cloud_coap.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ int nrf_cloud_coap_init(void);
* Negative values are device-side errors defined in errno.h.
* Positive values are cloud-side errors (CoAP result codes)
* defined in zephyr/net/coap.h.
* @retval -EACCES if @ref nrf_cloud_coap_init has not been called.
*/
int nrf_cloud_coap_connect(const char * const app_ver);

Expand Down
3 changes: 3 additions & 0 deletions subsys/net/lib/download_client/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ config DOWNLOAD_CLIENT_CID
Use DTLS Connection-ID option when downloading from CoAPS resources.
This requires modem firmware 1.3.5 or newer.

config DOWNLOAD_CLIENT_COAP_CLIENT
bool "Use CoAP Client library to perform CoAP downloads"

config DOWNLOAD_CLIENT_SHELL
bool "Enable shell"
depends on SHELL
Expand Down
205 changes: 199 additions & 6 deletions subsys/net/lib/download_client/src/download_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
#include <zephyr/logging/log.h>
#include "download_client_internal.h"

#if defined(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)
#include <zephyr/net/coap.h>
#include <zephyr/net/coap_client.h>
#endif

LOG_MODULE_REGISTER(download_client, CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL);

#define SIN6(A) ((struct sockaddr_in6 *)(A))
Expand Down Expand Up @@ -373,6 +378,13 @@
int type;
uint16_t port;

#if defined(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)
if (dl->coap.dlc_cc.cc->fd >= 0) {
/* Already connected */
return 0;
}
#endif

err = url_parse_proto(dl->host, &dl->proto, &type);
if (err == -EINVAL) {
LOG_DBG("Protocol not specified, defaulting to HTTP(S)");
Expand Down Expand Up @@ -791,6 +803,172 @@
return err;
}

#if defined(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)
static void coap_cb(int16_t result_code, size_t offset, const uint8_t *payload, size_t len,
bool last_block, void *user_data)
{
int ret;
struct download_client *dl = (struct download_client *)user_data;
struct download_client_evt evt = {0};

LOG_DBG("CoAP client:%d, offset:0x%X, len:0x%X, last_block:%d",
result_code, offset, len, last_block);

dl->coap.dlc_cc.xfer_res = result_code;

if ((result_code != COAP_RESPONSE_CODE_OK) && (result_code != COAP_RESPONSE_CODE_CONTENT)) {
LOG_ERR("Unexpected response: %*s", len, payload);
evt.id = DOWNLOAD_CLIENT_EVT_FRAGMENT;
evt.error = result_code;
} else {
dl->progress += len;
LOG_INF("Downloaded %u bytes", dl->progress);

evt.id = DOWNLOAD_CLIENT_EVT_FRAGMENT;
evt.fragment.buf = payload;
evt.fragment.len = len;
}

ret = dl->callback(&evt);
if (ret) {
// todo

Check failure on line 834 in subsys/net/lib/download_client/src/download_client.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

C99_COMMENTS

subsys/net/lib/download_client/src/download_client.c:834 do not use C99 // comments
}

if (last_block) {
LOG_INF("Download complete");

memset(&evt, 0, sizeof(evt));
evt.id = DOWNLOAD_CLIENT_EVT_DONE;

ret = dl->callback(&evt);
if (ret) {
// todo

Check failure on line 845 in subsys/net/lib/download_client/src/download_client.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

C99_COMMENTS

subsys/net/lib/download_client/src/download_client.c:845 do not use C99 // comments
}
}

if (last_block || (result_code >= COAP_RESPONSE_CODE_BAD_REQUEST)) {
k_sem_give(dl->coap.dlc_cc.xfer_sem);
}
}

#define MAX_RETRIES 5
int do_coap_client_transfer(struct download_client *const dl)
{
int err;
int retry = 0;
struct coap_client *cc = dl->coap.dlc_cc.cc;

struct coap_client_request request = {
.method = COAP_METHOD_GET,
.confirmable = true, // todo ?

Check failure on line 863 in subsys/net/lib/download_client/src/download_client.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

C99_COMMENTS

subsys/net/lib/download_client/src/download_client.c:863 do not use C99 // comments
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a bad idea to allow folks to do FOTA especially with non-confirmable transfers.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason to expose an option to the user? this would not always be used for FOTA downloads.

.path = dl->file,
.fmt = COAP_CONTENT_FORMAT_APP_OCTET_STREAM,
.payload = NULL,
.len = CONFIG_DOWNLOAD_CLIENT_BUF_SIZE,
.cb = coap_cb,
.user_data = dl,
.options = dl->coap.dlc_cc.opts,
.num_options = dl->coap.dlc_cc.opt_cnt,
};

k_sem_reset(dl->coap.dlc_cc.xfer_sem);
while ((err = coap_client_req(cc, cc->fd, NULL, &request, NULL)) == -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_DBG("Sent CoAP download request");
} else {
LOG_ERR("Error sending CoAP request: %d", err);
}

return err;
}
#endif /* CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT */

void download_thread_cc(void *client, void *a, void *b)
{
#if defined(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)
int rc;
ssize_t len;
struct download_client *const dl = client;
bool send_request = false;
struct k_sem xfer_sem;

k_sem_init(&xfer_sem, 0, 1);
dl->coap.dlc_cc.xfer_sem = &xfer_sem;

while (true) {
rc = 0;

/* Wait for action */
k_sem_take(&dl->wait_for_download, K_FOREVER);

/* Connect to the target host */
if (is_connecting(dl)) {

if (client_connect(dl)) {
continue;
}

len = 0;
send_request = true;

set_state(dl, DOWNLOAD_CLIENT_DOWNLOADING);
}

/* Request loop */
while (is_downloading(dl)) {
if (send_request) {
/* Request next fragment */
dl->offset = 0;
dl->file_size = 0;
dl->progress = 0;

rc = do_coap_client_transfer(dl);

send_request = false;

if (rc) {
rc = error_evt_send(dl, ECONNRESET);
if (rc) {
/* Restart and suspend */
break;
}
}
}

/* Wait for coap_client to finish*/
k_sem_take(&xfer_sem, K_FOREVER);

// todo, check dl->coap.dlc_cc.xfer_res for error, otherwise success/done

Check failure on line 949 in subsys/net/lib/download_client/src/download_client.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

C99_COMMENTS

subsys/net/lib/download_client/src/download_client.c:949 do not use C99 // comments

break;
}

if (is_downloading(dl)) {
if (dl->close_when_done) {
set_state(dl, DOWNLOAD_CLIENT_CLOSING);
} else {
set_state(dl, DOWNLOAD_CLIENT_FINISHED);
}
}

if (is_closing(dl)) {
handle_disconnect(dl);
LOG_DBG("Connection closed");
}

/* Do not let the thread return, since it can't be restarted */
}
#endif /* CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT */
}

void download_thread(void *client, void *a, void *b)
{
int rc;
Expand All @@ -813,8 +991,8 @@
}

/* Initialize CoAP */
if (IS_ENABLED(CONFIG_COAP) &&
(dl->proto == IPPROTO_UDP || dl->proto == IPPROTO_DTLS_1_2)) {
if ((dl->proto == IPPROTO_UDP || dl->proto == IPPROTO_DTLS_1_2) &&
IS_ENABLED(CONFIG_COAP)) {
coap_block_init(client, dl->progress);
}

Expand Down Expand Up @@ -899,8 +1077,8 @@
}
}

int download_client_init(struct download_client *const client,
download_client_callback_t callback)
static int client_init(struct download_client *const client, download_client_callback_t callback,
char const *const thread_name, k_thread_entry_t thread_fn)
{
if (client == NULL || callback == NULL) {
return -EINVAL;
Expand All @@ -920,16 +1098,31 @@
client->tid =
k_thread_create(&client->thread, client->thread_stack,
K_THREAD_STACK_SIZEOF(client->thread_stack),
download_thread, client, NULL, NULL,
thread_fn, client, NULL, NULL,
K_LOWEST_APPLICATION_THREAD_PRIO, 0, K_NO_WAIT);

k_thread_name_set(client->tid, "download_client");
k_thread_name_set(client->tid, thread_name);

k_mutex_unlock(&client->mutex);

return 0;
}

int download_client_init(struct download_client *const client,
download_client_callback_t callback)
{
return client_init(client, callback, "download_client", download_thread);
}

int download_client_init_coap(struct download_client *const client,
download_client_callback_t callback)
{
if (!IS_ENABLED(CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT)) {
__ASSERT(false, "CONFIG_DOWNLOAD_CLIENT_COAP_CLIENT is not enabled");
}
return client_init(client, callback, "download_client_cc", download_thread_cc);
}

int download_client_set_host(struct download_client *client, const char *host,
const struct download_client_cfg *config)
{
Expand Down
4 changes: 4 additions & 0 deletions subsys/net/lib/fota_download/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ config FOTA_DOWNLOAD_SEC_TAG_LIST_SIZE_MAX
help
Maximum size of the list of security tags used to store TLS credentials.

config FOTA_DOWNLOAD_COAP_OPTIONS
bool "Enable setting CoAP options for CoAP client download"
depends on DOWNLOAD_CLIENT_COAP_CLIENT

module=FOTA_DOWNLOAD
module-dep=LOG
module-str=Firmware Over the Air Download
Expand Down
1 change: 1 addition & 0 deletions subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_coap
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ menuconfig NRF_CLOUD_COAP
select ZCBOR
select COAP
select COAP_EXTENDED_OPTIONS_LEN
select DOWNLOAD_CLIENT_COAP_CLIENT
select COAP_CLIENT
select EXPERIMENTAL
select LTE_PROPRIETARY_PSM_REQ if (SOC_NRF9161_LACA || SOC_NRF9151_LACA || SOC_NRF9131_LACA)
Expand Down
Loading
Loading