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(&params, "url", p, buf_len);
+      if (len > 0) {
+        url = p;
+        p += len + 1;
+        buf_len -= len + 1;
+      }
+      len = mg_get_http_var(&params, "commit_timeout", p, buf_len);
+      if (len > 0) {
+        commit_timeout = atoi(p);
+      }
+      len = mg_get_http_var(&params, "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