[PATCH 01/17] parser: added function to get net child in tree

80 views
Skip to first unread message

Stefano Babic

unread,
Nov 20, 2017, 6:10:26 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Add function to get next element in the sw-description

Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/parsing_library.c | 15 +++++++++++++++
corelib/parsing_library_libconfig.c | 5 +++++
corelib/parsing_library_libjson.c | 12 ++++++++++++
include/parselib.h | 5 +++++
4 files changed, 37 insertions(+)

diff --git a/corelib/parsing_library.c b/corelib/parsing_library.c
index eb3001f..79f3886 100644
--- a/corelib/parsing_library.c
+++ b/corelib/parsing_library.c
@@ -59,6 +59,21 @@ int get_array_length(parsertype p, void *root)
return 0;
}

+void *get_child(parsertype p, void *e, const char *name)
+{
+ switch (p) {
+ case LIBCFG_PARSER:
+ return get_child_libconfig(e, name);
+ case JSON_PARSER:
+ return get_child_json((json_object *)e, name);
+ default:
+ (void)e;
+ (void)name;
+ }
+
+ return NULL;
+}
+
void *get_elem_from_idx(parsertype p, void *node, int idx)
{
switch (p) {
diff --git a/corelib/parsing_library_libconfig.c b/corelib/parsing_library_libconfig.c
index 5624c99..f0cfb38 100644
--- a/corelib/parsing_library_libconfig.c
+++ b/corelib/parsing_library_libconfig.c
@@ -54,6 +54,11 @@ void get_value_libconfig(const config_setting_t *e, void *dest)
}
}

+void *get_child_libconfig(void *e, const char *name)
+{
+ return config_setting_get_member(e, name);
+}
+
void get_field_cfg(config_setting_t *e, const char *path, void *dest)
{
config_setting_t *elem;
diff --git a/corelib/parsing_library_libjson.c b/corelib/parsing_library_libjson.c
index 12c95cc..2f64d41 100644
--- a/corelib/parsing_library_libjson.c
+++ b/corelib/parsing_library_libjson.c
@@ -49,6 +49,18 @@ json_object *find_json_recursive_node(json_object *root, const char **names)
return node;
}

