Rebase private OTA libs on 2.7

This commit is contained in:
Pim van Pelt
2018-10-28 12:12:24 +01:00
parent 497c62b382
commit 9ee393bc8a
29 changed files with 3836 additions and 521 deletions

View File

@ -0,0 +1,14 @@
Copyright (c) 2018 Cesanta Software Limited
All rights reserved
Licensed under the Apache License, Version 2.0 (the ""License"");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an ""AS IS"" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,26 @@
# Implementation of Mongoose OS OTA HTTP client
This library adds a device configuration section called `update`, where
a device could be configured to poll a specified HTTP URL for a new
app firmware.
Also, this library adds a C API to fetch a new firmware from the given
URL and update programmatically.
## Configuration section
The library adds the following object to the device configuration:
```javascript
"update": {
"commit_timeout": 0, // OTA commit timeout
"url": "", // HTTP URL to poll
"interval": 0, // Polling interval
"ssl_ca_file": "ca.pem", // TLS CA cert file
"ssl_client_cert_file": "", // TLS cert file
"ssl_server_name": "", // TLS server name
"enable_post": true
}
```

View File

@ -1,8 +1,18 @@
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* Copyright (c) 2014-2018 Cesanta Software Limited
* All rights reserved
*
* An updater implementation that fetches FW from the given URL.
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_
@ -16,13 +26,9 @@
extern "C" {
#endif /* __cplusplus */
#if MGOS_ENABLE_UPDATER
bool mgos_ota_http_client_init(void);
/* Start OTA update by pulling the firmware from the given URL. */
void mgos_ota_http_start(struct update_context *ctx, const char *url);
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -0,0 +1,6 @@
let OTA = {
// ## **`OTA.evdataOtaStatusMsg(evdata)`**
// Getter function for the `evdata` given to the event callback for the event
// `Event.OTA_STATUS`, see `Event.addHandler()` in `api_events.js`.
evdataOtaStatusMsg: ffi('char *mgos_ota_status_get_msg(void *)'),
};

View File

@ -1,13 +1,17 @@
author: mongoose-os
description: Implements Mongoose OS OTA HTTP client
type: lib
version: 1.18
version: 1.0
sources:
- src
includes:
- include
libs:
- origin: https://github.com/mongoose-os-libs/ota-common
config_schema:
- ["update.url", "s", {title : "Fetch updates form here"}]
- ["update.interval", "i", {title : "Check for updates this often"}]
@ -21,8 +25,7 @@ tags:
- c
- ota
- http
build_vars:
MGOS_ENABLE_UPDATER: 1
- rpc
- docs:net:OTA via HTTP GET
manifest_version: 2017-09-29

View File

@ -1,140 +1,182 @@
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* Copyright (c) 2014-2018 Cesanta Software Limited
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mgos_ota_http_client.h"
#include "common/cs_dbg.h"
#include "mgos_event.h"
#include "mgos_hal.h"
#include "mgos_mongoose.h"
#include "mgos_config.h"
#include "mgos_ro_vars.h"
#include "mgos_sys_config.h"
#include "mgos_timers.h"
#include "mgos_utils.h"
#if MGOS_ENABLE_UPDATER
static void mgos_ota_http_start_internal(struct update_context *ctx,
const char *url, bool restrict_url);
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 mbuf *io = &c->recv_mbuf;
struct update_context *ctx = (struct update_context *) user_data;
int res = 0;
struct mg_str *loc;
(void)p;
(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;
case MG_EV_CONNECT: {
int result = *((int *) p);
if (result != 0) {
LOG(LL_ERROR, ("Connect error: %d", result));
ctx->status_msg = "Failed to connect";
ctx->result = -10;
}
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;
}
case MG_EV_RECV: {
if (ctx->zip_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. */
LOG(LL_INFO, ("Got redirect: [%s]", loc->p));
/* Do not restrict redirected URLs, cause Github redirects */
mgos_ota_http_start_internal(ctx, loc->p, false);
c->user_data = NULL;
} else {
ctx->result = -hm.resp_code;
ctx->need_reboot = false;
ctx->status_msg = "Invalid HTTP response code";
LOG(LL_ERROR, ("%s: %d", ctx->status_msg, hm.resp_code));
updater_finish(ctx);
}
c->flags |= MG_F_CLOSE_IMMEDIATELY;
return;
}
if (hm.resp_code == 200 && hm.body.len != 0) {
LOG(LL_DEBUG, ("HTTP header: file size: %d", (int) hm.body.len));
if (hm.body.len == (size_t) ~0) {
ctx->status_msg =
"Invalid content-length, perhaps chunked-encoding";
LOG(LL_ERROR, (ctx->status_msg));
c->flags |= MG_F_CLOSE_IMMEDIATELY;
break;
} else {
ctx->zip_file_size = hm.body.len;
}
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";
mbuf_remove(io, parsed);
}
}
ctx->result = -1;
} else if (is_reboot_required(ctx)) {
LOG(LL_INFO, ("Rebooting device"));
mgos_system_restart_after(100);
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 = -5;
} else if (is_reboot_required(ctx)) {
LOG(LL_INFO, ("OTA finished, rebooting device"));
/* Give it 3 seconds to drain any possible pending RPC calls */
mgos_system_restart_after(3000);
}
updater_finish(ctx);
updater_context_free(ctx);
c->user_data = NULL;
break;
}
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));
mgos_ota_http_start_internal(ctx, url, true);
}
static void mgos_ota_http_start_internal(struct update_context *ctx,
const char *url, bool restrict_url) {
LOG(LL_INFO,
("Update URL: %s, ct: %d, isv? %d %d", url, ctx->fctx.commit_timeout,
ctx->ignore_same_version, restrict_url));
#if defined(MGOS_FREE_BUILD)
if (restrict_url) {
const char *allowed_url_prefixes[] = {
"https://github.com/mongoose-os-apps/", "https://mongoose-os.com/",
"http://mongoose-os.com/", "https://dash.mongoose-os.com/",
"http://dash.mongoose-os.com/", NULL,
};
int i;
for (i = 0; allowed_url_prefixes[i] != NULL; i++) {
const char *s = allowed_url_prefixes[i];
if (strncmp(s, url, strlen(s)) == 0) break;
}
if (allowed_url_prefixes[i] == NULL) {
ctx->result = -10;
ctx->need_reboot = false;
ctx->status_msg =
"Free version of OTA library can only perform OTA from "
"github.com/mongoose-os-apps/. For "
"commercial version, please contact "
"https://mongoose-os.com/contact.html";
LOG(LL_ERROR, ("%s", ctx->status_msg));
updater_finish(ctx);
updater_context_free(ctx);
return;
}
}
#endif
struct mg_connect_opts opts;
memset(&opts, 0, sizeof(opts));
@ -142,35 +184,34 @@ void mgos_ota_http_start(struct update_context *ctx, const char *url) {
#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();
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 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());
mg_asprintf(
&extra_headers, sizeof(ehb),
"Connection: close\r\n"
"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);
mgos_get_mgr(), fw_download_handler, ctx, opts, url, extra_headers, NULL);
if (extra_headers != ehb) {
free(extra_headers);
}
if (extra_headers != ehb) free(extra_headers);
if (c == NULL) {
LOG(LL_ERROR, ("Failed to connect to %s", url));
ctx->result = -10;
ctx->result = -11;
ctx->need_reboot = false;
ctx->status_msg = "Failed to connect";
ctx->status_msg = "Failed to connect";
updater_finish(ctx);
updater_context_free(ctx);
return;
}
@ -178,32 +219,42 @@ void mgos_ota_http_start(struct update_context *ctx, const char *url) {
}
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;
}
if (mgos_sys_config_get_update_url() == NULL) return;
struct update_context *ctx = updater_context_create(0);
if (ctx == NULL) return;
ctx->ignore_same_version = true;
ctx->fctx.commit_timeout = mcu->commit_timeout;
mgos_ota_http_start(ctx, mcu->url);
ctx->fctx.commit_timeout = mgos_sys_config_get_update_commit_timeout();
mgos_ota_http_start(ctx, mgos_sys_config_get_update_url());
(void)arg;
(void) arg;
}
static void ota_request_cb(int ev, void *ev_data, void *userdata) {
struct ota_request_param *p = (struct ota_request_param *) ev_data;
mgos_ota_http_start(p->updater_context, p->location);
(void) ev;
(void) userdata;
}
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);
const char *url = mgos_sys_config_get_update_url();
int interval = mgos_sys_config_get_update_interval();
if (url != NULL && interval > 0) {
LOG(LL_INFO, ("Updates from %s, every %d seconds", url, interval));
mgos_set_timer(interval * 1000, MGOS_TIMER_REPEAT, mgos_ota_timer_cb,
(void *) url);
}
const char *pref = mgos_sys_config_get_sys_pref_ota_lib();
const char *me = "ota-http-client";
bool ota_handler_set = false;
if ((pref == NULL || strcmp(pref, me) == 0) &&
mgos_event_register_base(MGOS_EVENT_OTA_REQUEST, me)) {
mgos_event_add_handler(MGOS_EVENT_OTA_REQUEST, ota_request_cb, NULL);
ota_handler_set = true;
}
LOG(LL_INFO, ("Init done, ota_lib=%s, ota handler %d",
pref == NULL ? "(null)" : pref, ota_handler_set));
(void) ota_handler_set;
return true;
}
#endif /* MGOS_ENABLE_UPDATER */