From f598290e5470235c3430424ebecadce825819760 Mon Sep 17 00:00:00 2001 From: Pim van Pelt <pim@ipng.nl> Date: Sun, 3 Dec 2017 23:56:16 +0100 Subject: [PATCH] Add (private) OTA libs --- .../include/mgos_ota_http_client.h | 29 +++ libs/ota-http-client/mos.yml | 28 +++ .../src/mgos_ota_http_client.c | 187 ++++++++++++++ .../include/mgos_ota_http_server.h | 23 ++ libs/ota-http-server/mos.yml | 22 ++ .../src/mgos_ota_http_server.c | 238 ++++++++++++++++++ .../include/mgos_rpc_service_ota.h | 26 ++ libs/rpc-service-ota/mos.yml | 17 ++ .../src/mgos_rpc_service_ota.c | 227 +++++++++++++++++ mos.yml | 5 +- 10 files changed, 801 insertions(+), 1 deletion(-) create mode 100644 libs/ota-http-client/include/mgos_ota_http_client.h create mode 100644 libs/ota-http-client/mos.yml create mode 100644 libs/ota-http-client/src/mgos_ota_http_client.c create mode 100644 libs/ota-http-server/include/mgos_ota_http_server.h create mode 100644 libs/ota-http-server/mos.yml create mode 100644 libs/ota-http-server/src/mgos_ota_http_server.c create mode 100644 libs/rpc-service-ota/include/mgos_rpc_service_ota.h create mode 100644 libs/rpc-service-ota/mos.yml create mode 100644 libs/rpc-service-ota/src/mgos_rpc_service_ota.c diff --git a/libs/ota-http-client/include/mgos_ota_http_client.h b/libs/ota-http-client/include/mgos_ota_http_client.h new file mode 100644 index 0000000..8d1b3f9 --- /dev/null +++ b/libs/ota-http-client/include/mgos_ota_http_client.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + * + * An updater implementation that fetches FW from the given URL. + */ + +#ifndef CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_ +#define CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_ + +#include <stdbool.h> + +#include "mgos_updater_common.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#if MGOS_ENABLE_UPDATER +bool mgos_ota_http_client_init(void); + +void mgos_ota_http_start(struct update_context *ctx, const char *url); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_ */ diff --git a/libs/ota-http-client/mos.yml b/libs/ota-http-client/mos.yml new file mode 100644 index 0000000..af39858 --- /dev/null +++ b/libs/ota-http-client/mos.yml @@ -0,0 +1,28 @@ +author: mongoose-os +description: Implements Mongoose OS OTA HTTP client +type: lib +version: 1.18 + +sources: + - src +includes: + - include + +config_schema: + - ["update.url", "s", {title : "Fetch updates form here"}] + - ["update.interval", "i", {title : "Check for updates this often"}] + + # Default CA bundle for updating from mongoose-os.com (and any other site that uses LetsEncrypt) and S3. + - ["update.ssl_ca_file", "s", "ca.pem", {title : "TLS CA file"}] + - ["update.ssl_client_cert_file", "s", {title: "TLS client cert file"}] + - ["update.ssl_server_name", "s", {title : "TLS Server Name"}] + +tags: + - c + - ota + - http + +build_vars: + MGOS_ENABLE_UPDATER: 1 + +manifest_version: 2017-09-29 diff --git a/libs/ota-http-client/src/mgos_ota_http_client.c b/libs/ota-http-client/src/mgos_ota_http_client.c new file mode 100644 index 0000000..616338a --- /dev/null +++ b/libs/ota-http-client/src/mgos_ota_http_client.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mgos_ota_http_client.h" + +#include "common/cs_dbg.h" +#include "mgos_hal.h" +#include "mgos_mongoose.h" +#include "mgos_config.h" +#include "mgos_ro_vars.h" +#include "mgos_timers.h" +#include "mgos_utils.h" + +#if MGOS_ENABLE_UPDATER + +static void fw_download_handler(struct mg_connection *c, int ev, void *p, + void *user_data) { + struct mbuf *io = &c->recv_mbuf; + struct update_context *ctx = (struct update_context *) user_data; + int res = 0; + struct mg_str *loc; + (void) p; + + switch (ev) { + case MG_EV_CONNECT: { + int result = *((int *) p); + if (result != 0) LOG(LL_ERROR, ("connect error: %d", result)); + break; + } + case MG_EV_RECV: { + if (ctx->file_size == 0) { + LOG(LL_DEBUG, ("Looking for HTTP header")); + struct http_message hm; + int parsed = mg_parse_http(io->buf, io->len, &hm, 0); + if (parsed <= 0) { + return; + } + if (hm.resp_code != 200) { + if (hm.resp_code == 304) { + ctx->result = 1; + ctx->need_reboot = false; + ctx->status_msg = "Not Modified"; + updater_finish(ctx); + } else if ((hm.resp_code == 301 || hm.resp_code == 302) && + (loc = mg_get_http_header(&hm, "Location")) != NULL) { + /* NUL-terminate the URL. Every header must be followed by \r\n, + * so there is deifnitely space there. */ + ((char *) loc->p)[loc->len] = '\0'; + /* We were told to look elsewhere. Detach update context from this + * connection so that it doesn't get finalized when it's closed. */ + mgos_ota_http_start(ctx, loc->p); + c->user_data = NULL; + } else { + ctx->result = -hm.resp_code; + ctx->need_reboot = false; + ctx->status_msg = "Invalid HTTP response code"; + updater_finish(ctx); + } + c->flags |= MG_F_CLOSE_IMMEDIATELY; + return; + } + if (hm.body.len != 0) { + LOG(LL_DEBUG, ("HTTP header: file size: %d", (int) hm.body.len)); + if (hm.body.len == (size_t) ~0) { + LOG(LL_ERROR, ("Invalid content-length, perhaps chunked-encoding")); + ctx->status_msg = + "Invalid content-length, perhaps chunked-encoding"; + c->flags |= MG_F_CLOSE_IMMEDIATELY; + break; + } else { + ctx->file_size = hm.body.len; + } + + mbuf_remove(io, parsed); + } + } + + if (io->len != 0) { + res = updater_process(ctx, io->buf, io->len); + mbuf_remove(io, io->len); + + if (res == 0) { + if (is_write_finished(ctx)) res = updater_finalize(ctx); + if (res == 0) { + /* Need more data, everything is OK */ + break; + } + } + + if (res < 0) { + /* Error */ + LOG(LL_ERROR, ("Update error: %d %s", ctx->result, ctx->status_msg)); + } + c->flags |= MG_F_CLOSE_IMMEDIATELY; + } + break; + } + case MG_EV_CLOSE: { + if (ctx == NULL) break; + + if (is_write_finished(ctx)) updater_finalize(ctx); + + if (!is_update_finished(ctx)) { + /* Update failed or connection was terminated by server */ + if (ctx->status_msg == NULL) ctx->status_msg = "Update failed"; + ctx->result = -1; + } else if (is_reboot_required(ctx)) { + LOG(LL_INFO, ("Rebooting device")); + mgos_system_restart_after(100); + } + updater_finish(ctx); + updater_context_free(ctx); + c->user_data = NULL; + break; + } + } +} + +void mgos_ota_http_start(struct update_context *ctx, const char *url) { + LOG(LL_INFO, ("Update URL: %s, ct: %d, isv? %d", url, + ctx->fctx.commit_timeout, ctx->ignore_same_version)); + + struct mg_connect_opts opts; + memset(&opts, 0, sizeof(opts)); + +#if MG_ENABLE_SSL + if (strlen(url) > 8 && strncmp(url, "https://", 8) == 0) { + opts.ssl_server_name = mgos_sys_config_get_update_ssl_server_name(); + opts.ssl_ca_cert = mgos_sys_config_get_update_ssl_ca_file(); + opts.ssl_cert = mgos_sys_config_get_update_ssl_client_cert_file(); + } +#endif + + char ehb[150]; + char *extra_headers = ehb; + mg_asprintf(&extra_headers, sizeof(ehb), + "X-MGOS-Device-ID: %s %s\r\n" + "X-MGOS-FW-Version: %s %s %s\r\n", + (mgos_sys_config_get_device_id() ? mgos_sys_config_get_device_id() : "-"), + mgos_sys_ro_vars_get_mac_address(), + mgos_sys_ro_vars_get_arch(), + mgos_sys_ro_vars_get_fw_version(), + mgos_sys_ro_vars_get_fw_id()); + + struct mg_connection *c = mg_connect_http_opt( + mgos_get_mgr(), fw_download_handler, ctx, opts, url, extra_headers, NULL); + + if (extra_headers != ehb) free(extra_headers); + + if (c == NULL) { + LOG(LL_ERROR, ("Failed to connect to %s", url)); + ctx->result = -10; + ctx->need_reboot = false; + ctx->status_msg = "Failed to connect"; + updater_finish(ctx); + return; + } + + ctx->nc = c; +} + +static void mgos_ota_timer_cb(void *arg) { + const struct mgos_config_update *mcu = mgos_sys_config_get_update(); + if (mcu->url == NULL) return; + struct update_context *ctx = updater_context_create(); + if (ctx == NULL) return; + ctx->ignore_same_version = true; + ctx->fctx.commit_timeout = mcu->commit_timeout; + mgos_ota_http_start(ctx, mcu->url); + + (void) arg; +} + +bool mgos_ota_http_client_init(void) { + const struct mgos_config_update *mcu = mgos_sys_config_get_update(); + if (mcu->url != NULL && mcu->interval > 0) { + LOG(LL_INFO, + ("Updates from %s, every %d seconds", mcu->url, mcu->interval)); + mgos_set_timer(mcu->interval * 1000, true /* repeat */, mgos_ota_timer_cb, + mcu->url); + } + return true; +} + +#endif /* MGOS_ENABLE_UPDATER */ diff --git a/libs/ota-http-server/include/mgos_ota_http_server.h b/libs/ota-http-server/include/mgos_ota_http_server.h new file mode 100644 index 0000000..3eb5d2f --- /dev/null +++ b/libs/ota-http-server/include/mgos_ota_http_server.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_ +#define CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_ + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#if MGOS_ENABLE_UPDATER +bool mgos_ota_http_server_init(void); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_ */ diff --git a/libs/ota-http-server/mos.yml b/libs/ota-http-server/mos.yml new file mode 100644 index 0000000..a512293 --- /dev/null +++ b/libs/ota-http-server/mos.yml @@ -0,0 +1,22 @@ +author: mongoose-os +description: Implements Mongoose OS OTA HTTP server +type: lib +version: 1.18 + +sources: + - src +includes: + - include +config_schema: + - ["update.enable_post", "b", true, {title : "Enable POST updates"}] + +libs: + - origin: https://github.com/mongoose-os-libs/http-server + - origin: libs/ota-http-client + +tags: + - c + - ota + - http + +manifest_version: 2017-09-29 diff --git a/libs/ota-http-server/src/mgos_ota_http_server.c b/libs/ota-http-server/src/mgos_ota_http_server.c new file mode 100644 index 0000000..af0aca8 --- /dev/null +++ b/libs/ota-http-server/src/mgos_ota_http_server.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mgos_ota_http_server.h" +#include "mgos_ota_http_client.h" + +#include "mgos_http_server.h" + +#include "common/cs_dbg.h" +#include "mgos_hal.h" +#include "mgos_mongoose.h" +#include "mgos_config.h" +#include "mgos_timers.h" +#include "mgos_utils.h" + +static void handle_update_post(struct mg_connection *c, int ev, void *p) { + struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p; + struct update_context *ctx = (struct update_context *) c->user_data; + if (ctx == NULL && ev != MG_EV_HTTP_MULTIPART_REQUEST) return; + switch (ev) { + case MG_EV_HTTP_MULTIPART_REQUEST: { + ctx = updater_context_create(); + if (ctx != NULL) { + ctx->nc = c; + c->user_data = ctx; + } else { + c->flags |= MG_F_CLOSE_IMMEDIATELY; + } + break; + } + case MG_EV_HTTP_PART_BEGIN: { + LOG(LL_DEBUG, ("MG_EV_HTTP_PART_BEGIN: %p %s %s", ctx, mp->var_name, + mp->file_name)); + /* We use ctx->file_name as a temp buffer for non-file variable values. */ + if (mp->file_name[0] == '\0') { + ctx->file_name[0] = '\0'; + } + break; + } + case MG_EV_HTTP_PART_DATA: { + LOG(LL_DEBUG, ("MG_EV_HTTP_PART_DATA: %p %s %s %d", ctx, mp->var_name, + mp->file_name, (int) mp->data.len)); + + if (mp->file_name[0] == '\0') { + /* It's a non-file form variable. */ + size_t l = strlen(ctx->file_name); + size_t avail = sizeof(ctx->file_name) - l - 1; + strncat(ctx->file_name, mp->data.p, MIN(mp->data.len, avail)); + break; + } else if (!is_update_finished(ctx)) { + updater_process(ctx, mp->data.p, mp->data.len); + LOG(LL_DEBUG, ("updater_process res: %d", ctx->result)); + } else { + /* Don't close connection just yet, not all browsers like that. */ + } + break; + } + case MG_EV_HTTP_PART_END: { + LOG(LL_DEBUG, ("MG_EV_HTTP_PART_END: %p %s %s %d", ctx, mp->var_name, + mp->file_name, mp->status)); + /* Part finished with an error. REQUEST_END will follow. */ + if (mp->status < 0) break; + if (mp->file_name[0] == '\0') { + /* It's a non-file form variable. Value is in ctx->file_name. */ + LOG(LL_DEBUG, ("Got var: %s=%s", mp->var_name, ctx->file_name)); + /* Commit timeout can be set after flashing. */ + if (strcmp(mp->var_name, "commit_timeout") == 0) { + ctx->fctx.commit_timeout = atoi(ctx->file_name); + } + } else { + /* End of the fw part, but there may still be parts with vars to follow, + * which can modify settings (that can be applied post-flashing). */ + } + break; + } + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + LOG(LL_DEBUG, + ("MG_EV_HTTP_MULTIPART_REQUEST_END: %p %d", ctx, mp->status)); + /* Whatever happens, this is the last thing we do. */ + c->flags |= MG_F_SEND_AND_CLOSE; + + if (ctx == NULL) break; + if (is_write_finished(ctx)) updater_finalize(ctx); + if (!is_update_finished(ctx)) { + ctx->result = -1; + ctx->status_msg = "Update aborted"; + updater_finish(ctx); + } + if (mp->status < 0) { + /* mp->status < 0 means connection is dead, do not send reply */ + } else { + int code = (ctx->result > 0 ? 200 : 400); + mg_send_response_line(c, code, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(c, "%s\r\n", + ctx->status_msg ? ctx->status_msg : "Unknown error"); + if (is_reboot_required(ctx)) { + LOG(LL_INFO, ("Rebooting device")); + mgos_system_restart_after(101); + } + c->flags |= MG_F_SEND_AND_CLOSE; + } + updater_context_free(ctx); + c->user_data = NULL; + break; + } + } +} + +struct mg_connection *s_update_request_conn; + +static void mgos_ota_result_cb(struct update_context *ctx) { + if (ctx != updater_context_get_current()) return; + if (s_update_request_conn != NULL) { + int code = (ctx->result > 0 ? 200 : 500); + mg_send_response_line(s_update_request_conn, code, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(s_update_request_conn, "(%d) %s\r\n", ctx->result, + ctx->status_msg); + s_update_request_conn->flags |= MG_F_SEND_AND_CLOSE; + s_update_request_conn = NULL; + } +} + +static void update_handler(struct mg_connection *c, int ev, void *ev_data, + void *user_data) { + switch (ev) { + case MG_EV_HTTP_MULTIPART_REQUEST: + case MG_EV_HTTP_PART_BEGIN: + case MG_EV_HTTP_PART_DATA: + case MG_EV_HTTP_PART_END: + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + if (mgos_sys_config_get_update_enable_post()) { + handle_update_post(c, ev, ev_data); + } else { + mg_send_response_line(c, 400, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(c, "POST updates are disabled."); + c->flags |= MG_F_SEND_AND_CLOSE; + } + return; + } + case MG_EV_HTTP_REQUEST: { + struct http_message *hm = (struct http_message *) ev_data; + if (updater_context_get_current() != NULL) { + mg_send_response_line(c, 409, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(c, "Another update is in progress.\r\n"); + c->flags |= MG_F_SEND_AND_CLOSE; + return; + } + const struct mgos_config_update *mcu = mgos_sys_config_get_update(); + char *url = mcu->url; + int commit_timeout = mcu->commit_timeout; + bool ignore_same_version = true; + struct mg_str params = + (mg_vcmp(&hm->method, "POST") == 0 ? hm->body : hm->query_string); + size_t buf_len = params.len; + char *buf = calloc(params.len, 1), *p = buf; + int len = mg_get_http_var(¶ms, "url", p, buf_len); + if (len > 0) { + url = p; + p += len + 1; + buf_len -= len + 1; + } + len = mg_get_http_var(¶ms, "commit_timeout", p, buf_len); + if (len > 0) { + commit_timeout = atoi(p); + } + len = mg_get_http_var(¶ms, "ignore_same_version", p, buf_len); + if (len > 0) { + ignore_same_version = (atoi(p) > 0); + } + if (url != NULL) { + s_update_request_conn = c; + struct update_context *ctx = updater_context_create(); + if (ctx == NULL) { + mg_send_response_line(c, 409, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(c, "Failed to create updater context.\r\n"); + c->flags |= MG_F_SEND_AND_CLOSE; + return; + } + ctx->ignore_same_version = ignore_same_version; + ctx->fctx.commit_timeout = commit_timeout; + ctx->result_cb = mgos_ota_result_cb; + mgos_ota_http_start(ctx, url); + + } else { + mg_send_response_line(c, 400, + "Content-Type: text/plain\r\n" + "Connection: close\r\n"); + mg_printf(c, "Update URL not specified and none is configured.\r\n"); + c->flags |= MG_F_SEND_AND_CLOSE; + } + free(buf); + break; + } + case MG_EV_CLOSE: { + if (s_update_request_conn == c) { + /* Client went away while waiting for response. */ + s_update_request_conn = NULL; + } + break; + } + } + (void) user_data; +} + +static void update_action_handler(struct mg_connection *c, int ev, void *p, + void *user_data) { + if (ev != MG_EV_HTTP_REQUEST) return; + struct http_message *hm = (struct http_message *) p; + bool is_commit = (mg_vcmp(&hm->uri, "/update/commit") == 0); + bool ok = + (is_commit ? mgos_upd_commit() : mgos_upd_revert(false /* reboot */)); + mg_send_response_line(c, (ok ? 200 : 400), + "Content-Type: text/html\r\n" + "Connection: close"); + mg_printf(c, "\r\n%s\r\n", (ok ? "Ok" : "Error")); + c->flags |= MG_F_SEND_AND_CLOSE; + if (ok && !is_commit) mgos_system_restart_after(100); + (void) user_data; +} + +bool mgos_ota_http_server_init(void) { + mgos_register_http_endpoint("/update/commit", update_action_handler, NULL); + mgos_register_http_endpoint("/update/revert", update_action_handler, NULL); + mgos_register_http_endpoint("/update", update_handler, NULL); + return true; +} diff --git a/libs/rpc-service-ota/include/mgos_rpc_service_ota.h b/libs/rpc-service-ota/include/mgos_rpc_service_ota.h new file mode 100644 index 0000000..1a56f61 --- /dev/null +++ b/libs/rpc-service-ota/include/mgos_rpc_service_ota.h @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2016 Cesanta Software Limited +* All rights reserved +*/ + +#ifndef CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_ +#define CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_ + +#include <inttypes.h> +#include <stdbool.h> +#include "common/mg_str.h" +#include "mgos_features.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +bool mgos_rpc_service_ota_init(void); +void mgos_updater_rpc_finish(int error_code, int64_t id, + const struct mg_str src); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_ */ diff --git a/libs/rpc-service-ota/mos.yml b/libs/rpc-service-ota/mos.yml new file mode 100644 index 0000000..9e2e577 --- /dev/null +++ b/libs/rpc-service-ota/mos.yml @@ -0,0 +1,17 @@ +author: mongoose-os +description: Support for Over-The-Air update via RPC +type: lib +version: 1.18 +manifest_version: 2017-09-29 +sources: + - src +includes: + - include +libs: + - origin: https://github.com/mongoose-os-libs/rpc-common + - origin: libs/ota-http-client +tags: + - c + - ota + - rpc + - updater diff --git a/libs/rpc-service-ota/src/mgos_rpc_service_ota.c b/libs/rpc-service-ota/src/mgos_rpc_service_ota.c new file mode 100644 index 0000000..14056e4 --- /dev/null +++ b/libs/rpc-service-ota/src/mgos_rpc_service_ota.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mgos_rpc_service_ota.h" + +#include "mg_rpc.h" +#include "mgos_rpc.h" +#include "mgos_ota_http_client.h" + +#include "common/cs_dbg.h" +#include "common/mg_str.h" +#include "mgos_mongoose.h" +#include "mgos_updater_common.h" +#include "mgos_utils.h" + +static struct mg_rpc_request_info *s_update_req; + +static void mg_rpc_updater_result(struct update_context *ctx) { + if (s_update_req == NULL) return; + mg_rpc_send_errorf(s_update_req, (ctx->result > 0 ? 0 : -1), ctx->status_msg); + s_update_req = NULL; +} + +static void handle_update_req(struct mg_rpc_request_info *ri, void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + char *blob_url = NULL; + struct json_token url_tok = JSON_INVALID_TOKEN; + int commit_timeout = 0; + struct update_context *ctx = NULL; + + LOG(LL_DEBUG, ("Update request received: %.*s", (int) args.len, args.p)); + + const char *reply = "Malformed request"; + + if (args.len == 0) { + goto clean; + } + + json_scanf(args.p, args.len, ri->args_fmt, &url_tok, &commit_timeout); + + if (url_tok.len == 0 || url_tok.type != JSON_TYPE_STRING) goto clean; + + LOG(LL_DEBUG, ("URL: %.*s commit_timeout: %d", url_tok.len, url_tok.ptr, + commit_timeout)); + + /* + * If user setup callback for updater, just call it. + * User can start update with Sys.updater.start() + */ + + blob_url = calloc(1, url_tok.len + 1); + if (blob_url == NULL) { + LOG(LL_ERROR, ("Out of memory")); + return; + } + + memcpy(blob_url, url_tok.ptr, url_tok.len); + + ctx = updater_context_create(); + if (ctx == NULL) { + reply = "Failed to init updater"; + goto clean; + } + ctx->fctx.commit_timeout = commit_timeout; + ctx->result_cb = mg_rpc_updater_result; + s_update_req = ri; + + mgos_ota_http_start(ctx, blob_url); + free(blob_url); + return; + +clean: + if (blob_url != NULL) free(blob_url); + LOG(LL_ERROR, ("Failed to start update: %s", reply)); + mg_rpc_send_errorf(ri, -1, reply); + ri = NULL; + (void) cb_arg; + (void) fi; +} + +static void handle_commit_req(struct mg_rpc_request_info *ri, void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + if (mgos_upd_commit()) { + mg_rpc_send_responsef(ri, NULL); + } else { + mg_rpc_send_errorf(ri, -1, NULL); + } + ri = NULL; + (void) cb_arg; + (void) fi; + (void) args; +} + +static void handle_revert_req(struct mg_rpc_request_info *ri, void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + if (mgos_upd_revert(false /* reboot */)) { + mg_rpc_send_responsef(ri, NULL); + mgos_system_restart_after(100); + } else { + mg_rpc_send_errorf(ri, -1, NULL); + } + ri = NULL; + (void) cb_arg; + (void) fi; + (void) args; +} + +static void handle_create_snapshot_req(struct mg_rpc_request_info *ri, + void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + const char *err_msg = NULL; + int ret = -1; + if (mgos_upd_is_committed()) { + ret = mgos_upd_create_snapshot(); + if (ret >= 0) { + bool set_as_revert = false; + int commit_timeout = -1; + json_scanf(args.p, args.len, ri->args_fmt, &set_as_revert, + &commit_timeout); + if (set_as_revert) { + struct mgos_upd_boot_state bs; + if (mgos_upd_boot_get_state(&bs)) { + bs.is_committed = false; + bs.revert_slot = ret; + if (mgos_upd_boot_set_state(&bs)) { + if (commit_timeout >= 0 && + !mgos_upd_set_commit_timeout(commit_timeout)) { + ret = -4; + err_msg = "Failed to set commit timeout"; + } + } else { + ret = -3; + err_msg = "Failed to set boot state"; + } + } else { + ret = -2; + err_msg = "Failed to get boot state"; + } + } + } else { + err_msg = "Failed to create snapshot"; + } + } else { + ret = -1; + err_msg = "Cannot create snapshots in uncommitted state"; + } + if (ret >= 0) { + mg_rpc_send_responsef(ri, "{slot: %d}", ret); + } else { + mg_rpc_send_errorf(ri, ret, err_msg); + } + (void) cb_arg; + (void) fi; +} + +static void handle_get_boot_state_req(struct mg_rpc_request_info *ri, + void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + struct mgos_upd_boot_state bs; + if (!mgos_upd_boot_get_state(&bs)) { + mg_rpc_send_errorf(ri, -1, NULL); + } else { + mg_rpc_send_responsef(ri, + "{active_slot: %d, is_committed: %B, revert_slot: " + "%d, commit_timeout: %d}", + bs.active_slot, bs.is_committed, bs.revert_slot, + mgos_upd_get_commit_timeout()); + } + (void) cb_arg; + (void) fi; + (void) args; +} + +static void handle_set_boot_state_req(struct mg_rpc_request_info *ri, + void *cb_arg, + struct mg_rpc_frame_info *fi, + struct mg_str args) { + int ret = 0; + struct mgos_upd_boot_state bs; + if (mgos_upd_boot_get_state(&bs)) { + int commit_timeout = -1; + if (json_scanf(args.p, args.len, ri->args_fmt, &bs.active_slot, + &bs.is_committed, &bs.revert_slot, &commit_timeout) > 0) { + ret = mgos_upd_boot_set_state(&bs) ? 0 : -3; + if (ret == 0 && commit_timeout >= 0) { + ret = mgos_upd_set_commit_timeout(commit_timeout) ? 0 : -4; + } + } else { + ret = -2; + } + } else { + ret = -1; + } + if (ret == 0) { + mg_rpc_send_responsef(ri, NULL); + } else { + mg_rpc_send_errorf(ri, ret, NULL); + } + (void) cb_arg; + (void) fi; +} + +bool mgos_rpc_service_ota_init(void) { + struct mg_rpc *mg_rpc = mgos_rpc_get_global(); + if (mg_rpc == NULL) return true; + mg_rpc_add_handler(mg_rpc, "OTA.Update", "{url: %T, commit_timeout: %d}", + handle_update_req, NULL); + mg_rpc_add_handler(mg_rpc, "OTA.Commit", "", handle_commit_req, NULL); + mg_rpc_add_handler(mg_rpc, "OTA.Revert", "", handle_revert_req, NULL); + mg_rpc_add_handler(mg_rpc, "OTA.CreateSnapshot", + "{set_as_revert: %B, commit_timeout: %d}", + handle_create_snapshot_req, NULL); + mg_rpc_add_handler(mg_rpc, "OTA.GetBootState", "", handle_get_boot_state_req, + NULL); + mg_rpc_add_handler(mg_rpc, "OTA.SetBootState", + "{active_slot: %d, is_committed: %B, revert_slot: %d, " + "commit_timeout: %d}", + handle_set_boot_state_req, NULL); + return true; +} diff --git a/mos.yml b/mos.yml index d713830..9353aa1 100644 --- a/mos.yml +++ b/mos.yml @@ -77,7 +77,6 @@ libs: - origin: https://github.com/mongoose-os-libs/http-server - origin: https://github.com/mongoose-os-libs/rpc-service-config - origin: https://github.com/mongoose-os-libs/rpc-service-fs - - origin: https://github.com/mongoose-os-libs/rpc-uart - origin: https://github.com/mongoose-os-libs/prometheus-metrics - origin: https://github.com/mongoose-os-libs/mqtt - origin: https://github.com/mongoose-os-libs/pwm @@ -85,6 +84,10 @@ libs: - origin: https://github.com/mongoose-os-libs/spi - origin: https://github.com/mongoose-os-libs/ili9341-spi - origin: https://github.com/mongoose-os-libs/stmpe610-spi + - origin: libs/ota-http-client + - origin: libs/ota-http-server + - origin: libs/rpc-service-ota + # Used by the mos tool to catch mos binaries incompatible with this file format manifest_version: 2017-05-18