+void *get_child_json(json_object *e, const char *path)
+{
+ json_object *node = NULL;
+
+ if (path) {
+ if (!json_object_object_get_ex(e, path, &node))
+ return NULL;
+ }
+
+ return node;
+}
+
const char *get_field_string_json(json_object *e, const char *path)
{
const char *str;
diff --git a/include/parselib.h b/include/parselib.h
index c055721..c32920a 100644
--- a/include/parselib.h
+++ b/include/parselib.h
@@ -37,6 +37,7 @@ typedef enum {

void get_value_libconfig(const config_setting_t *e, void *dest);
void get_field_cfg(config_setting_t *e, const char *path, void *dest);
+void *get_child_libconfig(void *e, const char *name);
const char *get_field_string_libconfig(config_setting_t *e, const char *path);

#else
@@ -45,6 +46,7 @@ const char *get_field_string_libconfig(config_setting_t *e, const char *path);
#define config_setting_lookup_string(a, b, str) (0)
#define find_node_libconfig(cfg, field, swcfg) (NULL)
#define get_field_string_libconfig(e, path) (NULL)
+#define get_child_libconfig(e, name) (NULL)
#define get_field_cfg(e, path, dest)
#endif

@@ -54,11 +56,13 @@ const char *get_field_string_libconfig(config_setting_t *e, const char *path);
const char *get_field_string_json(json_object *e, const char *path);
void get_value_json(json_object *e, void *dest);
void get_field_json(json_object *e, const char *path, void *dest);
+void *get_child_json(json_object *e, const char *name);
json_object *find_json_recursive_node(json_object *root, const char **names);

#else
#define find_node_json(a, b, c) (NULL)
#define get_field_string_json(e, path) (NULL)
+#define get_child_json(e, name) (NULL)
#define get_field_json(e, path, dest)
#define json_object_object_get_ex(a,b,c) (0)
#define json_object_array_get_idx(a, b) (0)
@@ -72,6 +76,7 @@ void get_field_string_with_size(parsertype p, void *e, const char *path,
char *d, size_t n);
int get_array_length(parsertype p, void *root);
void *get_elem_from_idx(parsertype p, void *node, int idx);
+void *get_child(parsertype p, void *node, const char *name);
void get_field(parsertype p, void *e, const char *path, void *dest);
int exist_field_string(parsertype p, void *e, const char *path);
void get_hash_value(parsertype p, void *elem, unsigned char *hash);
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:31 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Support free to be set attributes for each image.
They are set as forbootloader with a couple of <name>,<value>
and put into a dictionary for each image. They are not used by the
installer, but can be retrieved by the handler to trim the installation.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
include/swupdate.h | 1 +
parser/parser.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+)

diff --git a/include/swupdate.h b/include/swupdate.h
index 09e81d5..f0a6c28 100644
--- a/include/swupdate.h
+++ b/include/swupdate.h
@@ -77,6 +77,7 @@ struct img_type {
int install_directly;
int is_script;
int is_partitioner;
+ struct dictlist properties;
long long partsize;
int fdin; /* Used for streaming file */
off_t offset; /* offset in cpio file */
diff --git a/parser/parser.c b/parser/parser.c
index fe70b3b..8dfb326 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -152,6 +152,35 @@ static void *find_node(parsertype p, void *root, const char *node,
return NULL;
}

+static void add_properties(parsertype p, void *node, struct img_type *image)
+{
+
+ void *properties, *prop;
+ int count, i;
+
+ properties = get_child(p, node, "properties");
+ if (properties) {
+ count = get_array_length(p, properties);
+
+ TRACE("Found %d properties for %s:", count, image->fname);
+
+ for (i = 0; i < count; i++) {
+ char key[255];
+ char value[255];
+ prop = get_elem_from_idx(p, properties, i);
+ GET_FIELD_STRING(p, prop, "name", key);
+ GET_FIELD_STRING(p, prop, "value", value);
+ TRACE("\t\tProperty %d: name=%s val=%s ", i,
+ key,
+ value
+ );
+ if (dict_insert_entry(&image->properties, key, value))
+ ERROR("Property not stored, skipping...");
+
+ }
+ }
+}
+
#ifdef CONFIG_HW_COMPATIBILITY
/*
* Check if the software can run on the hardware
@@ -466,7 +495,10 @@ static int parse_images(parsertype p, void *cfg, struct swupdate_cfg *swcfg, lua
"Version must be checked" : ""
);

+ add_properties(p, elem, image);
+
if (run_embscript(p, elem, image, L, swcfg->embscript)) {
+ dict_drop_db(&image->properties);
free(image);
return -1;
}
@@ -537,7 +569,10 @@ static int parse_files(parsertype p, void *cfg, struct swupdate_cfg *swcfg, lua_
(strlen(file->id.name) && file->id.install_if_different) ?
"Version must be checked" : "");

+ add_properties(p, elem, file);
+
if (run_embscript(p, elem, file, L, swcfg->embscript)) {
+ dict_drop_db(&file->properties);
free(file);
return -1;
}
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:32 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/installer.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/corelib/installer.c b/corelib/installer.c
index ae86d09..b874e7e 100644
--- a/corelib/installer.c
+++ b/corelib/installer.c
@@ -429,6 +429,7 @@ void cleanup_files(struct swupdate_cfg *software) {
}
remove_sw_file(fn);
}
+ dict_drop_db(&img->properties);
LIST_REMOVE(img, next);
free(img);
}
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:36 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
A CONFIG_CURL is introduced to move curl code into a
separate library.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
Kconfig | 5 +++++
Makefile.flags | 2 +-
suricatta/Config.in | 1 +
3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/Kconfig b/Kconfig
index 4d244bc..e46ff05 100644
--- a/Kconfig
+++ b/Kconfig
@@ -61,6 +61,10 @@ menu "Swupdate Settings"

menu "General Configuration"

+config CURL
+ bool
+ default n
+
config SYSTEMD
bool "enable systemd support"
default n
@@ -278,6 +282,7 @@ config DOWNLOAD
bool "Enable image downloading"
default n
depends on HAVE_LIBCURL
+ select CURL
help
Enable update from image URL using libcurl. The stream is sent via IPC
to the installer as it is done for other interfaces.
diff --git a/Makefile.flags b/Makefile.flags
index c9f32f5..041a08e 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -113,7 +113,7 @@ endif
endif

# Image downloading support
-ifneq ($(CONFIG_DOWNLOAD),)
+ifneq ($(CONFIG_CURL),)
LDLIBS += curl
endif

diff --git a/suricatta/Config.in b/suricatta/Config.in
index 554ee61..62e448a 100644
--- a/suricatta/Config.in
+++ b/suricatta/Config.in
@@ -72,6 +72,7 @@ config SURICATTA_HAWKBIT
depends on HAVE_LIBCURL
depends on HAVE_JSON_C
select JSON
+ select CURL
help
Support for hawkBit server.
https://projects.eclipse.org/projects/iot.hawkbit
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:37 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
SWUpdate uses only sha256 to compute hashes. Add a parameter
to swupdate_HASH_init to let decide the caller the alg
to be used.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
core/cpio_utils.c | 2 +-
corelib/verify_signature.c | 19 +++++++++++++------
include/sslapi.h | 21 ++++++++++++++++-----
3 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/core/cpio_utils.c b/core/cpio_utils.c
index de674ec..0112f12 100644
--- a/core/cpio_utils.c
+++ b/core/cpio_utils.c
@@ -137,7 +137,7 @@ int copyfile(int fdin, void *out, unsigned int nbytes, unsigned long *offs, unsi
}

if (IsValidHash(hash)) {
- dgst = swupdate_HASH_init();
+ dgst = swupdate_HASH_init(SHA_DEFAULT);
if (!dgst)
return -EFAULT;
}
diff --git a/corelib/verify_signature.c b/corelib/verify_signature.c
index 0596578..3ffa328 100644
--- a/corelib/verify_signature.c
+++ b/corelib/verify_signature.c
@@ -29,12 +29,12 @@

#define BUFSIZE (1024 * 8)

-static int dgst_init(struct swupdate_digest *dgst)
+static int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md)
{
int rc;

ERR_clear_error();
- rc = EVP_DigestInit_ex(dgst->ctx, EVP_sha256(), NULL);
+ rc = EVP_DigestInit_ex(dgst->ctx, md, NULL);
if (rc != 1) {
ERROR("EVP_DigestInit_ex failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
return -EINVAL; /* failed */
@@ -158,7 +158,7 @@ int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
goto out;
}

- if ((dgst_init(dgst) < 0) || (dgst_verify_init(dgst) < 0)) {
+ if ((dgst_init(dgst, EVP_sha256()) < 0) || (dgst_verify_init(dgst) < 0)) {
status = -ENOKEY;
goto out;
}
@@ -331,9 +331,10 @@ out:
return status;
}
#endif
-struct swupdate_digest *swupdate_HASH_init(void)
+struct swupdate_digest *swupdate_HASH_init(const char *SHAlength)
{
struct swupdate_digest *dgst;
+ const EVP_MD *md;
int ret;

dgst = calloc(1, sizeof(*dgst));
@@ -341,6 +342,11 @@ struct swupdate_digest *swupdate_HASH_init(void)
return NULL;
}

+ if ((!SHAlength) || strcmp(SHAlength, "sha1"))
+ md = EVP_sha256();
+ else
+ md = EVP_sha1();
+
dgst->ctx = EVP_MD_CTX_create();
if(dgst->ctx == NULL) {
ERROR("EVP_MD_CTX_create failed, error 0x%lx\n", ERR_get_error());
@@ -348,7 +354,7 @@ struct swupdate_digest *swupdate_HASH_init(void)
return NULL;
}

- ret = dgst_init(dgst);
+ ret = dgst_init(dgst, md);
if (ret) {
free(dgst);
return NULL;
@@ -363,7 +369,8 @@ int swupdate_HASH_update(struct swupdate_digest *dgst, unsigned char *buf,
if (!dgst)
return -EFAULT;

- EVP_DigestUpdate (dgst->ctx, buf, len);
+ if (EVP_DigestUpdate (dgst->ctx, buf, len) != 1)
+ return -EIO;

return 0;
}
diff --git a/include/sslapi.h b/include/sslapi.h
index 1df656d..9d02502 100644
--- a/include/sslapi.h
+++ b/include/sslapi.h
@@ -20,12 +20,14 @@
#ifndef _SWUPDATE_SSL_H
#define _SWUPDATE_SSL_H

+#define SHA_DEFAULT "sha256"
+
/*
* openSSL is not mandatory
* Let compile when openSSL is not activated
*/
-#if defined(CONFIG_HASH_VERIFY) || defined(CONFIG_ENCRYPTED_IMAGES)
-
+#if defined(CONFIG_HASH_VERIFY) || defined(CONFIG_ENCRYPTED_IMAGES) || \
+ defined(CONFIG_SURICATTA_SSL)
#include <openssl/bio.h>
#include <openssl/objects.h>
#include <openssl/err.h>
@@ -35,6 +37,7 @@
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/aes.h>
+
#ifdef CONFIG_SIGALG_CMS
#if defined(LIBRESSL_VERSION_NUMBER)
#error "LibreSSL does not support CMS, please select RSA PKCS"
@@ -42,6 +45,7 @@
#include <openssl/cms.h>
#endif
#endif
+
#include <openssl/opensslv.h>

struct swupdate_digest {
@@ -84,7 +88,7 @@ struct swupdate_digest {

#if defined(CONFIG_HASH_VERIFY)
int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile);
-struct swupdate_digest *swupdate_HASH_init(void);
+struct swupdate_digest *swupdate_HASH_init(const char *SHALength);
int swupdate_HASH_update(struct swupdate_digest *dgst, unsigned char *buf,
size_t len);
int swupdate_HASH_final(struct swupdate_digest *dgst, unsigned char *md_value,
@@ -99,8 +103,8 @@ int swupdate_HASH_compare(unsigned char *hash1, unsigned char *hash2);
#define swupdate_dgst_init(sw, keyfile) ( 0 )
#define swupdate_HASH_init(p) ( NULL )
#define swupdate_verify_file(dgst, sigfile, file) ( 0 )
-#define swupdate_HASH_update(p, buf, len)
-#define swupdate_HASH_final(p, result, len)
+#define swupdate_HASH_update(p, buf, len) (-1)
+#define swupdate_HASH_final(p, result, len) (-1)
#define swupdate_HASH_cleanup(sw)
#define swupdate_HASH_compare(hash1,hash2) (0)
#endif
@@ -123,5 +127,12 @@ void swupdate_DECRYPT_cleanup(struct swupdate_digest *dgst);
#define swupdate_DECRYPT_cleanup(p)
#endif

+/*
+ * if openSSL is not selected
+ */
+#ifndef SHA_DIGEST_LENGTH
+#define SHA_DIGEST_LENGTH 20
+#endif
+
#endif

--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:38 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Function can be used outside hawkbit - rename channel_hawkbit_init()
to channel_curl_init().

Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/channel_curl.c | 4 ++--
suricatta/server_hawkbit.c | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 6c16592..073bf16 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -76,7 +76,7 @@ channel_op_res_t channel_set_options(channel_t *this, channel_data_t *channel_da
static void channel_log_effective_url(channel_t *this);

/* Prototypes for "public" functions */
-channel_op_res_t channel_hawkbit_init(void);
+channel_op_res_t channel_curl_init(void);
channel_op_res_t channel_close(channel_t *this);
channel_op_res_t channel_open(channel_t *this, void *cfg);
channel_op_res_t channel_get(channel_t *this, void *data);
@@ -85,7 +85,7 @@ channel_op_res_t channel_put(channel_t *this, void *data);
channel_t *channel_new(void);


-channel_op_res_t channel_hawkbit_init(void)
+channel_op_res_t channel_curl_init(void)
{
#ifdef CONFIG_SURICATTA_SSL
#define CURL_FLAGS CURL_GLOBAL_SSL
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 186e9f0..100f0a0 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -106,7 +106,7 @@ static hawkbit_enums_t hawkbit_enums[] = {
#define ALL_MANDATORY_SET (TENANT_BIT | ID_BIT | URL_BIT)


-extern channel_op_res_t channel_hawkbit_init(void);
+extern channel_op_res_t channel_curl_init(void);
/* Prototypes for "internal" functions */
/* Note that they're not `static` so that they're callable from unit tests. */
json_object *json_get_key(json_object *json_root, const char *key);
@@ -1718,7 +1718,7 @@ server_op_res_t server_start(char *fname, int argc, char *argv[])
return SERVER_EINIT;
}

- if (channel_hawkbit_init() != CHANNEL_OK)
+ if (channel_curl_init() != CHANNEL_OK)
return SERVER_EINIT;

/*
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:40 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Replace CONFIG_SURICATTA_SSL with a runtime variable to reuse
the code with or without SSL enabled.

Replace deprecated functions to compute SHA with EVP as
described in openSSL documentation. SWUpdate has already a set
of wrapper function that should be used instead of accessing
to the underlying library.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/channel_curl.c | 66 ++++++++++++++++++++++------------------------
include/channel_curl.h | 8 +++---
suricatta/server_hawkbit.c | 3 +++
3 files changed, 38 insertions(+), 39 deletions(-)

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 073bf16..257efbe 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -29,12 +29,9 @@
#include <unistd.h>
#include <network_ipc.h>
#include <util.h>
-#ifdef CONFIG_SURICATTA_SSL
-#include <openssl/sha.h>
-#endif
+#include "sslapi.h"
#include "suricatta/channel.h"
#include "channel_curl.h"
-#include "suricatta/suricatta.h"

#define SPEED_LOW_BYTES_SEC 8
#define SPEED_LOW_TIME_SEC 300
@@ -59,9 +56,6 @@ typedef struct {
output_data_t *outdata;
} write_callback_t;

-#ifdef CONFIG_SURICATTA_SSL
-static SHA_CTX checksum_ctx;
-#endif

/* Prototypes for "internal" functions */
/* Note that they're not `static` so that they're callable from unit tests. */
@@ -171,13 +165,17 @@ size_t channel_callback_write_file(void *streamdata, size_t size, size_t nmemb,
if (!data)
return 0;
result_channel_callback_write_file = CHANNEL_OK;
-#ifdef CONFIG_SURICATTA_SSL
- if (SHA1_Update(&checksum_ctx, streamdata, size * nmemb) != 1) {
- ERROR("Updating checksum of chunk failed.\n");
- result_channel_callback_write_file = CHANNEL_EIO;
- return 0;
+
+ if (data->channel_data->usessl) {
+ if (swupdate_HASH_update(data->channel_data->dgst,
+ streamdata,
+ size * nmemb) < 0) {
+ ERROR("Updating checksum of chunk failed.\n");
+ result_channel_callback_write_file = CHANNEL_EIO;
+ return 0;
+ }
}
-#endif
+
if (ipc_send_data(data->output, streamdata, (int)(size * nmemb)) <
0) {
ERROR("Writing into SWUpdate IPC stream failed.\n");
@@ -646,14 +644,15 @@ channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle)
channel_op_res_t result = CHANNEL_OK;
channel_data_t *channel_data = (channel_data_t *)data;

-#ifdef CONFIG_SURICATTA_SSL
- memset(channel_data->sha1hash, 0x0, SHA_DIGEST_LENGTH * 2 + 1);
- if (SHA1_Init(&checksum_ctx) != 1) {
- result = CHANNEL_EINIT;
- ERROR("Cannot initialize sha1 checksum context.\n");
- goto cleanup;
+ if (channel_data->usessl) {
+ memset(channel_data->sha1hash, 0x0, SHA_DIGEST_LENGTH * 2 + 1);
+ channel_data->dgst = swupdate_HASH_init("sha1");
+ if (!channel_data->dgst) {
+ result = CHANNEL_EINIT;
+ ERROR("Cannot initialize sha1 checksum context.\n");
+ return result;
+ }
}
-#endif

if (channel_data->debug) {
curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
@@ -799,18 +798,20 @@ channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle)
goto cleanup_file;
}

-#ifdef CONFIG_SURICATTA_SSL
- unsigned char sha1hash[SHA_DIGEST_LENGTH];
- if (SHA1_Final(sha1hash, &checksum_ctx) != 1) {
- ERROR("Cannot compute checksum.\n");
- goto cleanup_file;
- }
- char sha1hexchar[3];
- for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
- sprintf(sha1hexchar, "%02x", sha1hash[i]);
- strcat(channel_data->sha1hash, sha1hexchar);
+ if (channel_data->usessl) {
+ unsigned char sha1hash[SHA_DIGEST_LENGTH];
+ unsigned int md_len;
+ if (swupdate_HASH_final(channel_data->dgst, sha1hash, &md_len) != 1) {
+ ERROR("Cannot compute checksum.\n");
+ goto cleanup_file;
+ }
+
+ char sha1hexchar[3];
+ for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
+ sprintf(sha1hexchar, "%02x", sha1hash[i]);
+ strcat(channel_data->sha1hash, sha1hexchar);
+ }
}
-#endif

cleanup_file:
/* NOTE ipc_end() calls close() but does not return its error code,
@@ -826,9 +827,6 @@ cleanup_header:
curl_slist_free_all(channel_curl->header);
channel_curl->header = NULL;

-#ifdef CONFIG_SURICATTA_SSL
-cleanup:
-#endif
return result;
}

diff --git a/include/channel_curl.h b/include/channel_curl.h
index ed262fc..98240a9 100644
--- a/include/channel_curl.h
+++ b/include/channel_curl.h
@@ -20,9 +20,7 @@
#pragma once
#include <json-c/json.h>
#include <stdio.h>
-#ifdef CONFIG_SURICATTA_SSL
-#include <openssl/sha.h>
-#endif
+#include "sslapi.h"

/* hawkBit Channel Implementation Private Header File.
*
@@ -55,9 +53,9 @@ typedef struct {
unsigned int method;
unsigned int retries;
bool debug;
+ bool usessl;
bool strictssl;
int (*checkdwl)(void);
-#ifdef CONFIG_SURICATTA_SSL
+ struct swupdate_digest *dgst;
char sha1hash[SHA_DIGEST_LENGTH * 2 + 1];
-#endif
} channel_data_t;
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 100f0a0..59f4cb3 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -149,6 +149,9 @@ static channel_data_t channel_data_defaults = {.debug = false,
.retries = DEFAULT_RESUME_TRIES,
.retry_sleep =
DEFAULT_RESUME_DELAY,
+#ifdef CONFIG_SURICATTA_SSL
+ .usessl = true,
+#endif
.strictssl = true};

static struct timeval server_time;
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:41 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
A debug flag is allowed for each connection made via libcurl. Check if the
debug flag is set before calling DEBUG(/).

Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/channel_curl.c | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 257efbe..705b887 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -538,8 +538,10 @@ static channel_op_res_t channel_post_method(channel_t *this, void *data)
http_response_code);
goto cleanup_header;
}
- TRACE("Channel put operation returned HTTP status code %ld.\n",
- http_response_code);
+ if (channel_data->debug) {
+ TRACE("Channel put operation returned HTTP status code %ld.\n",
+ http_response_code);
+ }

cleanup_header:
curl_easy_reset(channel_curl->handle);
@@ -790,8 +792,10 @@ channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle)
http_response_code);
goto cleanup_file;
}
- TRACE("Channel operation returned HTTP status code %ld.\n",
- http_response_code);
+ if (channel_data->debug) {
+ TRACE("Channel operation returned HTTP status code %ld.\n",
+ http_response_code);
+ }

if (result_channel_callback_write_file != CHANNEL_OK) {
result = CHANNEL_EIO;
@@ -822,6 +826,10 @@ cleanup_file:
ERROR("Channel error while closing download target handle: '%s'\n",
strerror(errno));
}
+ if (channel_data->dgst) {
+ swupdate_HASH_cleanup(channel_data->dgst);
+ }
+
cleanup_header:
curl_easy_reset(channel_curl->handle);
curl_slist_free_all(channel_curl->header);
@@ -881,7 +889,9 @@ channel_op_res_t channel_get(channel_t *this, void *data)
goto cleanup_chunk;
}

- DEBUG("Trying to GET %s", channel_data->url);
+ if (channel_data->debug) {
+ DEBUG("Trying to GET %s", channel_data->url);
+ }
CURLcode curlrc = curl_easy_perform(channel_curl->handle);
if (curlrc != CURLE_OK) {
ERROR("Channel get operation failed (%d): '%s'\n", curlrc,
@@ -890,7 +900,9 @@ channel_op_res_t channel_get(channel_t *this, void *data)
goto cleanup_chunk;
}

- channel_log_effective_url(this);
+ if (channel_data->debug) {
+ channel_log_effective_url(this);
+ }

long http_response_code;
if ((result = channel_map_http_code(this, &http_response_code)) !=
@@ -905,8 +917,10 @@ channel_op_res_t channel_get(channel_t *this, void *data)
}
goto cleanup_chunk;
}
- TRACE("Channel operation returned HTTP status code %ld.\n",
- http_response_code);
+ if (channel_data->debug) {
+ TRACE("Channel operation returned HTTP status code %ld.\n",
+ http_response_code);
+ }

assert(channel_data->json_reply == NULL);
enum json_tokener_error json_res;
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:42 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
After moving to allocated channel, there is no need to
export the channels function.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
include/suricatta/channel.h | 5 -----
1 file changed, 5 deletions(-)

diff --git a/include/suricatta/channel.h b/include/suricatta/channel.h
index 3b20c14..98fc851 100644
--- a/include/suricatta/channel.h
+++ b/include/suricatta/channel.h
@@ -36,9 +36,4 @@ struct channel {
void *priv;
};

-extern channel_op_res_t channel_open(channel_t *this, void *cfg);
-extern channel_op_res_t channel_close(channel_t *this);
-extern channel_op_res_t channel_put(channel_t *this, void *data);
-extern channel_op_res_t channel_get(channel_t *this, void *data);
-extern channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle);
channel_t *channel_new(void);
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:43 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Open a channel via curl can be reused outside the Hawkbit server.
Rename the file dropping the hawkbit suffix and move the code outside
of suricatta.

The code can be generalised to be used by any code that
uses libcurl and JSON to communicate with a server.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/Makefile | 1 +
corelib/channel_curl.c | 941 ++++++++++++++++++++++++++++++++++++++++++++
include/channel_curl.h | 63 +++
suricatta/Makefile | 2 +-
suricatta/channel_hawkbit.c | 941 --------------------------------------------
suricatta/channel_hawkbit.h | 63 ---
suricatta/server_hawkbit.c | 2 +-
7 files changed, 1007 insertions(+), 1006 deletions(-)
create mode 100644 corelib/channel_curl.c
create mode 100644 include/channel_curl.h
delete mode 100644 suricatta/channel_hawkbit.c
delete mode 100644 suricatta/channel_hawkbit.h

diff --git a/corelib/Makefile b/corelib/Makefile
index 16767b2..693ad47 100644
--- a/corelib/Makefile
+++ b/corelib/Makefile
@@ -13,3 +13,4 @@ lib-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt.o
lib-$(CONFIG_LIBCONFIG) += swupdate_settings.o \
parsing_library_libconfig.o
lib-$(CONFIG_JSON) += parsing_library_libjson.o
+lib-$(CONFIG_CURL) += channel_curl.o
diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
new file mode 100644
index 0000000..6c16592
--- /dev/null
+++ b/corelib/channel_curl.c
@@ -0,0 +1,941 @@
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2016, Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <curl/curl.h>
+#include <json-c/json.h>
+#include <generated/autoconf.h>
+#include <unistd.h>
+#include <network_ipc.h>
+#include <util.h>
+#ifdef CONFIG_SURICATTA_SSL
+#include <openssl/sha.h>
+#endif
+#include "suricatta/channel.h"
+#include "channel_curl.h"
+#include "suricatta/suricatta.h"
+
+#define SPEED_LOW_BYTES_SEC 8
+#define SPEED_LOW_TIME_SEC 300
+#define KEEPALIVE_DELAY 204L
+#define KEEPALIVE_INTERVAL 120L
+
+typedef struct {
+ char *memory;
+ size_t size;
+} output_data_t;
+
+typedef struct {
+ char *proxy;
+ char *effective_url;
+ CURL *handle;
+ struct curl_slist *header;
+} channel_curl_t;
+
+typedef struct {
+ channel_data_t *channel_data;
+ int output;
+ output_data_t *outdata;
+} write_callback_t;
+
+#ifdef CONFIG_SURICATTA_SSL
+static SHA_CTX checksum_ctx;
+#endif
+
+/* Prototypes for "internal" functions */
+/* Note that they're not `static` so that they're callable from unit tests. */
+size_t channel_callback_write_file(void *streamdata, size_t size, size_t nmemb,
+ write_callback_t *data);
+size_t channel_callback_membuffer(void *streamdata, size_t size, size_t nmemb,
+ write_callback_t *data);
+channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code);
+channel_op_res_t channel_map_curl_error(CURLcode res);
+channel_op_res_t channel_set_options(channel_t *this, channel_data_t *channel_data,
+ channel_method_t method);
+static void channel_log_effective_url(channel_t *this);
+
+/* Prototypes for "public" functions */
+channel_op_res_t channel_hawkbit_init(void);
+channel_op_res_t channel_close(channel_t *this);
+channel_op_res_t channel_open(channel_t *this, void *cfg);
+channel_op_res_t channel_get(channel_t *this, void *data);
+channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle);
+channel_op_res_t channel_put(channel_t *this, void *data);
+channel_t *channel_new(void);
+
+
+channel_op_res_t channel_hawkbit_init(void)
+{
+#ifdef CONFIG_SURICATTA_SSL
+#define CURL_FLAGS CURL_GLOBAL_SSL
+#else
+#define CURL_FLAGS CURL_GLOBAL_NOTHING
+#endif
+ CURLcode curlrc = curl_global_init(CURL_FLAGS);
+ if (curlrc != CURLE_OK) {
+ ERROR("Initialization of channel failed (%d): '%s'\n", curlrc,
+ curl_easy_strerror(curlrc));
+ return CHANNEL_EINIT;
+ }
+#undef CURL_FLAGS
+ return CHANNEL_OK;
+}
+
+channel_t *channel_new(void)
+{
+ channel_t *newchan = (channel_t *)calloc(1, sizeof(*newchan) +
+ sizeof(channel_curl_t));
+
+ if (newchan) {
+ newchan->priv = (void *)newchan + sizeof(*newchan);
+ newchan->open = &channel_open;
+ newchan->close = &channel_close;
+ newchan->get = &channel_get;
+ newchan->get_file = &channel_get_file;
+ newchan->put = &channel_put;
+ }
+
+ return newchan;
+}
+
+channel_op_res_t channel_close(channel_t *this)
+{
+ channel_curl_t *channel_curl = this->priv;
+
+ if ((channel_curl->proxy != NULL) &&
+ (channel_curl->proxy != USE_PROXY_ENV)) {
+ free(channel_curl->proxy);
+ }
+ if (channel_curl->handle == NULL) {
+ return CHANNEL_OK;
+ }
+ curl_easy_cleanup(channel_curl->handle);
+ channel_curl->handle = NULL;
+
+ return CHANNEL_OK;
+}
+
+channel_op_res_t channel_open(channel_t *this, void *cfg)
+{
+ assert(this != NULL);
+ channel_curl_t *channel_curl = this->priv;
+ assert(channel_curl->handle == NULL);
+
+ channel_data_t *channel_cfg = (channel_data_t *)cfg;
+
+ if ((channel_cfg != NULL) && (channel_cfg->proxy != NULL)) {
+ channel_curl->proxy = channel_cfg->proxy == USE_PROXY_ENV
+ ? USE_PROXY_ENV
+ : strdup(channel_cfg->proxy);
+ if (channel_curl->proxy == NULL) {
+ return CHANNEL_EINIT;
+ }
+ }
+
+ if ((channel_curl->handle = curl_easy_init()) == NULL) {
+ ERROR("Initialization of channel failed.\n");
+ return CHANNEL_EINIT;
+ }
+
+ return CHANNEL_OK;
+}
+
+static channel_op_res_t result_channel_callback_write_file;
+size_t channel_callback_write_file(void *streamdata, size_t size, size_t nmemb,
+ write_callback_t *data)
+{
+ if (!nmemb) {
+ return 0;
+ }
+ if (!data)
+ return 0;
+ result_channel_callback_write_file = CHANNEL_OK;
+#ifdef CONFIG_SURICATTA_SSL
+ if (SHA1_Update(&checksum_ctx, streamdata, size * nmemb) != 1) {
+ ERROR("Updating checksum of chunk failed.\n");
+ result_channel_callback_write_file = CHANNEL_EIO;
+ return 0;
+ }
+#endif
+ if (ipc_send_data(data->output, streamdata, (int)(size * nmemb)) <
+ 0) {
+ ERROR("Writing into SWUpdate IPC stream failed.\n");
+ result_channel_callback_write_file = CHANNEL_EIO;
+ return 0;
+ }
+
+ if (data->channel_data->checkdwl && data->channel_data->checkdwl())
+ return 0;
+ /*
+ * Now check if there is a callback from the server
+ * during the download
+ */
+
+ return size * nmemb;
+}
+
+size_t channel_callback_membuffer(void *streamdata, size_t size, size_t nmemb,
+ write_callback_t *data)
+{
+ if (!nmemb) {
+ return 0;
+ }
+ if (!data) {
+ return 0;
+ }
+
+ size_t realsize = size * nmemb;
+ output_data_t *mem = data->outdata;
+
+ mem->memory = realloc(mem->memory, mem->size + realsize + 1);
+ if (mem->memory == NULL) {
+ ERROR("Channel get operation failed with OOM\n");
+ return 0;
+ }
+ memcpy(&(mem->memory[mem->size]), streamdata, realsize);
+ mem->size += realsize;
+ mem->memory[mem->size] = 0;
+ return realsize;
+}
+
+static void channel_log_effective_url(channel_t *this)
+{
+ channel_curl_t *channel_curl = this->priv;
+
+ CURLcode curlrc =
+ curl_easy_getinfo(channel_curl->handle, CURLINFO_EFFECTIVE_URL,
+ &channel_curl->effective_url);
+ if (curlrc != CURLE_OK && curlrc == CURLE_UNKNOWN_OPTION) {
+ ERROR("Get channel's effective URL response unsupported by "
+ "libcURL %s.\n",
+ LIBCURL_VERSION);
+ return;
+ }
+ TRACE("Channel's effective URL resolved to %s\n",
+ channel_curl->effective_url);
+}
+
+channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code)
+{
+ channel_curl_t *channel_curl = this->priv;
+ CURLcode curlrc =
+ curl_easy_getinfo(channel_curl->handle, CURLINFO_RESPONSE_CODE,
+ http_response_code);
+ if (curlrc != CURLE_OK && curlrc == CURLE_UNKNOWN_OPTION) {
+ ERROR("Get channel HTTP response code unsupported by libcURL "
+ "%s.\n",
+ LIBCURL_VERSION);
+ return CHANNEL_EINIT;
+ }
+ switch (*http_response_code) {
+ case 0: /* libcURL: no server response code has been received yet */
+ DEBUG("No HTTP response code has been received yet!\n");
+ return CHANNEL_EBADMSG;
+ case 401: /* Unauthorized. The request requires user authentication. */
+ case 403: /* Forbidden. */
+ case 405: /* Method not Allowed. */
+ case 407: /* Proxy Authentication Required */
+ case 503: /* Service Unavailable */
+ return CHANNEL_EACCES;
+ case 400: /* Bad Request, e.g., invalid parameters */
+ case 404: /* Wrong URL */
+ case 406: /* Not acceptable. Accept header is not response compliant */
+ case 443: /* Connection refused */
+ return CHANNEL_EBADMSG;
+ case 429: /* Bad Request, i.e., too many requests. Try again later. */
+ return CHANNEL_EAGAIN;
+ case 200:
+ case 206:
+ return CHANNEL_OK;
+ case 500:
+ return CHANNEL_EBADMSG;
+ default:
+ ERROR("Channel operation returned unhandled HTTP error code "
+ "%ld\n",
+ *http_response_code);
+ return CHANNEL_EBADMSG;
+ }
+}
+
+channel_op_res_t channel_map_curl_error(CURLcode res)
+{
+ switch (res) {
+ case CURLE_NOT_BUILT_IN:
+ case CURLE_BAD_FUNCTION_ARGUMENT:
+ case CURLE_UNKNOWN_OPTION:
+ case CURLE_SSL_ENGINE_NOTFOUND:
+ case CURLE_SSL_ENGINE_SETFAILED:
+ case CURLE_SSL_CERTPROBLEM:
+ case CURLE_SSL_CIPHER:
+ case CURLE_SSL_ENGINE_INITFAILED:
+ case CURLE_SSL_CACERT_BADFILE:
+ case CURLE_SSL_CRL_BADFILE:
+ case CURLE_SSL_ISSUER_ERROR:
+#if LIBCURL_VERSION_NUM >= 0x072900
+ case CURLE_SSL_INVALIDCERTSTATUS:
+#endif
+#if LIBCURL_VERSION_NUM >= 0x072700
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+#endif
+ return CHANNEL_EINIT;
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_INTERFACE_FAILED:
+ case CURLE_SSL_CONNECT_ERROR:
+ case CURLE_PEER_FAILED_VERIFICATION:
+ case CURLE_SSL_CACERT:
+ case CURLE_USE_SSL_FAILED:
+ return CHANNEL_ENONET;
+ case CURLE_OPERATION_TIMEDOUT:
+ case CURLE_SEND_ERROR:
+ case CURLE_RECV_ERROR:
+ case CURLE_GOT_NOTHING:
+ case CURLE_HTTP_POST_ERROR:
+ case CURLE_PARTIAL_FILE:
+ return CHANNEL_EAGAIN;
+ case CURLE_OUT_OF_MEMORY:
+ return CHANNEL_ENOMEM;
+ case CURLE_REMOTE_FILE_NOT_FOUND:
+ return CHANNEL_ENOENT;
+ case CURLE_FILESIZE_EXCEEDED:
+ case CURLE_ABORTED_BY_CALLBACK:
+ case CURLE_WRITE_ERROR:
+ case CURLE_CHUNK_FAILED:
+ case CURLE_SSL_SHUTDOWN_FAILED:
+ return CHANNEL_EIO;
+ case CURLE_TOO_MANY_REDIRECTS:
+ return CHANNEL_ELOOP;
+ case CURLE_BAD_CONTENT_ENCODING:
+ case CURLE_CONV_FAILED:
+ case CURLE_CONV_REQD:
+ return CHANNEL_EILSEQ;
+ case CURLE_REMOTE_ACCESS_DENIED:
+ case CURLE_LOGIN_DENIED:
+ return CHANNEL_EACCES;
+ case CURLE_OK:
+ return CHANNEL_OK;
+ default:
+ return CHANNEL_EINIT;
+ }
+}
+
+channel_op_res_t channel_set_options(channel_t *this,
+ channel_data_t *channel_data,
+ channel_method_t method)
+{
+ channel_curl_t *channel_curl = this->priv;
+ channel_op_res_t result = CHANNEL_OK;
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_URL,
+ channel_data->url) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_USERAGENT,
+ "libcurl-agent/1.0") != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_LOW_SPEED_LIMIT,
+ SPEED_LOW_BYTES_SEC) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_LOW_SPEED_TIME,
+ SPEED_LOW_TIME_SEC) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_HTTPHEADER,
+ channel_curl->header) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_MAXREDIRS, -1) !=
+ CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_FOLLOWLOCATION, 1) !=
+ CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTP | CURLPROTO_HTTPS) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_CAINFO,
+ channel_data->cafile) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSLKEY,
+ channel_data->sslkey) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSLCERT,
+ channel_data->sslcert) != CURLE_OK)) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+
+ if (channel_data->strictssl == true) {
+ if ((curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSL_VERIFYHOST,
+ 2L) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSL_VERIFYPEER,
+ 1L) != CURLE_OK)) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ }
+ else {
+ if ((curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSL_VERIFYHOST,
+ 0L) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_SSL_VERIFYPEER,
+ 0L) != CURLE_OK)) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ }
+
+ switch (method) {
+ case CHANNEL_GET:
+ if (curl_easy_setopt(channel_curl->handle, CURLOPT_CUSTOMREQUEST,
+ "GET") != CURLE_OK) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ break;
+ case CHANNEL_PUT:
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_PUT, 1L) !=
+ CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_UPLOAD, 1L) !=
+ CURLE_OK)) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ break;
+ case CHANNEL_POST:
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_POST, 1L) !=
+ CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_POSTFIELDS,
+ channel_data->json_string) != CURLE_OK)) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ if (channel_data->debug) {
+ TRACE("Post JSON: %s\n", channel_data->json_string);
+ }
+ break;
+ }
+
+ if (channel_curl->proxy != NULL) {
+ if (channel_curl->proxy != USE_PROXY_ENV) {
+ if (curl_easy_setopt(
+ channel_curl->handle, CURLOPT_PROXY,
+ channel_curl->proxy) != CURLE_OK) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ }
+ if (curl_easy_setopt(channel_curl->handle, CURLOPT_NETRC,
+ CURL_NETRC_OPTIONAL) != CURLE_OK) {
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ }
+
+ CURLcode curlrc =
+ curl_easy_setopt(channel_curl->handle, CURLOPT_TCP_KEEPALIVE, 1L);
+ if (curlrc == CURLE_OK) {
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_TCP_KEEPIDLE,
+ KEEPALIVE_DELAY) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle,
+ CURLOPT_TCP_KEEPINTVL,
+ KEEPALIVE_INTERVAL) != CURLE_OK)) {
+ ERROR("TCP Keep-alive interval and delay could not be "
+ "configured.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ } else {
+ if (curlrc != CURLE_UNKNOWN_OPTION) {
+ ERROR("Channel could not be configured to sent "
+ "keep-alive probes.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ return result;
+}
+
+static size_t put_read_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ channel_data_t *channel_data = (channel_data_t *)data;
+ unsigned int bytes;
+ size_t n;
+
+ /* Check data to be sent */
+ bytes = strlen(channel_data->json_string) - channel_data->offs;
+
+ if (!bytes)
+ return 0;
+
+ n = min(bytes, size * nmemb);
+
+ memcpy(ptr, &channel_data->json_string[channel_data->offs], n);
+ channel_data->offs += n;
+
+ return n;
+}
+
+static channel_op_res_t channel_post_method(channel_t *this, void *data)
+{
+ channel_curl_t *channel_curl = this->priv;
+ assert(data != NULL);
+ assert(channel_curl->handle != NULL);
+
+ channel_op_res_t result = CHANNEL_OK;
+ channel_data_t *channel_data = (channel_data_t *)data;
+
+ if (channel_data->debug) {
+ curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
+ }
+
+ if (((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Content-Type: application/json")) ==
+ NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Accept: application/json")) == NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "charsets: utf-8")) == NULL)) {
+ ERROR("Set channel header failed.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup_header;
+ }
+
+ if ((result = channel_set_options(this, channel_data, CHANNEL_POST)) !=
+ CHANNEL_OK) {
+ ERROR("Set channel option failed.\n");
+ goto cleanup_header;
+ }
+
+ CURLcode curlrc = curl_easy_perform(channel_curl->handle);
+ if (curlrc != CURLE_OK) {
+ ERROR("Channel put operation failed (%d): '%s'\n", curlrc,
+ curl_easy_strerror(curlrc));
+ result = channel_map_curl_error(curlrc);
+ goto cleanup_header;
+ }
+
+ channel_log_effective_url(this);
+
+ long http_response_code;
+ if ((result = channel_map_http_code(this, &http_response_code)) !=
+ CHANNEL_OK) {
+ ERROR("Channel operation returned HTTP error code %ld.\n",
+ http_response_code);
+ goto cleanup_header;
+ }
+ TRACE("Channel put operation returned HTTP status code %ld.\n",
+ http_response_code);
+
+cleanup_header:
+ curl_easy_reset(channel_curl->handle);
+ curl_slist_free_all(channel_curl->header);
+ channel_curl->header = NULL;
+
+ return result;
+}
+
+static channel_op_res_t channel_put_method(channel_t *this, void *data)
+{
+ channel_curl_t *channel_curl = this->priv;
+ assert(data != NULL);
+ assert(channel_curl->handle != NULL);
+
+ channel_op_res_t result = CHANNEL_OK;
+ channel_data_t *channel_data = (channel_data_t *)data;
+ channel_data->offs = 0;
+
+ if (channel_data->debug) {
+ curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
+ }
+
+ if (((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Content-Type: application/json")) ==
+ NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Accept: application/json")) == NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "charsets: utf-8")) == NULL)) {
+ ERROR("Set channel header failed.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup_header;
+ }
+
+ if ((result = channel_set_options(this, channel_data, CHANNEL_PUT)) !=
+ CHANNEL_OK) {
+ ERROR("Set channel option failed.\n");
+ goto cleanup_header;
+ }
+
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_READFUNCTION, put_read_callback) !=
+ CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_INFILESIZE_LARGE,
+ (curl_off_t)strlen(channel_data->json_string)) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_READDATA, channel_data) !=
+ CURLE_OK)) {
+ ERROR("Set channel option failed.\n");
+ goto cleanup_header;
+ }
+
+ CURLcode curlrc = curl_easy_perform(channel_curl->handle);
+ if (curlrc != CURLE_OK) {
+ ERROR("Channel put operation failed (%d): '%s'\n", curlrc,
+ curl_easy_strerror(curlrc));
+ result = channel_map_curl_error(curlrc);
+ goto cleanup_header;
+ }
+
+ channel_log_effective_url(this);
+
+ long http_response_code;
+ if ((result = channel_map_http_code(this, &http_response_code)) != CHANNEL_OK) {
+ ERROR("Channel operation returned HTTP error code %ld.\n",
+ http_response_code);
+ goto cleanup_header;
+ }
+ TRACE("Channel put operation returned HTTP error code %ld.\n",
+ http_response_code);
+
+cleanup_header:
+ curl_easy_reset(channel_curl->handle);
+ curl_slist_free_all(channel_curl->header);
+ channel_curl->header = NULL;
+
+ return result;
+}
+
+channel_op_res_t channel_put(channel_t *this, void *data)
+{
+ assert(data != NULL);
+
+ channel_data_t *channel_data = (channel_data_t *)data;
+
+ switch (channel_data->method) {
+ case CHANNEL_PUT:
+ return channel_put_method(this, data);
+ case CHANNEL_POST:
+ return channel_post_method(this, data);
+ default:
+ TRACE("Channel method (POST, PUT) is not set !\n");
+ return CHANNEL_EINIT;
+ }
+}
+
+channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle)
+{
+ channel_curl_t *channel_curl = this->priv;
+ assert(data != NULL);
+ assert(channel_curl->handle != NULL);
+
+ channel_op_res_t result = CHANNEL_OK;
+ channel_data_t *channel_data = (channel_data_t *)data;
+
+#ifdef CONFIG_SURICATTA_SSL
+ memset(channel_data->sha1hash, 0x0, SHA_DIGEST_LENGTH * 2 + 1);
+ if (SHA1_Init(&checksum_ctx) != 1) {
+ result = CHANNEL_EINIT;
+ ERROR("Cannot initialize sha1 checksum context.\n");
+ goto cleanup;
+ }
+#endif
+
+ if (channel_data->debug) {
+ curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
+ }
+
+ if (((channel_curl->header = curl_slist_append(
+ channel_curl->header,
+ "Content-Type: application/octet-stream")) == NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Accept: application/octet-stream")) ==
+ NULL)) {
+ result = CHANNEL_EINIT;
+ ERROR("Set channel header failed.\n");
+ goto cleanup_header;
+ }
+
+ if ((result = channel_set_options(this, channel_data, CHANNEL_GET)) !=
+ CHANNEL_OK) {
+ ERROR("Set channel option failed.\n");
+ goto cleanup_header;
+ }
+
+ if (file_handle == FD_USE_IPC) {
+ for (int retries = 3; retries >= 0; retries--) {
+ file_handle = ipc_inst_start_ext(SOURCE_SURICATTA,
+ channel_data->info == NULL ? 0 : strlen(channel_data->info),
+ channel_data->info);
+ if (file_handle > 0)
+ break;
+ sleep(1);
+ }
+ if (file_handle < 0) {
+ ERROR("Cannot open SWUpdate IPC stream: %s\n", strerror(errno));
+ result = CHANNEL_EIO;
+ goto cleanup_header;
+ }
+ } else {
+ assert(file_handle > 0);
+ }
+
+ write_callback_t wrdata;
+ wrdata.channel_data = channel_data;
+ wrdata.output = file_handle;
+ result_channel_callback_write_file = CHANNEL_OK;
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEFUNCTION,
+ channel_callback_write_file) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEDATA,
+ &wrdata) != CURLE_OK)) {
+ ERROR("Cannot setup file writer callback function.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup_file;
+ }
+
+ /* Depending on the architecture and libcurl, this is at
+ * least 2^32 - 1 bytes, i.e., enough to account for a 4GiB
+ * download. Although 32-Bit curl defines `curl_off_t` as
+ * (signed) long long, use unsigned here, but it wastes some
+ * bits on 64-Bit.
+ */
+ unsigned long long int total_bytes_downloaded = 0;
+ unsigned char try_count = 0;
+ CURLcode curlrc = CURLE_OK;
+ do {
+ if (try_count > 0) {
+ if (channel_data->retries == 0) {
+ ERROR(
+ "Channel get operation failed (%d): '%s'\n",
+ curlrc, curl_easy_strerror(curlrc));
+ result = channel_map_curl_error(curlrc);
+ goto cleanup_file;
+ }
+
+ if (try_count > channel_data->retries) {
+ ERROR("Channel get operation aborted because "
+ "of too many failed download attempts "
+ "(%d).\n",
+ channel_data->retries);
+ result = CHANNEL_ELOOP;
+ goto cleanup_file;
+ }
+
+ DEBUG("Channel connection interrupted, trying resume "
+ "after %llu bytes.",
+ total_bytes_downloaded);
+ if (curl_easy_setopt(
+ channel_curl->handle, CURLOPT_RESUME_FROM_LARGE,
+ total_bytes_downloaded) != CURLE_OK) {
+ ERROR("Could not set Channel resume seek (%d): "
+ "'%s'\n",
+ curlrc, curl_easy_strerror(curlrc));
+ result = channel_map_curl_error(curlrc);
+ goto cleanup_file;
+ }
+ TRACE("Channel sleeps for %d seconds now.",
+ channel_data->retry_sleep);
+ if (sleep(channel_data->retry_sleep) > 0) {
+ TRACE("Channel's sleep got interrupted, "
+ "retrying nonetheless now.");
+ }
+ TRACE("Channel awakened from sleep.");
+ }
+
+ curlrc = curl_easy_perform(channel_curl->handle);
+ result = channel_map_curl_error(curlrc);
+ if ((result != CHANNEL_OK) && (result != CHANNEL_EAGAIN)) {
+ ERROR("Channel operation returned error (%d): '%s'\n",
+ curlrc, curl_easy_strerror(curlrc));
+ goto cleanup_file;
+ }
+
+ double bytes_downloaded;
+ CURLcode resdlprogress = curl_easy_getinfo(
+ channel_curl->handle, CURLINFO_SIZE_DOWNLOAD,
+ &bytes_downloaded);
+ if (resdlprogress != CURLE_OK) {
+ ERROR("Channel does not report bytes downloaded (%d): "
+ "'%s'\n",
+ resdlprogress, curl_easy_strerror(resdlprogress));
+ result = channel_map_curl_error(resdlprogress);
+ goto cleanup_file;
+ }
+ total_bytes_downloaded += bytes_downloaded;
+
+ } while (++try_count && (result != CHANNEL_OK));
+
+ channel_log_effective_url(this);
+
+ DEBUG("Channel downloaded %llu bytes ~ %llu MiB.\n",
+ total_bytes_downloaded, total_bytes_downloaded / 1024 / 1024);
+
+ long http_response_code;
+ if ((result = channel_map_http_code(this, &http_response_code)) !=
+ CHANNEL_OK) {
+ ERROR("Channel operation returned HTTP error code %ld.\n",
+ http_response_code);
+ goto cleanup_file;
+ }
+ TRACE("Channel operation returned HTTP status code %ld.\n",
+ http_response_code);
+
+ if (result_channel_callback_write_file != CHANNEL_OK) {
+ result = CHANNEL_EIO;
+ goto cleanup_file;
+ }
+
+#ifdef CONFIG_SURICATTA_SSL
+ unsigned char sha1hash[SHA_DIGEST_LENGTH];
+ if (SHA1_Final(sha1hash, &checksum_ctx) != 1) {
+ ERROR("Cannot compute checksum.\n");
+ goto cleanup_file;
+ }
+ char sha1hexchar[3];
+ for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
+ sprintf(sha1hexchar, "%02x", sha1hash[i]);
+ strcat(channel_data->sha1hash, sha1hexchar);
+ }
+#endif
+
+cleanup_file:
+ /* NOTE ipc_end() calls close() but does not return its error code,
+ * so use close() here directly to issue an error in case.
+ * Also, for a given file handle, calling ipc_end() would make
+ * no semantic sense. */
+ if (close(file_handle) != 0) {
+ ERROR("Channel error while closing download target handle: '%s'\n",
+ strerror(errno));
+ }
+cleanup_header:
+ curl_easy_reset(channel_curl->handle);
+ curl_slist_free_all(channel_curl->header);
+ channel_curl->header = NULL;
+
+#ifdef CONFIG_SURICATTA_SSL
+cleanup:
+#endif
+ return result;
+}
+
+channel_op_res_t channel_get(channel_t *this, void *data)
+{
+ channel_curl_t *channel_curl = this->priv;
+ assert(data != NULL);
+ assert(channel_curl.handle != NULL);
+
+ channel_op_res_t result = CHANNEL_OK;
+ channel_data_t *channel_data = (channel_data_t *)data;
+
+ if (channel_data->debug) {
+ curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
+ }
+
+ if (((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Content-Type: application/json")) ==
+ NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "Accept: application/json")) == NULL) ||
+ ((channel_curl->header = curl_slist_append(
+ channel_curl->header, "charsets: utf-8")) == NULL)) {
+ result = CHANNEL_EINIT;
+ ERROR("Set channel header failed.\n");
+ goto cleanup_header;
+ }
+
+ if ((result = channel_set_options(this, channel_data, CHANNEL_GET)) !=
+ CHANNEL_OK) {
+ ERROR("Set channel option failed.\n");
+ goto cleanup_header;
+ }
+
+ output_data_t chunk = {.memory = NULL, .size = 0};
+ if ((chunk.memory = malloc(1)) == NULL) {
+ ERROR("Channel buffer reservation failed with OOM.\n");
+ result = CHANNEL_ENOMEM;
+ goto cleanup_header;
+ }
+
+ write_callback_t wrdata;
+ wrdata.channel_data = channel_data;
+ wrdata.outdata = &chunk;
+
+ if ((curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEFUNCTION,
+ channel_callback_membuffer) != CURLE_OK) ||
+ (curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEDATA,
+ (void *)&wrdata) != CURLE_OK)) {
+ ERROR("Cannot setup memory buffer writer callback function.\n");
+ result = CHANNEL_EINIT;
+ goto cleanup_chunk;
+ }
+
+ DEBUG("Trying to GET %s", channel_data->url);
+ CURLcode curlrc = curl_easy_perform(channel_curl->handle);
+ if (curlrc != CURLE_OK) {
+ ERROR("Channel get operation failed (%d): '%s'\n", curlrc,
+ curl_easy_strerror(curlrc));
+ result = channel_map_curl_error(curlrc);
+ goto cleanup_chunk;
+ }
+
+ channel_log_effective_url(this);
+
+ long http_response_code;
+ if ((result = channel_map_http_code(this, &http_response_code)) !=
+ CHANNEL_OK) {
+ ERROR("Channel operation returned HTTP error code %ld.\n",
+ http_response_code);
+ if (http_response_code == 500) {
+ DEBUG("The error's message is: '%s'\n", chunk.memory);
+ }
+ if (http_response_code == 404) {
+ DEBUG("The error's message is: '%s'\n", chunk.memory);
+ }
+ goto cleanup_chunk;
+ }
+ TRACE("Channel operation returned HTTP status code %ld.\n",
+ http_response_code);
+
+ assert(channel_data->json_reply == NULL);
+ enum json_tokener_error json_res;
+ struct json_tokener *json_tokenizer = json_tokener_new();
+ do {
+ channel_data->json_reply = json_tokener_parse_ex(
+ json_tokenizer, chunk.memory, (int)chunk.size);
+ } while ((json_res = json_tokener_get_error(json_tokenizer)) ==
+ json_tokener_continue);
+ if (json_res != json_tokener_success) {
+ ERROR("Error while parsing channel's returned JSON data: %s\n",
+ json_tokener_error_desc(json_res));
+ result = CHANNEL_EBADMSG;
+ goto cleanup_json_tokenizer;
+ }
+ if (channel_data->debug) {
+ TRACE("Get JSON: %s\n", chunk.memory);
+ }
+
+cleanup_json_tokenizer:
+ json_tokener_free(json_tokenizer);
+cleanup_chunk:
+ chunk.memory != NULL ? free(chunk.memory) : (void)0;
+cleanup_header:
+ curl_easy_reset(channel_curl->handle);
+ curl_slist_free_all(channel_curl->header);
+ channel_curl->header = NULL;
+
+ return result;
+}
diff --git a/include/channel_curl.h b/include/channel_curl.h
new file mode 100644
index 0000000..ed262fc
--- /dev/null
+++ b/include/channel_curl.h
@@ -0,0 +1,63 @@
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2016, Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#pragma once
+#include <json-c/json.h>
+#include <stdio.h>
+#ifdef CONFIG_SURICATTA_SSL
+#include <openssl/sha.h>
+#endif
+
+/* hawkBit Channel Implementation Private Header File.
+ *
+ * This is a "private" header for testability, i.e., the declarations and
+ * definitions herein should be used by `server_hawkbit.c`, `channel_hawkbit.c`,
+ * and unit tests only.
+ */
+
+typedef enum {
+ CHANNEL_GET,
+ CHANNEL_POST,
+ CHANNEL_PUT,
+} channel_method_t;
+
+#define FD_USE_IPC -2
+
+#define USE_PROXY_ENV (char *)0x11
+
+typedef struct {
+ char *url;
+ char *json_string;
+ json_object *json_reply;
+ char *cafile;
+ char *sslkey;
+ char *sslcert;
+ char *proxy;
+ char *info;
+ unsigned int retry_sleep;
+ unsigned int offs;
+ unsigned int method;
+ unsigned int retries;
+ bool debug;
+ bool strictssl;
+ int (*checkdwl)(void);
+#ifdef CONFIG_SURICATTA_SSL
+ char sha1hash[SHA_DIGEST_LENGTH * 2 + 1];
+#endif
+} channel_data_t;
diff --git a/suricatta/Makefile b/suricatta/Makefile
index 72b3e76..fabe926 100644
--- a/suricatta/Makefile
+++ b/suricatta/Makefile
@@ -1,4 +1,4 @@
lib-$(CONFIG_SURICATTA) += state.o suricatta.o
ifneq ($(CONFIG_SURICATTA_HAWKBIT),)
-lib-$(CONFIG_SURICATTA) += channel_hawkbit.o server_hawkbit.o
+lib-$(CONFIG_SURICATTA) += server_hawkbit.o
endif
diff --git a/suricatta/channel_hawkbit.c b/suricatta/channel_hawkbit.c
deleted file mode 100644
index f3d00a0..0000000
--- a/suricatta/channel_hawkbit.c
+++ /dev/null
@@ -1,941 +0,0 @@
-/*
- * Author: Christian Storm
- * Copyright (C) 2016, Siemens AG
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc.
- */
-
-#include <stdbool.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <curl/curl.h>
-#include <json-c/json.h>
-#include <generated/autoconf.h>
-#include <unistd.h>
-#include <network_ipc.h>
-#include <util.h>
-#ifdef CONFIG_SURICATTA_SSL
-#include <openssl/sha.h>
-#endif
-#include "suricatta/channel.h"
-#include "channel_hawkbit.h"
-#include "suricatta/suricatta.h"
-
-#define SPEED_LOW_BYTES_SEC 8
-#define SPEED_LOW_TIME_SEC 300
-#define KEEPALIVE_DELAY 204L
-#define KEEPALIVE_INTERVAL 120L
-
-typedef struct {
- char *memory;
- size_t size;
-} output_data_t;
-
-typedef struct {
- char *proxy;
- char *effective_url;
- CURL *handle;
- struct curl_slist *header;
-} channel_curl_t;
-
-typedef struct {
- channel_data_t *channel_data;
- int output;
- output_data_t *outdata;
-} write_callback_t;
-
-#ifdef CONFIG_SURICATTA_SSL
-static SHA_CTX checksum_ctx;
-#endif
-
-/* Prototypes for "internal" functions */
-/* Note that they're not `static` so that they're callable from unit tests. */
-size_t channel_callback_write_file(void *streamdata, size_t size, size_t nmemb,
- write_callback_t *data);
-size_t channel_callback_membuffer(void *streamdata, size_t size, size_t nmemb,
- write_callback_t *data);
-channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code);
-channel_op_res_t channel_map_curl_error(CURLcode res);
-channel_op_res_t channel_set_options(channel_t *this, channel_data_t *channel_data,
- channel_method_t method);
-static void channel_log_effective_url(channel_t *this);
-
-/* Prototypes for "public" functions */
-channel_op_res_t channel_hawkbit_init(void);
-channel_op_res_t channel_close(channel_t *this);
-channel_op_res_t channel_open(channel_t *this, void *cfg);
-channel_op_res_t channel_get(channel_t *this, void *data);
-channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle);
-channel_op_res_t channel_put(channel_t *this, void *data);
-channel_t *channel_new(void);
-
-
-channel_op_res_t channel_hawkbit_init(void)
-{
-#ifdef CONFIG_SURICATTA_SSL
-#define CURL_FLAGS CURL_GLOBAL_SSL
-#else
-#define CURL_FLAGS CURL_GLOBAL_NOTHING
-#endif
- CURLcode curlrc = curl_global_init(CURL_FLAGS);
- if (curlrc != CURLE_OK) {
- ERROR("Initialization of channel failed (%d): '%s'\n", curlrc,
- curl_easy_strerror(curlrc));
- return CHANNEL_EINIT;
- }
-#undef CURL_FLAGS
- return CHANNEL_OK;
-}
-
-channel_t *channel_new(void)
-{
- channel_t *newchan = (channel_t *)calloc(1, sizeof(*newchan) +
- sizeof(channel_curl_t));
-
- if (newchan) {
- newchan->priv = (void *)newchan + sizeof(*newchan);
- newchan->open = &channel_open;
- newchan->close = &channel_close;
- newchan->get = &channel_get;
- newchan->get_file = &channel_get_file;
- newchan->put = &channel_put;
- }
-
- return newchan;
-}
-
-channel_op_res_t channel_close(channel_t *this)
-{
- channel_curl_t *channel_curl = this->priv;
-
- if ((channel_curl->proxy != NULL) &&
- (channel_curl->proxy != USE_PROXY_ENV)) {
- free(channel_curl->proxy);
- }
- if (channel_curl->handle == NULL) {
- return CHANNEL_OK;
- }
- curl_easy_cleanup(channel_curl->handle);
- channel_curl->handle = NULL;
-
- return CHANNEL_OK;
-}
-
-channel_op_res_t channel_open(channel_t *this, void *cfg)
-{
- assert(this != NULL);
- channel_curl_t *channel_curl = this->priv;
- assert(channel_curl->handle == NULL);
-
- channel_data_t *channel_cfg = (channel_data_t *)cfg;
-
- if ((channel_cfg != NULL) && (channel_cfg->proxy != NULL)) {
- channel_curl->proxy = channel_cfg->proxy == USE_PROXY_ENV
- ? USE_PROXY_ENV
- : strdup(channel_cfg->proxy);
- if (channel_curl->proxy == NULL) {
- return CHANNEL_EINIT;
- }
- }
-
- if ((channel_curl->handle = curl_easy_init()) == NULL) {
- ERROR("Initialization of channel failed.\n");
- return CHANNEL_EINIT;
- }
-
- return CHANNEL_OK;
-}
-
-static channel_op_res_t result_channel_callback_write_file;
-size_t channel_callback_write_file(void *streamdata, size_t size, size_t nmemb,
- write_callback_t *data)
-{
- if (!nmemb) {
- return 0;
- }
- if (!data)
- return 0;
- result_channel_callback_write_file = CHANNEL_OK;
-#ifdef CONFIG_SURICATTA_SSL
- if (SHA1_Update(&checksum_ctx, streamdata, size * nmemb) != 1) {
- ERROR("Updating checksum of chunk failed.\n");
- result_channel_callback_write_file = CHANNEL_EIO;
- return 0;
- }
-#endif
- if (ipc_send_data(data->output, streamdata, (int)(size * nmemb)) <
- 0) {
- ERROR("Writing into SWUpdate IPC stream failed.\n");
- result_channel_callback_write_file = CHANNEL_EIO;
- return 0;
- }
-
- if (data->channel_data->checkdwl && data->channel_data->checkdwl())
- return 0;
- /*
- * Now check if there is a callback from the server
- * during the download
- */
-
- return size * nmemb;
-}
-
-size_t channel_callback_membuffer(void *streamdata, size_t size, size_t nmemb,
- write_callback_t *data)
-{
- if (!nmemb) {
- return 0;
- }
- if (!data) {
- return 0;
- }
-
- size_t realsize = size * nmemb;
- output_data_t *mem = data->outdata;
-
- mem->memory = realloc(mem->memory, mem->size + realsize + 1);
- if (mem->memory == NULL) {
- ERROR("Channel get operation failed with OOM\n");
- return 0;
- }
- memcpy(&(mem->memory[mem->size]), streamdata, realsize);
- mem->size += realsize;
- mem->memory[mem->size] = 0;
- return realsize;
-}
-
-static void channel_log_effective_url(channel_t *this)
-{
- channel_curl_t *channel_curl = this->priv;
-
- CURLcode curlrc =
- curl_easy_getinfo(channel_curl->handle, CURLINFO_EFFECTIVE_URL,
- &channel_curl->effective_url);
- if (curlrc != CURLE_OK && curlrc == CURLE_UNKNOWN_OPTION) {
- ERROR("Get channel's effective URL response unsupported by "
- "libcURL %s.\n",
- LIBCURL_VERSION);
- return;
- }
- TRACE("Channel's effective URL resolved to %s\n",
- channel_curl->effective_url);
-}
-
-channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code)
-{
- channel_curl_t *channel_curl = this->priv;
- CURLcode curlrc =
- curl_easy_getinfo(channel_curl->handle, CURLINFO_RESPONSE_CODE,
- http_response_code);
- if (curlrc != CURLE_OK && curlrc == CURLE_UNKNOWN_OPTION) {
- ERROR("Get channel HTTP response code unsupported by libcURL "
- "%s.\n",
- LIBCURL_VERSION);
- return CHANNEL_EINIT;
- }
- switch (*http_response_code) {
- case 0: /* libcURL: no server response code has been received yet */
- DEBUG("No HTTP response code has been received yet!\n");
- return CHANNEL_EBADMSG;
- case 401: /* Unauthorized. The request requires user authentication. */
- case 403: /* Forbidden. */
- case 405: /* Method not Allowed. */
- case 407: /* Proxy Authentication Required */
- case 503: /* Service Unavailable */
- return CHANNEL_EACCES;
- case 400: /* Bad Request, e.g., invalid parameters */
- case 404: /* Wrong URL */
- case 406: /* Not acceptable. Accept header is not response compliant */
- case 443: /* Connection refused */
- return CHANNEL_EBADMSG;
- case 429: /* Bad Request, i.e., too many requests. Try again later. */
- return CHANNEL_EAGAIN;
- case 200:
- case 206:
- return CHANNEL_OK;
- case 500:
- return CHANNEL_EBADMSG;
- default:
- ERROR("Channel operation returned unhandled HTTP error code "
- "%ld\n",
- *http_response_code);
- return CHANNEL_EBADMSG;
- }
-}
-
-channel_op_res_t channel_map_curl_error(CURLcode res)
-{
- switch (res) {
- case CURLE_NOT_BUILT_IN:
- case CURLE_BAD_FUNCTION_ARGUMENT:
- case CURLE_UNKNOWN_OPTION:
- case CURLE_SSL_ENGINE_NOTFOUND:
- case CURLE_SSL_ENGINE_SETFAILED:
- case CURLE_SSL_CERTPROBLEM:
- case CURLE_SSL_CIPHER:
- case CURLE_SSL_ENGINE_INITFAILED:
- case CURLE_SSL_CACERT_BADFILE:
- case CURLE_SSL_CRL_BADFILE:
- case CURLE_SSL_ISSUER_ERROR:
-#if LIBCURL_VERSION_NUM >= 0x072900
- case CURLE_SSL_INVALIDCERTSTATUS:
-#endif
-#if LIBCURL_VERSION_NUM >= 0x072700
- case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
-#endif
- return CHANNEL_EINIT;
- case CURLE_COULDNT_RESOLVE_PROXY:
- case CURLE_COULDNT_RESOLVE_HOST:
- case CURLE_COULDNT_CONNECT:
- case CURLE_INTERFACE_FAILED:
- case CURLE_SSL_CONNECT_ERROR:
- case CURLE_PEER_FAILED_VERIFICATION:
- case CURLE_SSL_CACERT:
- case CURLE_USE_SSL_FAILED:
- return CHANNEL_ENONET;
- case CURLE_OPERATION_TIMEDOUT:
- case CURLE_SEND_ERROR:
- case CURLE_RECV_ERROR:
- case CURLE_GOT_NOTHING:
- case CURLE_HTTP_POST_ERROR:
- case CURLE_PARTIAL_FILE:
- return CHANNEL_EAGAIN;
- case CURLE_OUT_OF_MEMORY:
- return CHANNEL_ENOMEM;
- case CURLE_REMOTE_FILE_NOT_FOUND:
- return CHANNEL_ENOENT;
- case CURLE_FILESIZE_EXCEEDED:
- case CURLE_ABORTED_BY_CALLBACK:
- case CURLE_WRITE_ERROR:
- case CURLE_CHUNK_FAILED:
- case CURLE_SSL_SHUTDOWN_FAILED:
- return CHANNEL_EIO;
- case CURLE_TOO_MANY_REDIRECTS:
- return CHANNEL_ELOOP;
- case CURLE_BAD_CONTENT_ENCODING:
- case CURLE_CONV_FAILED:
- case CURLE_CONV_REQD:
- return CHANNEL_EILSEQ;
- case CURLE_REMOTE_ACCESS_DENIED:
- case CURLE_LOGIN_DENIED:
- return CHANNEL_EACCES;
- case CURLE_OK:
- return CHANNEL_OK;
- default:
- return CHANNEL_EINIT;
- }
-}
-
-channel_op_res_t channel_set_options(channel_t *this,
- channel_data_t *channel_data,
- channel_method_t method)
-{
- channel_curl_t *channel_curl = this->priv;
- channel_op_res_t result = CHANNEL_OK;
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_URL,
- channel_data->url) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_USERAGENT,
- "libcurl-agent/1.0") != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_LOW_SPEED_LIMIT,
- SPEED_LOW_BYTES_SEC) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_LOW_SPEED_TIME,
- SPEED_LOW_TIME_SEC) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_HTTPHEADER,
- channel_curl->header) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_MAXREDIRS, -1) !=
- CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_FOLLOWLOCATION, 1) !=
- CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_REDIR_PROTOCOLS,
- CURLPROTO_HTTP | CURLPROTO_HTTPS) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_CAINFO,
- channel_data->cafile) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSLKEY,
- channel_data->sslkey) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSLCERT,
- channel_data->sslcert) != CURLE_OK)) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
-
- if (channel_data->strictssl == true) {
- if ((curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSL_VERIFYHOST,
- 2L) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSL_VERIFYPEER,
- 1L) != CURLE_OK)) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- }
- else {
- if ((curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSL_VERIFYHOST,
- 0L) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_SSL_VERIFYPEER,
- 0L) != CURLE_OK)) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- }
-
- switch (method) {
- case CHANNEL_GET:
- if (curl_easy_setopt(channel_curl->handle, CURLOPT_CUSTOMREQUEST,
- "GET") != CURLE_OK) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- break;
- case CHANNEL_PUT:
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_PUT, 1L) !=
- CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_UPLOAD, 1L) !=
- CURLE_OK)) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- break;
- case CHANNEL_POST:
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_POST, 1L) !=
- CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_POSTFIELDS,
- channel_data->json_string) != CURLE_OK)) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- if (channel_data->debug) {
- TRACE("Post JSON: %s\n", channel_data->json_string);
- }
- break;
- }
-
- if (channel_curl->proxy != NULL) {
- if (channel_curl->proxy != USE_PROXY_ENV) {
- if (curl_easy_setopt(
- channel_curl->handle, CURLOPT_PROXY,
- channel_curl->proxy) != CURLE_OK) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- }
- if (curl_easy_setopt(channel_curl->handle, CURLOPT_NETRC,
- CURL_NETRC_OPTIONAL) != CURLE_OK) {
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- }
-
- CURLcode curlrc =
- curl_easy_setopt(channel_curl->handle, CURLOPT_TCP_KEEPALIVE, 1L);
- if (curlrc == CURLE_OK) {
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_TCP_KEEPIDLE,
- KEEPALIVE_DELAY) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle,
- CURLOPT_TCP_KEEPINTVL,
- KEEPALIVE_INTERVAL) != CURLE_OK)) {
- ERROR("TCP Keep-alive interval and delay could not be "
- "configured.\n");
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- } else {
- if (curlrc != CURLE_UNKNOWN_OPTION) {
- ERROR("Channel could not be configured to sent "
- "keep-alive probes.\n");
- result = CHANNEL_EINIT;
- goto cleanup;
- }
- }
-
-cleanup:
- return result;
-}
-
-static size_t put_read_callback(void *ptr, size_t size, size_t nmemb, void *data)
-{
- channel_data_t *channel_data = (channel_data_t *)data;
- unsigned int bytes;
- size_t n;
-
- /* Check data to be sent */
- bytes = strlen(channel_data->json_string) - channel_data->offs;
-
- if (!bytes)
- return 0;
-
- n = min(bytes, size * nmemb);
-
- memcpy(ptr, &channel_data->json_string[channel_data->offs], n);
- channel_data->offs += n;
-
- return n;
-}
-
-static channel_op_res_t channel_post_method(channel_t *this, void *data)
-{
- channel_curl_t *channel_curl = this->priv;
- assert(data != NULL);
- assert(channel_curl->handle != NULL);
-
- channel_op_res_t result = CHANNEL_OK;
- channel_data_t *channel_data = (channel_data_t *)data;
-
- if (channel_data->debug) {
- curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
- }
-
- if (((channel_curl->header = curl_slist_append(
- channel_curl->header, "Content-Type: application/json")) ==
- NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "Accept: application/json")) == NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "charsets: utf-8")) == NULL)) {
- ERROR("Set channel header failed.\n");
- result = CHANNEL_EINIT;
- goto cleanup_header;
- }
-
- if ((result = channel_set_options(this, channel_data, CHANNEL_POST)) !=
- CHANNEL_OK) {
- ERROR("Set channel option failed.\n");
- goto cleanup_header;
- }
-
- CURLcode curlrc = curl_easy_perform(channel_curl->handle);
- if (curlrc != CURLE_OK) {
- ERROR("Channel put operation failed (%d): '%s'\n", curlrc,
- curl_easy_strerror(curlrc));
- result = channel_map_curl_error(curlrc);
- goto cleanup_header;
- }
-
- channel_log_effective_url(this);
-
- long http_response_code;
- if ((result = channel_map_http_code(this, &http_response_code)) !=
- CHANNEL_OK) {
- ERROR("Channel operation returned HTTP error code %ld.\n",
- http_response_code);
- goto cleanup_header;
- }
- TRACE("Channel put operation returned HTTP status code %ld.\n",
- http_response_code);
-
-cleanup_header:
- curl_easy_reset(channel_curl->handle);
- curl_slist_free_all(channel_curl->header);
- channel_curl->header = NULL;
-
- return result;
-}
-
-static channel_op_res_t channel_put_method(channel_t *this, void *data)
-{
- channel_curl_t *channel_curl = this->priv;
- assert(data != NULL);
- assert(channel_curl->handle != NULL);
-
- channel_op_res_t result = CHANNEL_OK;
- channel_data_t *channel_data = (channel_data_t *)data;
- channel_data->offs = 0;
-
- if (channel_data->debug) {
- curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
- }
-
- if (((channel_curl->header = curl_slist_append(
- channel_curl->header, "Content-Type: application/json")) ==
- NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "Accept: application/json")) == NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "charsets: utf-8")) == NULL)) {
- ERROR("Set channel header failed.\n");
- result = CHANNEL_EINIT;
- goto cleanup_header;
- }
-
- if ((result = channel_set_options(this, channel_data, CHANNEL_PUT)) !=
- CHANNEL_OK) {
- ERROR("Set channel option failed.\n");
- goto cleanup_header;
- }
-
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_READFUNCTION, put_read_callback) !=
- CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_INFILESIZE_LARGE,
- (curl_off_t)strlen(channel_data->json_string)) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_READDATA, channel_data) !=
- CURLE_OK)) {
- ERROR("Set channel option failed.\n");
- goto cleanup_header;
- }
-
- CURLcode curlrc = curl_easy_perform(channel_curl->handle);
- if (curlrc != CURLE_OK) {
- ERROR("Channel put operation failed (%d): '%s'\n", curlrc,
- curl_easy_strerror(curlrc));
- result = channel_map_curl_error(curlrc);
- goto cleanup_header;
- }
-
- channel_log_effective_url(this);
-
- long http_response_code;
- if ((result = channel_map_http_code(this, &http_response_code)) != CHANNEL_OK) {
- ERROR("Channel operation returned HTTP error code %ld.\n",
- http_response_code);
- goto cleanup_header;
- }
- TRACE("Channel put operation returned HTTP error code %ld.\n",
- http_response_code);
-
-cleanup_header:
- curl_easy_reset(channel_curl->handle);
- curl_slist_free_all(channel_curl->header);
- channel_curl->header = NULL;
-
- return result;
-}
-
-channel_op_res_t channel_put(channel_t *this, void *data)
-{
- assert(data != NULL);
-
- channel_data_t *channel_data = (channel_data_t *)data;
-
- switch (channel_data->method) {
- case CHANNEL_PUT:
- return channel_put_method(this, data);
- case CHANNEL_POST:
- return channel_post_method(this, data);
- default:
- TRACE("Channel method (POST, PUT) is not set !\n");
- return CHANNEL_EINIT;
- }
-}
-
-channel_op_res_t channel_get_file(channel_t *this, void *data, int file_handle)
-{
- channel_curl_t *channel_curl = this->priv;
- assert(data != NULL);
- assert(channel_curl->handle != NULL);
-
- channel_op_res_t result = CHANNEL_OK;
- channel_data_t *channel_data = (channel_data_t *)data;
-
-#ifdef CONFIG_SURICATTA_SSL
- memset(channel_data->sha1hash, 0x0, SHA_DIGEST_LENGTH * 2 + 1);
- if (SHA1_Init(&checksum_ctx) != 1) {
- result = CHANNEL_EINIT;
- ERROR("Cannot initialize sha1 checksum context.\n");
- goto cleanup;
- }
-#endif
-
- if (channel_data->debug) {
- curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
- }
-
- if (((channel_curl->header = curl_slist_append(
- channel_curl->header,
- "Content-Type: application/octet-stream")) == NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "Accept: application/octet-stream")) ==
- NULL)) {
- result = CHANNEL_EINIT;
- ERROR("Set channel header failed.\n");
- goto cleanup_header;
- }
-
- if ((result = channel_set_options(this, channel_data, CHANNEL_GET)) !=
- CHANNEL_OK) {
- ERROR("Set channel option failed.\n");
- goto cleanup_header;
- }
-
- if (file_handle == FD_USE_IPC) {
- for (int retries = 3; retries >= 0; retries--) {
- file_handle = ipc_inst_start_ext(SOURCE_SURICATTA,
- channel_data->info == NULL ? 0 : strlen(channel_data->info),
- channel_data->info);
- if (file_handle > 0)
- break;
- sleep(1);
- }
- if (file_handle < 0) {
- ERROR("Cannot open SWUpdate IPC stream: %s\n", strerror(errno));
- result = CHANNEL_EIO;
- goto cleanup_header;
- }
- } else {
- assert(file_handle > 0);
- }
-
- write_callback_t wrdata;
- wrdata.channel_data = channel_data;
- wrdata.output = file_handle;
- result_channel_callback_write_file = CHANNEL_OK;
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEFUNCTION,
- channel_callback_write_file) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEDATA,
- &wrdata) != CURLE_OK)) {
- ERROR("Cannot setup file writer callback function.\n");
- result = CHANNEL_EINIT;
- goto cleanup_file;
- }
-
- /* Depending on the architecture and libcurl, this is at
- * least 2^32 - 1 bytes, i.e., enough to account for a 4GiB
- * download. Although 32-Bit curl defines `curl_off_t` as
- * (signed) long long, use unsigned here, but it wastes some
- * bits on 64-Bit.
- */
- unsigned long long int total_bytes_downloaded = 0;
- unsigned char try_count = 0;
- CURLcode curlrc = CURLE_OK;
- do {
- if (try_count > 0) {
- if (channel_data->retries == 0) {
- ERROR(
- "Channel get operation failed (%d): '%s'\n",
- curlrc, curl_easy_strerror(curlrc));
- result = channel_map_curl_error(curlrc);
- goto cleanup_file;
- }
-
- if (try_count > channel_data->retries) {
- ERROR("Channel get operation aborted because "
- "of too many failed download attempts "
- "(%d).\n",
- channel_data->retries);
- result = CHANNEL_ELOOP;
- goto cleanup_file;
- }
-
- DEBUG("Channel connection interrupted, trying resume "
- "after %llu bytes.",
- total_bytes_downloaded);
- if (curl_easy_setopt(
- channel_curl->handle, CURLOPT_RESUME_FROM_LARGE,
- total_bytes_downloaded) != CURLE_OK) {
- ERROR("Could not set Channel resume seek (%d): "
- "'%s'\n",
- curlrc, curl_easy_strerror(curlrc));
- result = channel_map_curl_error(curlrc);
- goto cleanup_file;
- }
- TRACE("Channel sleeps for %d seconds now.",
- channel_data->retry_sleep);
- if (sleep(channel_data->retry_sleep) > 0) {
- TRACE("Channel's sleep got interrupted, "
- "retrying nonetheless now.");
- }
- TRACE("Channel awakened from sleep.");
- }
-
- curlrc = curl_easy_perform(channel_curl->handle);
- result = channel_map_curl_error(curlrc);
- if ((result != CHANNEL_OK) && (result != CHANNEL_EAGAIN)) {
- ERROR("Channel operation returned error (%d): '%s'\n",
- curlrc, curl_easy_strerror(curlrc));
- goto cleanup_file;
- }
-
- double bytes_downloaded;
- CURLcode resdlprogress = curl_easy_getinfo(
- channel_curl->handle, CURLINFO_SIZE_DOWNLOAD,
- &bytes_downloaded);
- if (resdlprogress != CURLE_OK) {
- ERROR("Channel does not report bytes downloaded (%d): "
- "'%s'\n",
- resdlprogress, curl_easy_strerror(resdlprogress));
- result = channel_map_curl_error(resdlprogress);
- goto cleanup_file;
- }
- total_bytes_downloaded += bytes_downloaded;
-
- } while (++try_count && (result != CHANNEL_OK));
-
- channel_log_effective_url(this);
-
- DEBUG("Channel downloaded %llu bytes ~ %llu MiB.\n",
- total_bytes_downloaded, total_bytes_downloaded / 1024 / 1024);
-
- long http_response_code;
- if ((result = channel_map_http_code(this, &http_response_code)) !=
- CHANNEL_OK) {
- ERROR("Channel operation returned HTTP error code %ld.\n",
- http_response_code);
- goto cleanup_file;
- }
- TRACE("Channel operation returned HTTP status code %ld.\n",
- http_response_code);
-
- if (result_channel_callback_write_file != CHANNEL_OK) {
- result = CHANNEL_EIO;
- goto cleanup_file;
- }
-
-#ifdef CONFIG_SURICATTA_SSL
- unsigned char sha1hash[SHA_DIGEST_LENGTH];
- if (SHA1_Final(sha1hash, &checksum_ctx) != 1) {
- ERROR("Cannot compute checksum.\n");
- goto cleanup_file;
- }
- char sha1hexchar[3];
- for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
- sprintf(sha1hexchar, "%02x", sha1hash[i]);
- strcat(channel_data->sha1hash, sha1hexchar);
- }
-#endif
-
-cleanup_file:
- /* NOTE ipc_end() calls close() but does not return its error code,
- * so use close() here directly to issue an error in case.
- * Also, for a given file handle, calling ipc_end() would make
- * no semantic sense. */
- if (close(file_handle) != 0) {
- ERROR("Channel error while closing download target handle: '%s'\n",
- strerror(errno));
- }
-cleanup_header:
- curl_easy_reset(channel_curl->handle);
- curl_slist_free_all(channel_curl->header);
- channel_curl->header = NULL;
-
-#ifdef CONFIG_SURICATTA_SSL
-cleanup:
-#endif
- return result;
-}
-
-channel_op_res_t channel_get(channel_t *this, void *data)
-{
- channel_curl_t *channel_curl = this->priv;
- assert(data != NULL);
- assert(channel_curl.handle != NULL);
-
- channel_op_res_t result = CHANNEL_OK;
- channel_data_t *channel_data = (channel_data_t *)data;
-
- if (channel_data->debug) {
- curl_easy_setopt(channel_curl->handle, CURLOPT_VERBOSE, 1L);
- }
-
- if (((channel_curl->header = curl_slist_append(
- channel_curl->header, "Content-Type: application/json")) ==
- NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "Accept: application/json")) == NULL) ||
- ((channel_curl->header = curl_slist_append(
- channel_curl->header, "charsets: utf-8")) == NULL)) {
- result = CHANNEL_EINIT;
- ERROR("Set channel header failed.\n");
- goto cleanup_header;
- }
-
- if ((result = channel_set_options(this, channel_data, CHANNEL_GET)) !=
- CHANNEL_OK) {
- ERROR("Set channel option failed.\n");
- goto cleanup_header;
- }
-
- output_data_t chunk = {.memory = NULL, .size = 0};
- if ((chunk.memory = malloc(1)) == NULL) {
- ERROR("Channel buffer reservation failed with OOM.\n");
- result = CHANNEL_ENOMEM;
- goto cleanup_header;
- }
-
- write_callback_t wrdata;
- wrdata.channel_data = channel_data;
- wrdata.outdata = &chunk;
-
- if ((curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEFUNCTION,
- channel_callback_membuffer) != CURLE_OK) ||
- (curl_easy_setopt(channel_curl->handle, CURLOPT_WRITEDATA,
- (void *)&wrdata) != CURLE_OK)) {
- ERROR("Cannot setup memory buffer writer callback function.\n");
- result = CHANNEL_EINIT;
- goto cleanup_chunk;
- }
-
- DEBUG("Trying to GET %s", channel_data->url);
- CURLcode curlrc = curl_easy_perform(channel_curl->handle);
- if (curlrc != CURLE_OK) {
- ERROR("Channel get operation failed (%d): '%s'\n", curlrc,
- curl_easy_strerror(curlrc));
- result = channel_map_curl_error(curlrc);
- goto cleanup_chunk;
- }
-
- channel_log_effective_url(this);
-
- long http_response_code;
- if ((result = channel_map_http_code(this, &http_response_code)) !=
- CHANNEL_OK) {
- ERROR("Channel operation returned HTTP error code %ld.\n",
- http_response_code);
- if (http_response_code == 500) {
- DEBUG("The error's message is: '%s'\n", chunk.memory);
- }
- if (http_response_code == 404) {
- DEBUG("The error's message is: '%s'\n", chunk.memory);
- }
- goto cleanup_chunk;
- }
- TRACE("Channel operation returned HTTP status code %ld.\n",
- http_response_code);
-
- assert(channel_data->json_reply == NULL);
- enum json_tokener_error json_res;
- struct json_tokener *json_tokenizer = json_tokener_new();
- do {
- channel_data->json_reply = json_tokener_parse_ex(
- json_tokenizer, chunk.memory, (int)chunk.size);
- } while ((json_res = json_tokener_get_error(json_tokenizer)) ==
- json_tokener_continue);
- if (json_res != json_tokener_success) {
- ERROR("Error while parsing channel's returned JSON data: %s\n",
- json_tokener_error_desc(json_res));
- result = CHANNEL_EBADMSG;
- goto cleanup_json_tokenizer;
- }
- if (channel_data->debug) {
- TRACE("Get JSON: %s\n", chunk.memory);
- }
-
-cleanup_json_tokenizer:
- json_tokener_free(json_tokenizer);
-cleanup_chunk:
- chunk.memory != NULL ? free(chunk.memory) : (void)0;
-cleanup_header:
- curl_easy_reset(channel_curl->handle);
- curl_slist_free_all(channel_curl->header);
- channel_curl->header = NULL;
-
- return result;
-}
diff --git a/suricatta/channel_hawkbit.h b/suricatta/channel_hawkbit.h
deleted file mode 100644
index ed262fc..0000000
--- a/suricatta/channel_hawkbit.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Author: Christian Storm
- * Copyright (C) 2016, Siemens AG
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc.
- */
-
-#pragma once
-#include <json-c/json.h>
-#include <stdio.h>
-#ifdef CONFIG_SURICATTA_SSL
-#include <openssl/sha.h>
-#endif
-
-/* hawkBit Channel Implementation Private Header File.
- *
- * This is a "private" header for testability, i.e., the declarations and
- * definitions herein should be used by `server_hawkbit.c`, `channel_hawkbit.c`,
- * and unit tests only.
- */
-
-typedef enum {
- CHANNEL_GET,
- CHANNEL_POST,
- CHANNEL_PUT,
-} channel_method_t;
-
-#define FD_USE_IPC -2
-
-#define USE_PROXY_ENV (char *)0x11
-
-typedef struct {
- char *url;
- char *json_string;
- json_object *json_reply;
- char *cafile;
- char *sslkey;
- char *sslcert;
- char *proxy;
- char *info;
- unsigned int retry_sleep;
- unsigned int offs;
- unsigned int method;
- unsigned int retries;
- bool debug;
- bool strictssl;
- int (*checkdwl)(void);
-#ifdef CONFIG_SURICATTA_SSL
- char sha1hash[SHA_DIGEST_LENGTH * 2 + 1];
-#endif
-} channel_data_t;
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 1b0e600..186e9f0 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -33,7 +33,7 @@
#include <swupdate_status.h>
#include "suricatta/suricatta.h"
#include "suricatta/channel.h"
-#include "channel_hawkbit.h"
+#include "channel_curl.h"
#include "suricatta/state.h"
#include "server_hawkbit.h"
#include "parselib.h"
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:45 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/channel_curl.c | 2 +-
include/channel.h | 39 +++++++++++++++++++++++++++++++++++++++
include/suricatta/channel.h | 39 ---------------------------------------
suricatta/server_hawkbit.c | 2 +-
suricatta/server_hawkbit.h | 1 -
5 files changed, 41 insertions(+), 42 deletions(-)
create mode 100644 include/channel.h
delete mode 100644 include/suricatta/channel.h

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 705b887..608f5d3 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -30,7 +30,7 @@
#include <network_ipc.h>
#include <util.h>
#include "sslapi.h"
-#include "suricatta/channel.h"
+#include "channel.h"
#include "channel_curl.h"

#define SPEED_LOW_BYTES_SEC 8
diff --git a/include/channel.h b/include/channel.h
new file mode 100644
index 0000000..f0c13a7
--- /dev/null
+++ b/include/channel.h
@@ -0,0 +1,39 @@
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2016, Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#pragma once
+#include "channel_op_res.h"
+
+/* Suricatta Channel Interface.
+ *
+ * Each suricatta channel has to implement this interface.
+ * Cf. `channel_hawkbit.c` for an example implementation targeted towards the
+ * [hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) server.
+ */
+typedef struct channel channel_t;
+struct channel {
+ channel_op_res_t (*open)(channel_t *this, void *cfg);
+ channel_op_res_t (*close)(channel_t *this);
+ channel_op_res_t (*get)(channel_t *this, void *data);
+ channel_op_res_t (*get_file)(channel_t *this, void *data, int file_handle);
+ channel_op_res_t (*put)(channel_t *this, void *data);
+ void *priv;
+};
+
+channel_t *channel_new(void);
diff --git a/include/suricatta/channel.h b/include/suricatta/channel.h
deleted file mode 100644
index f0c13a7..0000000
--- a/include/suricatta/channel.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Author: Christian Storm
- * Copyright (C) 2016, Siemens AG
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc.
- */
-
-#pragma once
-#include "channel_op_res.h"
-
-/* Suricatta Channel Interface.
- *
- * Each suricatta channel has to implement this interface.
- * Cf. `channel_hawkbit.c` for an example implementation targeted towards the
- * [hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) server.
- */
-typedef struct channel channel_t;
-struct channel {
- channel_op_res_t (*open)(channel_t *this, void *cfg);
- channel_op_res_t (*close)(channel_t *this);
- channel_op_res_t (*get)(channel_t *this, void *data);
- channel_op_res_t (*get_file)(channel_t *this, void *data, int file_handle);
- channel_op_res_t (*put)(channel_t *this, void *data);
- void *priv;
-};
-
-channel_t *channel_new(void);
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 59f4cb3..9649d27 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -32,7 +32,7 @@
#include <sys/time.h>
#include <swupdate_status.h>
#include "suricatta/suricatta.h"
-#include "suricatta/channel.h"
+#include "channel.h"
#include "channel_curl.h"
#include "suricatta/state.h"
#include "server_hawkbit.h"
diff --git a/suricatta/server_hawkbit.h b/suricatta/server_hawkbit.h
index 51938c9..fba399c 100644
--- a/suricatta/server_hawkbit.h
+++ b/suricatta/server_hawkbit.h
@@ -19,7 +19,6 @@

#pragma once
#include <stdbool.h>
-#include <suricatta/channel.h>
#include <suricatta/state.h>
#include <swupdate_dict.h>

--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:45 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Channel API is independent from suricatta daemon. Split the headers
to separate channel API from suricatta.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
include/channel_op_res.h | 37 +++++++++++++++++++++++++++++++++++++
include/suricatta/channel.h | 2 +-
include/suricatta/suricatta.h | 16 ++--------------
3 files changed, 40 insertions(+), 15 deletions(-)
create mode 100644 include/channel_op_res.h

diff --git a/include/channel_op_res.h b/include/channel_op_res.h
new file mode 100644
index 0000000..2e40ef1
--- /dev/null
+++ b/include/channel_op_res.h
@@ -0,0 +1,37 @@
+/*
+ * (C) Copyright 2017
+ * Stefano Babic, DENX Software Engineering, sba...@denx.de.
+ *
+ * Author: Christian Storm
+ * Copyright (C) 2016, Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#pragma once
+
+typedef enum {
+ CHANNEL_OK,
+ CHANNEL_EINIT,
+ CHANNEL_ENONET,
+ CHANNEL_ENOMEM,
+ CHANNEL_EACCES,
+ CHANNEL_ENOENT,
+ CHANNEL_EIO,
+ CHANNEL_EILSEQ,
+ CHANNEL_EAGAIN,
+ CHANNEL_ELOOP,
+ CHANNEL_EBADMSG
+} channel_op_res_t;
diff --git a/include/suricatta/channel.h b/include/suricatta/channel.h
index 98fc851..f0c13a7 100644
--- a/include/suricatta/channel.h
+++ b/include/suricatta/channel.h
@@ -18,7 +18,7 @@
*/

#pragma once
-#include "suricatta.h"
+#include "channel_op_res.h"

/* Suricatta Channel Interface.
*
diff --git a/include/suricatta/suricatta.h b/include/suricatta/suricatta.h
index c04aff4..e38af4d 100644
--- a/include/suricatta/suricatta.h
+++ b/include/suricatta/suricatta.h
@@ -19,6 +19,8 @@

#pragma once

+#include "channel_op_res.h"
+
/* Suricatta Main Interface.
*
* `start_suricatta()` is the main interface to suricatta's functionality.
@@ -29,20 +31,6 @@
*/

typedef enum {
- CHANNEL_OK,
- CHANNEL_EINIT,
- CHANNEL_ENONET,
- CHANNEL_ENOMEM,
- CHANNEL_EACCES,
- CHANNEL_ENOENT,
- CHANNEL_EIO,
- CHANNEL_EILSEQ,
- CHANNEL_EAGAIN,
- CHANNEL_ELOOP,
- CHANNEL_EBADMSG
-} channel_op_res_t;
-
-typedef enum {
SERVER_OK,
SERVER_EERR,
SERVER_EBADMSG,
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:46 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Signed-off-by: Stefano Babic <sba...@denx.de>
---
corelib/parsing_library_libjson.c | 47 ++++++++++++++++++++++++++++++++++++
include/parselib.h | 5 ++++
suricatta/server_hawkbit.c | 50 +--------------------------------------
3 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/corelib/parsing_library_libjson.c b/corelib/parsing_library_libjson.c
index 2f64d41..67cbc2c 100644
--- a/corelib/parsing_library_libjson.c
+++ b/corelib/parsing_library_libjson.c
@@ -31,6 +31,8 @@
#include "swupdate.h"
#include "parselib.h"

+#define MAX_URL_LENGTH 2048
+
json_object *find_json_recursive_node(json_object *root, const char **names)
{
json_object *node = root;
@@ -114,3 +116,48 @@ void get_field_json(json_object *e, const char *path, void *dest)
get_value_json(e, dest);
}
}
+
+json_object *json_get_key(json_object *json_root, const char *key)
+{
+ json_object *json_child;
+ if (json_object_object_get_ex(json_root, key, &json_child)) {
+ return json_child;
+ }
+ return NULL;
+}
+
+const char *json_get_value(struct json_object *json_root,
+ const char *key)
+{
+ json_object *json_data = json_get_key(json_root, key);
+
+ if (json_data == NULL)
+ return "";
+
+ return json_object_get_string(json_data);
+}
+
+json_object *json_get_path_key(json_object *json_root, const char **json_path)
+{
+ json_object *json_data = json_root;
+ while (*json_path) {
+ const char *key = *json_path;
+ json_data = json_get_key(json_data, key);
+ if (json_data == NULL) {
+ return NULL;
+ }
+ json_path++;
+ }
+ return json_data;
+}
+
+char *json_get_data_url(json_object *json_root, const char *key)
+{
+ json_object *json_data = json_get_path_key(
+ json_root, (const char *[]){"_links", key, "href", NULL});
+ return json_data == NULL
+ ? NULL
+ : strndup(json_object_get_string(json_data), MAX_URL_LENGTH);
+}
+
+
diff --git a/include/parselib.h b/include/parselib.h
index c32920a..7c44a5e 100644
--- a/include/parselib.h
+++ b/include/parselib.h
@@ -58,6 +58,11 @@ void get_value_json(json_object *e, void *dest);
void get_field_json(json_object *e, const char *path, void *dest);
void *get_child_json(json_object *e, const char *name);
json_object *find_json_recursive_node(json_object *root, const char **names);
+json_object *json_get_key(json_object *json_root, const char *key);
+const char *json_get_value(struct json_object *json_root,
+ const char *key);
+json_object *json_get_path_key(json_object *json_root, const char **json_path);
+char *json_get_data_url(json_object *json_root, const char *key);

#else
#define find_node_json(a, b, c) (NULL)
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 9649d27..175396c 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -32,6 +32,7 @@
#include <sys/time.h>
#include <swupdate_status.h>
#include "suricatta/suricatta.h"
+#include "parselib.h"
#include "channel.h"
#include "channel_curl.h"
#include "suricatta/state.h"
@@ -45,7 +46,6 @@
#define DEFAULT_RESUME_DELAY 5
#define INITIAL_STATUS_REPORT_WAIT_DELAY 10

-#define MAX_URL_LENGTH 2048
#define STRINGIFY(...) #__VA_ARGS__
#define JSON_OBJECT_FREED 1
#define ENOMEM_ASPRINTF -1
@@ -109,11 +109,6 @@ static hawkbit_enums_t hawkbit_enums[] = {
extern channel_op_res_t channel_curl_init(void);
/* Prototypes for "internal" functions */
/* Note that they're not `static` so that they're callable from unit tests. */
-json_object *json_get_key(json_object *json_root, const char *key);
-const char *json_get_value(struct json_object *json_root,
- const char *key);
-json_object *json_get_path_key(json_object *json_root, const char **json_path);
-char *json_get_data_url(json_object *json_root, const char *key);
server_op_res_t map_channel_retcode(channel_op_res_t response);
server_op_res_t server_handle_initial_state(update_state_t stateovrrd);
static int server_update_status_callback(ipc_message *msg);
@@ -208,49 +203,6 @@ static bool hawkbit_enum_check(const char *key, const char *value)
return false;
}

-json_object *json_get_key(json_object *json_root, const char *key)
-{
- json_object *json_child;
- if (json_object_object_get_ex(json_root, key, &json_child)) {
- return json_child;
- }
- return NULL;
-}
-
-const char *json_get_value(struct json_object *json_root,
- const char *key)
-{
- json_object *json_data = json_get_key(json_root, key);
-
- if (json_data == NULL)
- return "";
-
- return json_object_get_string(json_data);
-}
-
-json_object *json_get_path_key(json_object *json_root, const char **json_path)
-{
- json_object *json_data = json_root;
- while (*json_path) {
- const char *key = *json_path;
- json_data = json_get_key(json_data, key);
- if (json_data == NULL) {
- return NULL;
- }
- json_path++;
- }
- return json_data;
-}
-
-char *json_get_data_url(json_object *json_root, const char *key)
-{
- json_object *json_data = json_get_path_key(
- json_root, (const char *[]){"_links", key, "href", NULL});
- return json_data == NULL
- ? NULL
- : strndup(json_object_get_string(json_data), MAX_URL_LENGTH);
-}
-
static const char *json_get_deployment_update_action(json_object *json_reply)
{
if (!json_reply)
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:48 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Signed-off-by: Stefano Babic <sba...@denx.de>
---
doc/source/handlers.rst | 39 +++++++++++++++++++++++++++++++++++++--
1 file changed, 37 insertions(+), 2 deletions(-)

diff --git a/doc/source/handlers.rst b/doc/source/handlers.rst
index 5536e0f..0167245 100644
--- a/doc/source/handlers.rst
+++ b/doc/source/handlers.rst
@@ -258,8 +258,8 @@ image, this is not implemented as it carries some security
implications since the behavior of SWUpdate is changed
dynamically.

-Remote handlers
----------------
+Remote handler
+--------------

Remote handlers are thought for binding legacy installers
without having the necessity to rewrite them in Lua. The remote
@@ -311,3 +311,38 @@ It is duty of the external process to take care of the amount of
data transferred and to release resources when the last chunk
is received. For each DATA message, the external process answers with a
*ACK* or *NACK* message.
+
+SWU forwarder
+---------------
+
+The SWU forwarder handler can be used to update other systems where SWUpdate
+is running. It can be used in case of master / slaves systems, where the master
+is connected to the network and the "slaves" are hidden to the external world.
+The master is then the only interface to the world. A geenral SWU can contain
+embedded SWU images as single artifacts, and the SWU handler will forward it
+to the devices listed in the descritpion of the artifact.
+The handler can have a list of "url" properties. Each url is the address of a
+secondary board where SWUpdate is running with webserver activated.
+The SWU handler expects to talk with SWUpdate's embedded webserver. This helps
+to update systems where an old version of SWUpdate is running, because the
+embedded webserver is a common feature present in all versions.
+The handler will send the embedded SWU to all URLs at the same time, and setting
+``installed-directly`` is supported by this handler.
+
+The following example shows how to set a SWU as artifact and enables
+the SWU forwarder:
+
+
+::
+
+ images: (
+ {
+ filename = "image.swu";
+ type = "swuforward";
+
+ properties: (
+ {
+ name = "url";
+ value = "http://192.168.178.41:8080";
+ });
+ });
--
2.7.4

Stefano Babic

unread,
Nov 20, 2017, 6:10:48 AM11/20/17
to swup...@googlegroups.com, Stefano Babic
Add a handler to forward SWU images. A global SWU can contain
other SWU that are forwarded to other devices (or subsystems)
running SWUpdate as well.

The handler transfers an embedded SWU to all destinations assigned
into sw-description at the same time (streaming works with the handler).

The destinations are defined in properties for the entry:

{
filename = "image.swu";
type = "swuforward";

properties: ({
name = "url";
value = "http://<dest ip>:8080";
}
);
}

The handler assumes that the destinations are running SWUpdate
with activated Webserver and uses the REST-API to send the image.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
handlers/Config.in | 15 ++
handlers/Makefile | 1 +
handlers/swuforward_handler.c | 465 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 481 insertions(+)
create mode 100644 handlers/swuforward_handler.c

diff --git a/handlers/Config.in b/handlers/Config.in
index 5264f0d..596f069 100644
--- a/handlers/Config.in
+++ b/handlers/Config.in
@@ -161,6 +161,21 @@ config REMOTE_HANDLER
comment "remote handler needs zeromq"
depends on !HAVE_LIBZEROMQ

+config SWUFORWARDER_HANDLER
+ bool "SWU forwarder"
+ depends on HAVE_LIBCURL
+ select CURL
+ select JSON
+ default n
+ help
+ This allows to build a chain of updater. A
+ SWU can contains other SWUs for other systems.
+ The handler takes a list of URLs and forward the
+ embedded SWU to the other devices using the
+ Webserver REST API.
+
+comment "SWU forwarder requires libcurl"
+ depends on !HAVE_LIBCURL

config BOOTLOADERHANDLER
bool "bootloader"
diff --git a/handlers/Makefile b/handlers/Makefile
index b51e3ec..f89bbad 100644
--- a/handlers/Makefile
+++ b/handlers/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_SHELLSCRIPTHANDLER) += shell_scripthandler.o
obj-$(CONFIG_RAW) += raw_handler.o
obj-$(CONFIG_REMOTE_HANDLER) += remote_handler.o
obj-$(CONFIG_BOOTLOADERHANDLER) += boot_handler.o
+obj-$(CONFIG_SWUFORWARDER_HANDLER) += swuforward_handler.o
diff --git a/handlers/swuforward_handler.c b/handlers/swuforward_handler.c
new file mode 100644
index 0000000..b59de98
--- /dev/null
+++ b/handlers/swuforward_handler.c
@@ -0,0 +1,465 @@
+/*
+ * (C) Copyright 2017
+ * Stefano Babic, DENX Software Engineering, sba...@denx.de.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+/*
+ * This handler allows to create a mesh of devices using SWUpdate
+ * as agent. The handler is called if an artifact is a SWU image
+ * and sends it to the devices provided in sw-description.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <swupdate.h>
+#include <handler.h>
+#include <util.h>
+#include <curl/curl.h>
+#include <json-c/json.h>
+#include "bsdqueue.h"
+#include "channel_curl.h"
+#include "channel.h"
+#include "parselib.h"
+
+/*
+ * The Webserver in SWUpdate expets a custom header
+ * with the filename
+ */
+#define CUSTOM_HEADER "X_FILENAME: "
+#define MAX_WAIT_MS 30000
+#define POST_URL "/handle_post_request"
+#define STATUS_URL "/getstatus.json"
+
+/*
+ * The hzandler checks if a remote update was successful
+ * asking for the status. It is supposed that the boards go on
+ * until they report a success or failure.
+ * Following timeout is introduced in case boards answer, but they do not
+ * go out for some reasons from the running state.
+ */
+#define TIMEOUT_GET_ANSWER_SEC 900 /* 15 minutes */
+#define POLLING_TIME_REQ_STATUS 50 /* in mSec */
+
+void swuforward_handler(void);
+
+/*
+ * Track each connection
+ * The handler maintains a list of connections and sends the SWU
+ * to all of them at once.
+ */
+struct curlconn {
+ CURL *curl_handle; /* CURL handle for posting image */
+ const void *buffer; /* temprary buffer to transfer image */
+ unsigned int nbytes; /* bytes to be transferred per iteration */
+ size_t total_bytes; /* size of SWU image */
+ char *url; /* URL for forwarding */
+ bool gotMsg; /* set if the remote board has sent a new msg */
+ RECOVERY_STATUS SWUpdateStatus; /* final status of update */
+ LIST_ENTRY(curlconn) next;
+};
+LIST_HEAD(listconns, curlconn);
+
+/*
+ * global handler data
+ *
+ */
+struct hnd_priv {
+ CURLM *cm; /* libcurl multi handle */
+ unsigned int maxwaitms; /* maximum time in CURL wait */
+ size_t size; /* size of SWU */
+ struct listconns conns; /* list of connections */
+};
+
+/*
+ * CURL callback when posting data
+ * Read from connection buffer and copy to CURL buffer
+ */
+static size_t curl_read_data(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+ struct curlconn *conn = (struct curlconn *)userp;
+ size_t nbytes;
+
+ if (!nmemb)
+ return 0;
+ if (!userp) {
+ ERROR("Failure IPC stream file descriptor \n");
+ return -EFAULT;
+ }
+
+ if (conn->nbytes > (nmemb * size))
+ nbytes = nmemb * size;
+ else
+ nbytes = conn->nbytes;
+
+ memcpy(buffer, conn->buffer, nbytes);
+
+ conn->nbytes -= nbytes;
+
+ return nmemb;
+}
+
+/*
+ * This is the copyimage's callback. When called,
+ * there is a buffer to be passed to curl connections
+ */
+static int swu_forward_data(void *data, const void *buf, unsigned int len)
+{
+ struct hnd_priv *priv = (struct hnd_priv *)data;
+ int ret, still_running = 0;
+
+ struct curlconn *conn;
+ LIST_FOREACH(conn, &priv->conns, next) {
+ conn->nbytes += len;
+ conn->buffer = buf;
+ }
+
+ do {
+ int ready = 1;
+
+ LIST_FOREACH(conn, &priv->conns, next) {
+ if (conn->nbytes > 0) {
+ ready = 0;
+ break;
+ }
+ }
+
+ /*
+ * Buffer transferred to all connections,
+ * just returns and wait for next
+ */
+ if (ready)
+ break;
+
+ int numfds=0;
+ ret = curl_multi_wait(priv->cm, NULL, 0, priv->maxwaitms, &numfds);
+ if (ret != CURLM_OK) {
+ ERROR("curl_multi_wait() returns %d", ret);
+ return FAILURE;
+ }
+
+ curl_multi_perform(priv->cm, &still_running);
+ } while (still_running);
+
+ if (!still_running) {
+ LIST_FOREACH(conn, &priv->conns, next) {
+ /* check if the buffer was transfered */
+ if (conn->nbytes) {
+ ERROR("Connection lost, data not transferred");
+ }
+ conn->total_bytes += len - conn->nbytes;
+ if (conn->total_bytes != priv->size) {
+ ERROR("Connection lost, SWU not transferred");
+ return -EIO;
+ }
+ }
+ ERROR("Connection lost, skipping data");
+ }
+
+ return 0;
+}
+
+/*
+ * Send a GET to retrieve all traces from the connected board
+ */
+static int get_answer(struct curlconn *conn, RECOVERY_STATUS *result, bool ignore)
+{
+ channel_data_t channel_cfg = {
+ .debug = false,
+ .retries = 0,
+ .retry_sleep = 0,
+ .usessl = false};
+ channel_op_res_t response;
+ channel_t *channel = channel_new();
+
+ /*
+ * Open a curl channel, do not connect yet
+ */
+ if (channel->open(channel, &channel_cfg) != CHANNEL_OK) {
+ return -EIO;
+ }
+
+ if (asprintf(&channel_cfg.url, "%s/%s",
+ conn->url, STATUS_URL) < 0) {
+ ERROR("Out of memory.\n");
+ return -ENOMEM;
+ }
+
+ /* Retrieve last message */
+ response = channel->get(channel, (void *)&channel_cfg);
+
+ if (response != CHANNEL_OK) {
+ channel->close(channel);
+ free(channel);
+ free(channel_cfg.url);
+ return -1;
+ }
+
+ json_object *json_data = json_get_path_key(
+ channel_cfg.json_reply,
+ (const char *[]){"Status", NULL});
+ if (json_data == NULL) {
+ ERROR("Got malformed JSON: Could not find Status");
+ DEBUG("Got JSON: %s\n", json_object_to_json_string(json_data));
+ return -1;
+ }
+ int status = json_object_get_int(json_data);
+
+ json_data = json_get_path_key(
+ channel_cfg.json_reply,
+ (const char *[]){"Msg", NULL});
+ const char *msg = json_object_get_string(json_data);
+
+ json_data = json_get_path_key(
+ channel_cfg.json_reply,
+ (const char *[]){"LastResult", NULL});
+ if (json_data == NULL) {
+ ERROR("Got malformed JSON: Could not find Last Result");
+ DEBUG("Got JSON: %s\n", json_object_to_json_string(json_data));
+ return -1;
+ }
+ int lastResult = json_object_get_int(json_data);
+
+ if (strlen(msg) > 0)
+ conn->gotMsg = (strlen(msg) > 0) ? true : false;
+
+ if (!ignore) {
+ if (strlen(msg)) {
+ TRACE("Update to %s : %s", conn->url, msg);
+ }
+ if (status == IDLE) {
+ TRACE("Update to %s : %s", conn->url,
+ (lastResult == SUCCESS) ? "SUCCESS !" : "FAILURE");
+ }
+ }
+
+ free(channel_cfg.url);
+ channel->close(channel);
+ free(channel);
+
+ *result = lastResult;
+
+ return status;
+}
+
+static int retrieve_msgs(struct hnd_priv *priv, bool ignore)
+{
+ struct curlconn *conn;
+ int ret;
+ int result = 0;
+
+ LIST_FOREACH(conn, &priv->conns, next) {
+ int count = 0;
+ do {
+ ret = get_answer(conn, &conn->SWUpdateStatus, ignore);
+ if (!conn->gotMsg) {
+ usleep(POLLING_TIME_REQ_STATUS * 1000);
+ count++;
+ } else
+ count = 0;
+ if (count > ((TIMEOUT_GET_ANSWER_SEC * 1000) /
+ POLLING_TIME_REQ_STATUS)) {
+ ret = -ETIMEDOUT;
+ }
+ } while (ret > 0);
+ if (ret != 0 || (conn->SWUpdateStatus != SUCCESS)) {
+ ERROR("Update to %s was NOT successful !", conn->url);
+ result = -1;
+ }
+ }
+
+ return result;
+}
+
+static int install_remote_swu(struct img_type *img,
+ void __attribute__ ((__unused__)) *data)
+{
+ struct hnd_priv priv;
+ struct curlconn *conn;
+ int ret, still_running = 0;
+ struct dict_entry *url;
+ struct curl_slist *headerlist;
+ CURLMsg *msg = NULL;
+
+ /*
+ * A single SWU can contains encrypted artifacts,
+ * but the SWU itself canot be encrypted.
+ * Raise an error if the encrypted attribute is set
+ */
+
+ if (img->is_encrypted) {
+ ERROR("SWU to be forwarded cannot be encrypted");
+ return -EINVAL;
+ }
+
+ /* Reset list of connections */
+ LIST_INIT(&priv.conns);
+
+ /* initialize CURL */
+ ret = curl_global_init(CURL_GLOBAL_DEFAULT);
+ if (ret != CURLE_OK) {
+ ret = FAILURE;
+ goto handler_exit;
+ }
+ priv.cm = curl_multi_init();
+ priv.maxwaitms = MAX_WAIT_MS;
+ priv.size = img->size;
+
+ /*
+ * Parse handler properties to get URLs for destination
+ *
+ */
+ LIST_FOREACH(url, &img->properties, next) {
+ char curlheader[SWUPDATE_GENERAL_STRING_SIZE + strlen(CUSTOM_HEADER)];
+
+ if (!url->varname || !url->value || strcmp(url->varname, "url"))
+ continue;
+
+ conn = (struct curlconn *)calloc(1, sizeof(struct curlconn));
+ if (!conn) {
+ ERROR("FAULT: no memory");
+ ret = -ENOMEM;
+ goto handler_exit;
+ }
+
+ headerlist = NULL;
+
+ conn->curl_handle = curl_easy_init();
+ conn->url = url->value;
+
+ if (!conn->curl_handle) {
+ /* something very bad, it should never happen */
+ ERROR("FAULT: no handle from libcurl");
+ return FAILURE;
+ }
+
+ snprintf(curlheader, sizeof(curlheader), "%s%s", CUSTOM_HEADER, img->fname);
+ headerlist = curl_slist_append(headerlist, curlheader);
+
+ if ((curl_easy_setopt(conn->curl_handle, CURLOPT_POST, 1L) != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_READFUNCTION,
+ curl_read_data) != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_READDATA,
+ conn) !=CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_USERAGENT,
+ "libcurl-agent/1.0") != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_POSTFIELDSIZE,
+ img->size)!=CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_HTTPHEADER,
+ headerlist) != CURLE_OK)) {
+ ERROR("curl set_option was not successful");
+ ret = FAILURE;
+ goto handler_exit;
+ }
+
+ /* get verbose debug output please */
+ curl_easy_setopt(conn->curl_handle, CURLOPT_VERBOSE, 1L);
+
+ char *posturl = NULL;
+ posturl = (char *)malloc(strlen(conn->url) + strlen(POST_URL) + 1);
+ sprintf(posturl, "%s%s", conn->url, POST_URL);
+
+ /* Set URL */
+ if (curl_easy_setopt(conn->curl_handle, CURLOPT_URL, posturl) != CURLE_OK) {
+ ERROR("Cannot set URL in libcurl");
+ free(posturl);
+ ret = FAILURE;
+ goto handler_exit;
+ }
+ free(posturl);
+ curl_multi_add_handle(priv.cm, conn->curl_handle);
+ LIST_INSERT_HEAD(&priv.conns, conn, next);
+ }
+
+ retrieve_msgs(&priv, true);
+
+ curl_multi_perform(priv.cm, &still_running);
+
+ ret = copyimage(&priv, img, swu_forward_data);
+
+ if (ret) {
+ ERROR("Transferring SWU image was not successful");
+ goto handler_exit;
+ }
+
+ /*
+ * Now checks if transfer was successful
+ */
+ int msgs_left = 0;
+ while ((msg = curl_multi_info_read(priv.cm, &msgs_left))) {
+ CURL *eh = NULL;
+ int http_status_code=0;
+ if (msg->msg != CURLMSG_DONE) {
+ ERROR("curl_multi_info_read(), CURLMsg=%d\n", msg->msg);
+ ret = FAILURE;
+ break;
+ }
+ LIST_FOREACH(conn, &priv.conns, next) {
+ if (conn->curl_handle == msg->easy_handle) {
+ eh = conn->curl_handle;
+ break;
+ }
+ }
+
+ if (!eh) {
+ ERROR("curl handle not found in connections");
+ ret = FAILURE;
+ break;
+ }
+
+ curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code);
+
+ if (http_status_code != 200) {
+ ERROR("Sending %s to %s failed with %d",
+ img->fname, conn->url, http_status_code);
+ ret = FAILURE;
+ break;
+ }
+ }
+
+ /*
+ * Now check if remote updates were successful
+ */
+ if (!ret) {
+ ret = retrieve_msgs(&priv, false);
+ }
+
+handler_exit:
+ LIST_FOREACH(conn, &priv.conns, next) {
+ LIST_REMOVE(conn, next);
+ curl_multi_remove_handle(priv.cm, conn->curl_handle);
+ curl_easy_cleanup(conn->curl_handle);
+ free(conn);
+ }
+
+ curl_multi_cleanup(priv.cm);
+
+ return ret;
+}
+
+__attribute__((constructor))
+void swuforward_handler(void)
+{
+ register_handler("swuforward", install_remote_swu,
+ IMAGE_HANDLER, NULL);
+}
--
2.7.4

Reply all
Reply to author
Forward
0 new messages