Device management with online dashboard See https://freshen.cc for full description

Files at this revision

API Documentation at this revision

Comitter:
lsm
Date:
Tue Apr 10 06:50:09 2018 +0000
Child:
1:dd0e3cc2211a
Commit message:
Version 1.3

Changed in this revision

README.md Show annotated file Show diff for this revision Revisions of this file
freshen.h Show annotated file Show diff for this revision Revisions of this file
mbed_lib.json Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md	Tue Apr 10 06:50:09 2018 +0000
@@ -0,0 +1,36 @@
+# Freshen - device management framework
+
+See https://freshen.cc for the full description and documentation
+
+## Example mbedOS app
+
+```c
+
+#include "mbed.h"
+#include "EthernetInterface.h"
+
+#define FRESHEN_ENABLE_DASH
+#include "freshen.h"
+static struct freshen_ctx ctx;
+
+EthernetInterface net;
+DigitalOut myled(LED1);
+
+int main() {
+    net.connect();
+    freshen_net = &net;
+
+    while(1) {
+        const char *ip = net.get_ip_address();
+        printf("IP address is: %s\n", ip ? ip : "No IP");
+
+        myled = 1;
+        wait(0.5);
+        myled = 0;
+        wait(0.5);
+
+        freshen_poll_dash(&ctx, "1.2", "ACCESS_TOKEN");
+    }
+}
+
+```
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/freshen.h	Tue Apr 10 06:50:09 2018 +0000
@@ -0,0 +1,1636 @@
+/*
+ * Copyright (c) 2014-2018 Cesanta Software Limited
+ * All rights reserved
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#define FRESHEN_VERSION "1.3"
+
+#ifndef FRESHEN_H
+#define FRESHEN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <stddef.h>
+
+#define FRESHEN_API extern
+
+/* Common JSON-RPC error codes */
+#define FRESHEN_ERROR_INVALID -32700   /* Invalid JSON was received */
+#define FRESHEN_ERROR_NOT_FOUND -32601 /* The method does not exist */
+#define FRESHEN_ERROR_INTERNAL -32603  /* Internal JSON-RPC error */
+
+#if __STDC_VERSION__ >= 201112L
+#include <stdatomic.h>
+typedef atomic_flag freshen_spinlock_t;
+#define FRESHEN_SPINLOCK_INIT ATOMIC_FLAG_INIT
+static void freshen_spinlock_lock(freshen_spinlock_t *lock) {
+  while (atomic_flag_test_and_set(lock))
+    ;
+}
+static void freshen_spinlock_unlock(freshen_spinlock_t *lock) {
+  atomic_flag_clear(lock);
+}
+#else
+typedef int freshen_spinlock_t;
+#define FRESHEN_SPINLOCK_INIT 0
+static void freshen_spinlock_lock(freshen_spinlock_t *lock) {
+}
+static void freshen_spinlock_unlock(freshen_spinlock_t *lock) {
+}
+#endif /* __STDC_VERSION__ */
+
+/* Append-only buffer of limited capacity */
+struct freshen_buf {
+  void *ptr;
+  size_t len;
+  size_t cap;
+  int overflow;
+};
+
+struct freshen_queue {
+  void *buf;
+  size_t buf_sz;
+  size_t tail;
+  freshen_spinlock_t lock;
+};
+
+struct freshen_method {
+  const char *method;
+  size_t method_sz;
+  int (*func)(const char *, char *, size_t, void *);
+  void *userdata;
+  struct freshen_method *next;
+};
+
+/*
+ * Main Freshen context, stores current request information and a list of
+ * exported RPC methods.
+ */
+struct freshen_ctx {
+  struct freshen_queue queue;
+  struct freshen_method *methods;
+  void *privdata;
+};
+
+#define FRESHEN_CTX_INTIALIZER \
+  { {NULL, 0, 0, FRESHEN_SPINLOCK_INIT}, NULL, NULL }
+
+FRESHEN_API struct freshen_ctx freshen_default_context;
+
+/* Registers function fn under the given name within the given RPC context */
+#define freshen_core_export(freshen, name, fn, ud)                          \
+  do {                                                                      \
+    static struct freshen_method m = {(name), sizeof(name) - 1, (fn), NULL, \
+                                      NULL};                                \
+    m.userdata = (ud);                                                      \
+    m.next = (freshen)->methods;                                            \
+    (freshen)->methods = &m;                                                \
+  } while (0)
+
+FRESHEN_API int freshen_core_publish(struct freshen_ctx *freshen,
+                                     const char *event, const char *data);
+
+FRESHEN_API int freshen_core_process(struct freshen_ctx *freshen, void *input,
+                                     size_t input_sz, void *output,
+                                     size_t output_sz);
+
+#if !defined(FRESHEN_DECLARATIONS_ONLY)
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(ESP_PLATFORM)
+#include "esp_log.h"
+#define FLOGI(...) ESP_LOGI("freshen", __VA_ARGS__)
+#else
+#define FLOGI(...)       \
+  do {                   \
+    printf(__VA_ARGS__); \
+    putchar('\n');       \
+  } while (0)
+#endif
+#define FLOGE FLOGI
+
+static int freshen_json(const char *s, size_t sz, const char *key, size_t keysz,
+                        const char **value, size_t *valuesz) {
+  enum {
+    JSON_STATE_VALUE,
+    JSON_STATE_LITERAL,
+    JSON_STATE_STRING,
+    JSON_STATE_ESCAPE,
+    JSON_STATE_UTF8
+  } state = JSON_STATE_VALUE;
+  const char *k = NULL;
+  int index = 1;
+  int depth = 0;
+  int utf8_bytes = 0;
+
+  if (key == NULL) {
+    index = keysz;
+    keysz = 0;
+  }
+
+  *value = NULL;
+  *valuesz = 0;
+
+  for (; sz > 0; s++, sz--) {
+    enum {
+      JSON_ACTION_NONE,
+      JSON_ACTION_START,
+      JSON_ACTION_END,
+      JSON_ACTION_START_STRUCT,
+      JSON_ACTION_END_STRUCT
+    } action = JSON_ACTION_NONE;
+    unsigned char c = *s;
+    switch (state) {
+      case JSON_STATE_VALUE:
+        if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+            c == ':') {
+          continue;
+        } else if (c == '"') {
+          action = JSON_ACTION_START;
+          state = JSON_STATE_STRING;
+        } else if (c == '{' || c == '[') {
+          action = JSON_ACTION_START_STRUCT;
+        } else if (c == '}' || c == ']') {
+          action = JSON_ACTION_END_STRUCT;
+        } else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
+                   (c >= '0' && c <= '9')) {
+          action = JSON_ACTION_START;
+          state = JSON_STATE_LITERAL;
+        } else {
+          return -1;
+        }
+        break;
+      case JSON_STATE_LITERAL:
+        if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+            c == ']' || c == '}' || c == ':') {
+          state = JSON_STATE_VALUE;
+          s--;
+          sz++;
+          action = JSON_ACTION_END;
+        } else if (c < 32 || c > 126) {
+          return -1;
+        }
+      case JSON_STATE_STRING:
+        if (c < 32 || (c > 126 && c < 192)) {
+          return -1;
+        } else if (c == '"') {
+          action = JSON_ACTION_END;
+          state = JSON_STATE_VALUE;
+        } else if (c == '\\') {
+          state = JSON_STATE_ESCAPE;
+        } else if (c >= 192 && c < 224) {
+          utf8_bytes = 1;
+          state = JSON_STATE_UTF8;
+        } else if (c >= 224 && c < 240) {
+          utf8_bytes = 2;
+          state = JSON_STATE_UTF8;
+        } else if (c >= 240 && c < 247) {
+          utf8_bytes = 3;
+          state = JSON_STATE_UTF8;
+        } else if (c >= 128 && c < 192) {
+          return -1;
+        }
+        break;
+      case JSON_STATE_ESCAPE:
+        if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
+            c == 'n' || c == 'r' || c == 't' || c == 'u') {
+          state = JSON_STATE_STRING;
+        } else {
+          return -1;
+        }
+        break;
+      case JSON_STATE_UTF8:
+        if (c < 128 || c > 191) {
+          return -1;
+        }
+        utf8_bytes--;
+        if (utf8_bytes == 0) {
+          state = JSON_STATE_STRING;
+        }
+        break;
+      default:
+        return -1;
+    }
+
+    if (action == JSON_ACTION_END_STRUCT) {
+      depth--;
+    }
+
+    if (depth == 1) {
+      if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
+        if (index == 0) {
+          *value = s;
+        } else if (keysz > 0 && index == 1) {
+          k = s;
+        } else {
+          index--;
+        }
+      } else if (action == JSON_ACTION_END ||
+                 action == JSON_ACTION_END_STRUCT) {
+        if (*value != NULL && index == 0) {
+          *valuesz = (size_t)(s + 1 - *value);
+          return 0;
+        } else if (keysz > 0 && k != NULL) {
+          if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
+            index = 0;
+          } else {
+            index = 2;
+          }
+          k = NULL;
+        }
+      }
+    }
+
+    if (action == JSON_ACTION_START_STRUCT) {
+      depth++;
+    }
+  }
+  return -1;
+}
+
+static int freshen_json_unescape(const char *s, size_t n, char *out) {
+  int r = 0;
+  if (*s++ != '"') {
+    return FRESHEN_ERROR_INVALID;
+  }
+  while (n > 2) {
+    char c = *s;
+    if (c == '\\') {
+      s++;
+      n--;
+      switch (*s) {
+        case 'b':
+          c = '\b';
+          break;
+        case 'f':
+          c = '\f';
+          break;
+        case 'n':
+          c = '\n';
+          break;
+        case 'r':
+          c = '\r';
+          break;
+        case 't':
+          c = '\t';
+          break;
+        case '\\':
+          c = '\\';
+          break;
+        case '/':
+          c = '/';
+          break;
+        case '\"':
+          c = '\"';
+          break;
+        default: /* XXX: we don't support unicode \uXXXX yet */
+          return FRESHEN_ERROR_INVALID;
+      }
+    }
+    if (out != NULL) {
+      *out++ = c;
+    }
+    s++;
+    n--;
+    r++;
+  }
+  if (*s != '"') {
+    return FRESHEN_ERROR_INVALID;
+  }
+  if (out != NULL) {
+    *out = '\0';
+  }
+  return r;
+}
+
+static int freshen_json_escape(const char *in, size_t in_sz, char *out,
+                               size_t out_sz) {
+  size_t n = 0;
+  for (; in != NULL && *in && in_sz > 0; in++, in_sz--) {
+    const char *chr;
+    size_t chr_sz;
+    switch (*in) {
+      case '\\':
+        chr = "\\\\";
+        chr_sz = 2;
+        break;
+      case '"':
+        chr = "\\\"";
+        chr_sz = 2;
+        break;
+      case '/':
+        chr = "\\/";
+        chr_sz = 2;
+        break;
+      case '\b':
+        chr = "\\b";
+        chr_sz = 2;
+        break;
+      case '\f':
+        chr = "\\f";
+        chr_sz = 2;
+        break;
+      case '\n':
+        chr = "\\n";
+        chr_sz = 2;
+        break;
+      case '\r':
+        chr = "\\r";
+        chr_sz = 2;
+        break;
+      case '\t':
+        chr = "\\t";
+        chr_sz = 2;
+        break;
+      default:
+        chr = in;
+        chr_sz = 1;
+        break;
+    }
+    if (out != NULL && out_sz > chr_sz) {
+      memmove(out + n, chr, chr_sz);
+      out = out + chr_sz;
+      out_sz = out_sz - chr_sz;
+    }
+    n = n + chr_sz;
+  }
+  if (out != NULL && out_sz > 0) {
+    *out = '\0';
+  }
+  n = n + 1;
+  return n;
+}
+static void freshen_buf_init(struct freshen_buf *buf, void *data,
+                             size_t data_sz) {
+  buf->ptr = data;
+  buf->len = 0;
+  buf->cap = data_sz;
+  buf->overflow = 0;
+}
+
+static void freshen_buf_append(struct freshen_buf *buf, const void *data,
+                               size_t data_sz) {
+  if (buf->overflow) {
+    return;
+  }
+  if (buf->cap - buf->len < data_sz) {
+    data_sz = buf->cap - buf->len;
+    buf->overflow = 1;
+  }
+  memmove((char *) buf->ptr + buf->len, data, data_sz);
+  buf->len = buf->len + data_sz;
+}
+
+static void freshen_buf_append_int(struct freshen_buf *buf, int n) {
+  if (n < 0) {
+    freshen_buf_append(buf, "-", 1);
+    freshen_buf_append_int(buf, -n);
+    return;
+  } else if (n > 10) {
+    freshen_buf_append_int(buf, n / 10);
+  }
+  freshen_buf_append(buf, &("0123456789"[n % 10]), 1);
+}
+
+static void freshen_buf_append_json_string(struct freshen_buf *buf,
+                                           const char *s) {
+  for (; s != NULL && *s; s++) {
+    char out[7];
+    int n = freshen_json_escape(s, 1, out, sizeof(out));
+    freshen_buf_append(buf, out, n - 1);
+  }
+}
+FRESHEN_API int freshen_core_notify(struct freshen_ctx *freshen,
+                                    const char *method, const char *arg) {
+  if (method == NULL || strlen(method) == 0) {
+    return FRESHEN_ERROR_INVALID;
+  }
+  if (arg == NULL) {
+    arg = "";
+  }
+  int method_sz = strlen(method) + 1;
+  int arg_sz = strlen(arg) + 1;
+  freshen_spinlock_lock(&freshen->queue.lock);
+  if (freshen->queue.tail + method_sz + arg_sz >= freshen->queue.buf_sz) {
+    freshen_spinlock_unlock(&freshen->queue.lock);
+    return FRESHEN_ERROR_INTERNAL;
+  }
+  memmove((char *) freshen->queue.buf + freshen->queue.tail, method, method_sz);
+  memmove((char *) freshen->queue.buf + freshen->queue.tail + method_sz, arg,
+          arg_sz);
+  freshen->queue.tail = freshen->queue.tail + method_sz + arg_sz;
+  freshen_spinlock_unlock(&freshen->queue.lock);
+  return 0;
+}
+
+static int freshen_jsonrpc_notify(struct freshen_ctx *freshen, void *res,
+                                  size_t res_sz) {
+  struct freshen_buf buf;
+  freshen_buf_init(&buf, res, res_sz);
+  freshen_spinlock_lock(&freshen->queue.lock);
+  char *method = (char *) freshen->queue.buf;
+  int method_sz = strlen(method) + 1;
+  if (method_sz == 1) {
+    freshen_spinlock_unlock(&freshen->queue.lock);
+    return 0;
+  }
+  char *arg = (char *) freshen->queue.buf + method_sz;
+  int arg_sz = strlen(arg) + 1;
+
+  freshen_buf_append(&buf, "{\"method\":\"", 11);
+  freshen_buf_append_json_string(&buf, method);
+  freshen_buf_append(&buf, "\",\"params\":[\"", 13);
+  freshen_buf_append_json_string(&buf, arg);
+  freshen_buf_append(&buf, "\"]}", 4);
+
+  memmove(freshen->queue.buf, (char *) freshen->queue.buf + method_sz + arg_sz,
+          freshen->queue.buf_sz - method_sz - arg_sz);
+  memset(
+      (char *) freshen->queue.buf + freshen->queue.buf_sz - method_sz - arg_sz,
+      0, method_sz + arg_sz);
+  freshen->queue.tail = freshen->queue.tail - method_sz - arg_sz;
+  freshen_spinlock_unlock(&freshen->queue.lock);
+
+  if (buf.overflow) {
+    return FRESHEN_ERROR_INTERNAL;
+  }
+  return buf.len;
+}
+
+static int freshen_jsonrpc_result(struct freshen_buf *buf, const char *id,
+                                  size_t id_sz, int code, const char *res) {
+  freshen_buf_append(buf, "{\"id\":", 6);
+  freshen_buf_append(buf, id, id_sz);
+  if (code == 0) {
+    freshen_buf_append(buf, ",\"result\":\"", 11);
+  } else {
+    freshen_buf_append(buf, ",\"error\":{\"code\":", 17);
+    freshen_buf_append_int(buf, code);
+    freshen_buf_append(buf, ",\"message\":\"", 12);
+  }
+  freshen_buf_append_json_string(buf, res);
+  if (code == 0) {
+    freshen_buf_append(buf, "\"}", 2);
+  } else {
+    freshen_buf_append(buf, "\"}}", 3);
+  }
+  freshen_buf_append(buf, "", 1);
+  if (buf->overflow) {
+    return FRESHEN_ERROR_INTERNAL;
+  }
+  return buf->len;
+}
+
+static int freshen_jsonrpc_call(struct freshen_ctx *freshen, char *req,
+                                size_t req_sz, char *res, size_t res_sz) {
+  int n;
+  const char *id = NULL, *method = NULL, *params = NULL, *arg = NULL;
+  size_t id_sz = 0, method_sz = 0, params_sz = 0, arg_sz = 0;
+  struct freshen_method *m;
+
+  /* ID must exist */
+  if (freshen_json(req, req_sz, "id", 2, &id, &id_sz) < 0) {
+    return FRESHEN_ERROR_INVALID;
+  }
+
+  /* Method must exist and must be a string*/
+  if (freshen_json(req, req_sz, "method", 6, &method, &method_sz) < 0) {
+    return FRESHEN_ERROR_INVALID;
+  }
+  if (method_sz < 3 || method[0] != '\"' || method[method_sz - 1] != '\"') {
+    return FRESHEN_ERROR_INVALID;
+  }
+
+  /* Params must exist and must be an array */
+  if (freshen_json(req, req_sz, "params", 6, &params, &params_sz) < 0) {
+    return FRESHEN_ERROR_INVALID;
+  }
+  if (params_sz < 2 || params[0] != '[' || params[params_sz - 1] != ']') {
+    return FRESHEN_ERROR_INVALID;
+  }
+
+  /* Extract first param value and unescape it as a string */
+  if (freshen_json(params, params_sz, NULL, 0, &arg, &arg_sz) < 0) {
+    return FRESHEN_ERROR_INVALID;
+  }
+  if ((n = freshen_json_unescape(arg, arg_sz, req)) < 0) {
+    return n;
+  }
+
+  /* number of bytes used for success result prologue */
+  const size_t prologue_sz = 19;
+  struct freshen_buf buf;
+  freshen_buf_init(&buf, res, res_sz);
+  for (m = freshen->methods; m != NULL; m = m->next) {
+    if (m->method_sz == method_sz - 2 &&
+        memcmp(m->method, method + 1, method_sz - 2) == 0) {
+      int code = m->func(req, res, res_sz - id_sz - prologue_sz, m->userdata);
+      /*
+       * move result string to the end of the buffer, then escape it in-place
+       * when preparing the result JSON. If overflow happens - return an error
+       */
+      size_t n = strlen(res) + 1;
+      char *p = res + res_sz - n;
+      memmove(p, res, n);
+      int r = freshen_jsonrpc_result(&buf, id, id_sz, code, p);
+      if (buf.overflow) {
+        freshen_buf_init(&buf, res, res_sz);
+        return freshen_jsonrpc_result(&buf, id, id_sz, FRESHEN_ERROR_INTERNAL,
+                                      "result is too long");
+      }
+      return r;
+    }
+  }
+  return freshen_jsonrpc_result(&buf, id, id_sz, FRESHEN_ERROR_NOT_FOUND,
+                                "method not found");
+}
+
+FRESHEN_API int freshen_core_process(struct freshen_ctx *freshen, void *req,
+                                     size_t req_sz, void *res, size_t res_sz) {
+  int r = freshen_jsonrpc_notify(freshen, res, res_sz);
+
+  if (r == 0 && req_sz > 0) {
+    r = freshen_jsonrpc_call(freshen, (char *) req, req_sz, (char *) res,
+                             res_sz);
+  }
+  return r;
+}
+#include <stdio.h>
+#include <stdlib.h>
+/*
+ * Common OTA code
+ */
+static int b64rev(int c) {
+  if (c >= 'A' && c <= 'Z') {
+    return c - 'A';
+  } else if (c >= 'a' && c <= 'z') {
+    return c + 26 - 'a';
+  } else if (c >= '0' && c <= '9') {
+    return c + 52 - '0';
+  } else if (c == '+') {
+    return 62;
+  } else if (c == '/') {
+    return 63;
+  } else {
+    return 64;
+  }
+}
+
+static int b64dec(const char *src, int n, char *dst) {
+  const char *end = src + n;
+  int len = 0;
+  while (src + 3 < end) {
+    int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]),
+        d = b64rev(src[3]);
+    dst[len++] = (a << 2) | (b >> 4);
+    if (src[2] != '=') {
+      dst[len++] = (b << 4) | (c >> 2);
+      if (src[3] != '=') {
+        dst[len++] = (c << 6) | d;
+      }
+    }
+    src += 4;
+  }
+  return len;
+}
+
+static int freshen_ota_init(struct freshen_ctx *ctx);
+static int freshen_ota_enabled(struct freshen_ctx *ctx);
+static int freshen_ota_begin(struct freshen_ctx *ctx);
+static int freshen_ota_end(struct freshen_ctx *ctx, int success);
+static int freshen_ota_write(struct freshen_ctx *ctx, void *buf, size_t bufsz);
+static int freshen_ota_commit(struct freshen_ctx *ctx);
+
+static int freshen_rpc_ota_begin(const char *args, char *buf, size_t len,
+                                 void *userdata) {
+  struct freshen_ctx *ctx = (struct freshen_ctx *) userdata;
+  int r = freshen_ota_begin(ctx);
+  snprintf(buf, len, "%s", (r == 0 ? "true" : "false"));
+  return 0;
+  (void) args;
+}
+
+static int freshen_rpc_ota_end(const char *args, char *buf, size_t len,
+                               void *userdata) {
+  struct freshen_ctx *ctx = (struct freshen_ctx *) userdata;
+  int success = 0;
+  if (sscanf(args, "%d", &success) != 1) {
+    snprintf(buf, len, "bad args");
+    return 400;
+  } else if (freshen_ota_end(ctx, success) != 0) {
+    snprintf(buf, len, "failed");
+    return 500;
+  } else {
+    snprintf(buf, len, "true");
+    return 0;
+  }
+}
+
+static int freshen_rpc_ota_write(const char *args, char *buf, size_t len,
+                                 void *userdata) {
+  struct freshen_ctx *ctx = (struct freshen_ctx *) userdata;
+  char *dec = (char *) args;  // Decode in-place
+  int dec_len = b64dec(args, strlen(args), dec);
+  dec[len] = '\0';
+  if (freshen_ota_write(ctx, (void *) dec, dec_len) != 0) {
+    snprintf(buf, len, "cannot write");
+    return 500;
+  } else {
+    snprintf(buf, len, "true");
+    return 0;
+  }
+}
+
+#ifdef ESP_PLATFORM
+
+#include <esp_log.h>
+#include <esp_ota_ops.h>
+#include <esp_system.h>
+#include <nvs_flash.h>
+#include <unistd.h>
+
+#define FRESHEN_TAG "freshen"
+#define FRESHEN_NVS_NAMESPACE "freshen"
+#define ROLLBACK_KV_KEY "__rollback"
+
+static struct {
+  int can_rollback;
+  const esp_partition_t *update_partition;
+  esp_ota_handle_t update_handle;
+} s_ota;
+
+static int freshen_kv_set(const char *key, const char *value) {
+  nvs_handle nvs_handle;
+  esp_err_t err;
+  err = nvs_open(FRESHEN_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_open: err=%d", err);
+    return -1;
+  }
+  err = nvs_set_str(nvs_handle, key, value == NULL ? "" : value);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_set_str: err=%d", err);
+    return -1;
+  }
+  err = nvs_commit(nvs_handle);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_commit: err=%d", err);
+    return -1;
+  }
+  nvs_close(nvs_handle);
+  return 0;
+}
+
+static const char *freshen_kv_get(const char *key) {
+  nvs_handle nvs_handle = 0;
+  size_t size;
+  char *value = NULL;
+  esp_err_t err;
+
+  if ((err = nvs_open(FRESHEN_NVS_NAMESPACE, NVS_READONLY, &nvs_handle)) !=
+      ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_open: err=%d", err);
+  } else if ((err = nvs_get_str(nvs_handle, key, NULL, &size)) != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_get_str: err=%d", err);
+  } else if ((value = (char *) calloc(1, size)) == NULL) {
+    ESP_LOGE(FRESHEN_TAG, "OOM: value is %zu", size);
+  } else if ((err = nvs_get_str(nvs_handle, key, value, &size)) != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "nvs_get_str: err=%d", err);
+  }
+  nvs_close(nvs_handle);
+
+  return value;
+}
+
+static int freshen_ota_init(struct freshen_ctx *ctx) {
+  const char *label = freshen_kv_get(ROLLBACK_KV_KEY);
+  if (label == NULL) {
+    ESP_LOGI(FRESHEN_TAG,
+             "no rollback partition is given, booting up normally");
+    return 0;
+  }
+  const esp_partition_t *p = esp_partition_find_first(
+      ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, label);
+  if (p == NULL) {
+    ESP_LOGI(FRESHEN_TAG, "can't find partition %s for rollback", label);
+    return 0;
+  }
+  ESP_LOGI(FRESHEN_TAG, "use partition %s for rollback", label);
+  freshen_kv_set(ROLLBACK_KV_KEY, NULL);
+  esp_ota_set_boot_partition(p);
+  s_ota.can_rollback = 1;
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_enabled(struct freshen_ctx *ctx) {
+  return esp_ota_get_next_update_partition(NULL) == NULL ? 0 : 1;
+  (void) ctx;
+}
+
+static int freshen_ota_begin(struct freshen_ctx *ctx) {
+  if (s_ota.update_partition != NULL) {
+    ESP_LOGE(FRESHEN_TAG, "another OTA is already in-progress");
+    return -1;
+  }
+  s_ota.update_partition = esp_ota_get_next_update_partition(NULL);
+  ESP_LOGI(FRESHEN_TAG, "Starting OTA. update_partition=%p",
+           s_ota.update_partition);
+  esp_err_t err = esp_ota_begin(s_ota.update_partition, OTA_SIZE_UNKNOWN,
+                                &s_ota.update_handle);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "esp_ota_begin failed, err=%#x", err);
+    return -1;
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_write(struct freshen_ctx *ctx, void *buf, size_t bufsz) {
+  esp_err_t err = esp_ota_write(s_ota.update_handle, buf, bufsz);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "esp_ota_write failed, err=%#x", err);
+    return -1;
+  }
+  ESP_LOGD(FRESHEN_TAG, "written %d bytes", bufsz);
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_end(struct freshen_ctx *ctx, int success) {
+  const esp_partition_t *p = s_ota.update_partition;
+  esp_err_t err = esp_ota_end(s_ota.update_handle);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "err=0x%x", err);
+    return -1;
+  }
+  s_ota.update_partition = NULL;
+  if (success) {
+    const esp_partition_t *rollback = esp_ota_get_running_partition();
+    ESP_LOGI(FRESHEN_TAG, "use partition %s for the next boot",
+             rollback->label);
+    if (freshen_kv_set(ROLLBACK_KV_KEY, rollback->label) < 0) return -1;
+    esp_err_t err = esp_ota_set_boot_partition(p);
+    if (err != ESP_OK) {
+      ESP_LOGE(FRESHEN_TAG, "esp_ota_set_boot_partition: err=0x%d", err);
+      return -1;
+    }
+    ESP_LOGI(FRESHEN_TAG, "restarting...");
+    sleep(2);
+    esp_restart();
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_commit(struct freshen_ctx *ctx) {
+  if (!s_ota.can_rollback) return 0;
+  const esp_partition_t *p = esp_ota_get_running_partition();
+  esp_err_t err = esp_ota_set_boot_partition(p);
+  if (err != ESP_OK) {
+    ESP_LOGE(FRESHEN_TAG, "esp_ota_set_boot_partition: err=0x%d", err);
+    return -1;
+  }
+  s_ota.can_rollback = 0;
+  return 0;
+  (void) ctx;
+}
+
+#elif (defined(__linux__) || defined(__APPLE__))
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#define FRESHEN_OTA_FILE "./tmp"
+
+#ifdef __APPLE__
+/* https://groups.google.com/forum/#!topic/comp.unix.programmer/JlTIncgY-vg */
+int sigtimedwait(const sigset_t *set, siginfo_t *info,
+                 const struct timespec *timeout) {
+  struct timespec elapsed = {0, 0}, rem;
+  sigset_t pending;
+  int signo;
+  long ns;
+
+  do {
+    sigpending(&pending); /* doesn't clear pending queue */
+    for (signo = 1; signo < NSIG; signo++) {
+      if (sigismember(set, signo) && sigismember(&pending, signo)) {
+        if (info) {
+          memset(info, 0, sizeof *info);
+          info->si_signo = signo;
+        }
+
+        return signo;
+      }
+    }
+    ns = 200000000L; /* 2/10th second */
+    nanosleep(&(struct timespec){0, ns}, &rem);
+    ns -= rem.tv_nsec;
+    elapsed.tv_sec += (elapsed.tv_nsec + ns) / 1000000000L;
+    elapsed.tv_nsec = (elapsed.tv_nsec + ns) % 1000000000L;
+  } while (elapsed.tv_sec < timeout->tv_sec ||
+           (elapsed.tv_sec == timeout->tv_sec &&
+            elapsed.tv_nsec < timeout->tv_nsec));
+  errno = EAGAIN;
+  return -1;
+}
+#endif
+
+static struct {
+  int can_rollback;
+  FILE *ota_file;
+} s_ota = {0, NULL};
+
+static const char *freshen_arg(int n) {
+#if defined(__linux__)
+  static char cmdline[8192];
+  FILE *f = fopen("/proc/self/cmdline", "r");
+  if (f == NULL) {
+    return NULL;
+  }
+  int sz = fread(cmdline, 1, sizeof(cmdline), f);
+  if (sz < 0) {
+    return NULL;
+  }
+  fclose(f);
+  int i = 0;
+  char *arg = &cmdline[0];
+  for (i = 0; i < n && *arg; i++) {
+    arg = arg + strlen(arg) + 1;
+  }
+  if (*arg == '\0') {
+    return NULL;
+  }
+  return arg;
+#elif defined(__APPLE__)
+  extern int *_NSGetArgc(void);
+  extern char ***_NSGetArgv(void);
+  if (n < *_NSGetArgc()) {
+    return (*_NSGetArgv())[n];
+  }
+  return NULL;
+#endif
+}
+
+static void freshen_ota_restart(void) {
+  FLOGI("called system restart..");
+  int exit_code = 0;
+  struct stat st;
+  if (strcmp(freshen_arg(0), FRESHEN_OTA_FILE) != 0 &&
+      stat(FRESHEN_OTA_FILE, &st) == 0) {
+    FLOGI("update is available");
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGCHLD);
+    sigaddset(&mask, SIGUSR1);
+    if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
+      FLOGE("sigprocmask(): errno=%d", errno);
+      goto error;
+    }
+    int pid = fork();
+    FLOGI("pid=%d, ppid=%d", pid, getppid());
+    if (pid < 0) {
+      FLOGE("fork(): errno=%d", errno);
+      goto error;
+    }
+    if (pid == 0) {
+      int argc = 0;
+      for (; freshen_arg(argc) != NULL; argc++)
+        ;
+      char **argv = (char **) calloc(argc + 1, sizeof(char *));
+      if (argv == NULL) {
+        exit(EXIT_FAILURE);
+      }
+      for (int i = 0; i < argc + 1; i++) {
+        argv[i] = (char *) freshen_arg(i);
+      }
+      argv[0] = FRESHEN_OTA_FILE;
+      chmod(FRESHEN_OTA_FILE, 0755);
+      execv(FRESHEN_OTA_FILE, argv);
+      exit(EXIT_FAILURE);
+    }
+
+    siginfo_t sig;
+    int status;
+    struct timespec t;
+    t.tv_sec = 10;
+    t.tv_nsec = 0;
+    for (;;) {
+      if (sigtimedwait(&mask, &sig, &t) < 0) {
+        if (errno == EINTR) {
+          FLOGI("interruped by another signal, continue");
+          continue;
+        } else if (errno == EAGAIN) {
+          FLOGE("timeout, killing child process");
+          kill(pid, SIGKILL);
+        } else {
+          FLOGE("sigtimedwait() failed: errno=%d", errno);
+        }
+        break;
+      }
+      if (sig.si_signo == SIGUSR1) {
+        FLOGI("child process succeeded!");
+        rename(FRESHEN_OTA_FILE, freshen_arg(0));
+      } else {
+        FLOGI("child process exited!");
+      }
+      break;
+    }
+    if (waitpid(pid, &status, 0) < 0) {
+      FLOGE("waitpid(): errno=%d", errno);
+      return;
+    }
+    exit_code = (WEXITSTATUS(status));
+    FLOGI("child process exited with %d", exit_code);
+    goto error;
+  }
+  return;
+error:
+  unlink(FRESHEN_OTA_FILE);
+}
+
+static int freshen_ota_init(struct freshen_ctx *ctx) {
+  FLOGI("argv0=%s", freshen_arg(0));
+  if (strcmp(freshen_arg(0), FRESHEN_OTA_FILE) == 0) {
+    s_ota.can_rollback = 1;
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_enabled(struct freshen_ctx *ctx) {
+  return 1;
+  (void) ctx;
+}
+
+static int freshen_ota_begin(struct freshen_ctx *ctx) {
+  FLOGI("ctx: %p", (void *) ctx);
+  if (s_ota.ota_file != NULL) {
+    FLOGE("another OTA process is in-progress");
+    return -1;
+  }
+  s_ota.ota_file = fopen(FRESHEN_OTA_FILE, "wb");
+  if (s_ota.ota_file == NULL) {
+    FLOGE("failed to open temporary file");
+    return -1;
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_end(struct freshen_ctx *ctx, int success) {
+  FLOGI("ctx: %p, success=%d", (void *) ctx, success);
+  if (s_ota.ota_file == NULL) {
+    FLOGE("ota_write: OTA process is not started");
+    return -1;
+  }
+  fclose(s_ota.ota_file);
+  s_ota.ota_file = NULL;
+  if (!success) {
+    FLOGI("not succeeded, remove temporary file");
+    unlink(FRESHEN_OTA_FILE);
+  } else {
+    freshen_ota_restart();
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_write(struct freshen_ctx *ctx, void *buf, size_t bufsz) {
+  FLOGI("ctx: %p, bufsz=%zu", (void *) ctx, bufsz);
+  if (s_ota.ota_file == NULL) {
+    FLOGE("OTA process is not started");
+    return -1;
+  }
+  if (fwrite(buf, bufsz, 1, s_ota.ota_file) != 1) {
+    FLOGE("failed to write chunk");
+    return -1;
+  }
+  return 0;
+  (void) ctx;
+}
+
+static int freshen_ota_commit(struct freshen_ctx *ctx) {
+  if (!s_ota.can_rollback) {
+    return 0;
+  }
+  FLOGI("ctx: %p, argv0=%s", (void *) ctx, freshen_arg(0));
+  s_ota.can_rollback = 0;
+  return kill(getppid(), SIGUSR1);
+  (void) ctx;
+}
+#else
+
+static int freshen_ota_enabled(struct freshen_ctx *ctx) {
+  (void) ctx;
+  return 0;
+}
+
+static int freshen_ota_init(struct freshen_ctx *ctx) {
+  (void) ctx;
+  return 0;
+}
+
+static int freshen_ota_commit(struct freshen_ctx *ctx) {
+  (void) ctx;
+  return 0;
+}
+
+#endif
+
+#if defined(FRESHEN_ENABLE_DASH)
+#define FRESHEN_ENABLE_MBEDTLS
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#if defined(__unix__) || defined(__APPLE__)
+#define FRESHEN_ENABLE_SOCKET
+#include <signal.h>
+#include <unistd.h>
+#endif
+
+#if defined(FRESHEN_ENABLE_SOCKET)
+#include <netdb.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#endif
+
+#if defined(MBED_LIBRARY_VERSION)
+#include "mbed.h"
+#endif
+
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/entropy.h"
+#include "mbedtls/net.h"
+#include "mbedtls/ssl.h"
+#endif
+
+struct conn {
+  int sock;
+  bool is_tls;
+  bool is_ws;
+
+#if defined(MBED_LIBRARY_VERSION)
+  TCPSocket *mbedsock;
+#endif
+
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+  mbedtls_net_context server_fd;
+  mbedtls_entropy_context entropy;
+  mbedtls_ctr_drbg_context drbg;
+  mbedtls_ssl_context ssl;
+  mbedtls_ssl_config conf;
+  mbedtls_x509_crt cacert;
+#endif
+};
+
+static void freshen_net_init_conn(struct conn *c) {
+  memset(c, 0, sizeof(*c));
+  c->sock = -1;
+}
+
+static bool freshen_net_is_disconnected(const struct conn *c) {
+#if defined(MBED_LIBRARY_VERSION)
+  return c->mbedsock == NULL;
+#else
+  return c->sock == -1;
+#endif
+}
+
+static int freshen_net_recv(struct conn *c, void *buf, size_t len) {
+  int n = 0;
+
+#if defined(FRESHEN_ENABLE_SOCKET)
+  /* If socket has no data, return immediately */
+  struct timeval tv = {0, 0};
+  fd_set rset;
+  FD_ZERO(&rset);
+  FD_SET(c->sock, &rset);
+  if (select(c->sock + 1, &rset, NULL, NULL, &tv) != 1) return n;
+#endif
+
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+  if (c->is_tls) {
+    n = mbedtls_ssl_read(&c->ssl, (unsigned char *) buf, len);
+  } else
+#endif
+  {
+#if defined(FRESHEN_ENABLE_SOCKET)
+    n = recv(c->sock, buf, len, 0);
+#endif
+  }
+  return n <= 0 ? -1 : n;
+}
+
+static int freshen_net_send(struct conn *c, const void *buf, size_t len) {
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+  if (c->is_tls) {
+    return mbedtls_ssl_write(&c->ssl, (const unsigned char *) buf, len);
+  } else
+#endif
+  {
+#if defined(FRESHEN_ENABLE_SOCKET)
+    return send(c->sock, buf, len, 0);
+#endif
+  }
+}
+
+static void freshen_net_close(struct conn *c) {
+#if defined(MBED_LIBRARY_VERSION)
+  if (c->mbedsock != NULL) c->mbedsock->close();
+  c->mbedsock = NULL;
+  FLOGI("closed mbedsock");
+#endif
+
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+  if (c->is_tls) {
+#if defined(FRESHEN_ENABLE_SOCKET)
+    mbedtls_net_free(&c->server_fd);
+#endif
+    mbedtls_ssl_free(&c->ssl);
+    mbedtls_ssl_config_free(&c->conf);
+    mbedtls_ctr_drbg_free(&c->drbg);
+    mbedtls_entropy_free(&c->entropy);
+    mbedtls_x509_crt_free(&c->cacert);
+  }
+#endif
+
+  if (c->sock != -1) close(c->sock);
+  c->sock = -1;
+  c->is_ws = false;
+}
+
+#if defined(MBED_LIBRARY_VERSION)
+NetworkInterface *freshen_net;
+
+static int tls_net_recv(void *ctx, unsigned char *buf, size_t len) {
+  TCPSocket *socket = static_cast<TCPSocket *>(ctx);
+  int n = socket->recv(buf, len);
+  return n;
+}
+
+static int tls_net_send(void *ctx, const unsigned char *buf, size_t len) {
+  TCPSocket *socket = static_cast<TCPSocket *>(ctx);
+  int n = socket->send(buf, len);
+  return n;
+}
+
+void mbedtls_net_init(mbedtls_net_context *ctx) {
+  (void) ctx;
+}
+
+void mbedtls_net_free(mbedtls_net_context *ctx) {
+  (void) ctx;
+}
+
+int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len,
+                          size_t *olen) {
+  memset(output, 0, len);
+  return 0;
+}
+#endif
+
+static bool fwsconnect(struct conn *c, const char *host, const char *port,
+                       const char *uri, const char *token) {
+  int n, sent;
+  char buf[512];
+  if ((n = snprintf(buf, sizeof(buf),
+                    "GET %s HTTP/1.1\r\n"
+                    "Host: %s:%s\r\n"
+                    "Authorization: Bearer %s\r\n"
+                    "Sec-WebSocket-Version: 13\r\n"
+                    "Sec-WebSocket-Key: p0EAAPE61hDZrLdgKgy1Og==\r\n"
+                    "Sec-WebSocket-Protocol: dash.freshen.cc\r\n"
+                    "Upgrade: websocket\r\n"
+                    "Connection: Upgrade\r\n"
+                    "Origin: http://%s\r\n"
+                    "\r\n",
+                    uri, host, port, token, host)) > (int) sizeof(buf) ||
+      n < 0) {
+    FLOGE("snprintf: %d", n);
+  } else if ((sent = freshen_net_send(c, buf, n)) != n) {
+    FLOGE("send(%d) = %d : %d", n, sent, errno);
+  } else {
+    return true;
+  }
+  freshen_net_close(c);
+  return false;
+}
+
+static bool freconnect(const char *url, const char *token, struct conn *c) {
+#if defined(FRESHEN_ENABLE_SOCKET)
+  struct sockaddr_in sin;
+  struct hostent *he;
+#endif
+  char proto[10], host[100], port[10] = "80", uri[20];
+  int sock;
+
+  if (!freshen_net_is_disconnected(c)) return true;
+
+  sock = c->sock = -1;
+  proto[0] = host[0] = port[0] = uri[0] = '\0';
+  if (sscanf(url, "%9[^:]://%99[^:]:%9[0-9]%19s", proto, host, port, uri) !=
+      4) {
+    if (strcmp(proto, "wss") == 0) strcpy(port, "443");
+    sscanf(url, "%9[^:]://%99[^/]%19s", proto, host, uri);
+  }
+  c->is_tls = strcmp(proto, "wss") == 0 ? true : false;
+  FLOGI("dst [%s][%s][%s][%s]", proto, host, port, uri);
+
+#if defined(FRESHEN_ENABLE_SOCKET)
+  memset(&sin, 0, sizeof(sin));
+  sin.sin_family = AF_INET;
+  sin.sin_port = htons((uint16_t) atoi(port));
+  if ((he = gethostbyname(host)) == NULL) {
+    FLOGE("gethostbyname(%s): %d", host, errno);
+  } else if (!memcpy(&sin.sin_addr, he->h_addr_list[0], sizeof(sin.sin_addr))) {
+  } else if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+    FLOGE("socket: %d", errno);
+  } else if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
+    FLOGE("connect: %d", errno);
+    close(sock);
+  } else if (!c->is_tls) {
+    c->sock = sock;
+    return true;
+  } else {
+#elif defined(MBED_LIBRARY_VERSION)
+  if (freshen_net == NULL) {
+    FLOGE("freshen_net pointer is not set");
+  } else if ((c->mbedsock = new TCPSocket(freshen_net)) == NULL) {
+    FLOGE("cant create tcp socket");
+  } else if (c->mbedsock->connect(host, atoi(port)) != NSAPI_ERROR_OK) {
+    FLOGE("mbed connect(%s:%d): %d", host, atoi(port), errno);
+  } else {
+#endif
+#if defined(FRESHEN_ENABLE_MBEDTLS)
+
+#if !defined(FRESHEN_CA_PEM)
+#define FRESHEN_CA_PEM                                                 \
+  "-----BEGIN CERTIFICATE-----\n"                                      \
+  "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \
+  "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
+  "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \
+  "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \
+  "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \
+  "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \
+  "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \
+  "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \
+  "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \
+  "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \
+  "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \
+  "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \
+  "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \
+  "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \
+  "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \
+  "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \
+  "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \
+  "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \
+  "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \
+  "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \
+  "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \
+  "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \
+  "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \
+  "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \
+  "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n"                                 \
+  "-----END CERTIFICATE-----"
+#endif
+
+    const char *ca_pem = FRESHEN_CA_PEM;
+    int res;
+    mbedtls_ssl_init(&c->ssl);
+    mbedtls_ssl_config_init(&c->conf);
+    mbedtls_entropy_init(&c->entropy);
+    mbedtls_ctr_drbg_init(&c->drbg);
+    mbedtls_x509_crt_init(&c->cacert);
+    mbedtls_ctr_drbg_seed(&c->drbg, mbedtls_entropy_func, &c->entropy, 0, 0);
+    mbedtls_ssl_config_defaults(&c->conf, MBEDTLS_SSL_IS_CLIENT,
+                                MBEDTLS_SSL_TRANSPORT_STREAM,
+                                MBEDTLS_SSL_PRESET_DEFAULT);
+    mbedtls_ssl_conf_rng(&c->conf, mbedtls_ctr_drbg_random, &c->drbg);
+    res = mbedtls_x509_crt_parse(&c->cacert, (const unsigned char *) ca_pem,
+                                 strlen(ca_pem) + 1); /* must contain  \0 */
+    if (res != 0) FLOGE("crt_parse: -%#x", -res);
+    mbedtls_ssl_conf_ca_chain(&c->conf, &c->cacert, NULL);
+    mbedtls_ssl_conf_authmode(&c->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
+
+    res = mbedtls_ssl_setup(&c->ssl, &c->conf);
+    if (res != 0) FLOGE("ssl_setup: -%#x", -res);
+    mbedtls_ssl_set_hostname(&c->ssl, host);
+#if defined(FRESHEN_ENABLE_SOCKET)
+    mbedtls_net_init(&c->server_fd);
+    c->server_fd.fd = c->sock = sock;
+    mbedtls_ssl_set_bio(&c->ssl, &c->server_fd, mbedtls_net_send,
+                        mbedtls_net_recv, NULL);
+#elif defined(MBED_LIBRARY_VERSION)
+    mbedtls_ssl_set_bio(&c->ssl, static_cast<void *>(c->mbedsock), tls_net_send,
+                        tls_net_recv, NULL);
+#endif
+    if ((res = mbedtls_ssl_handshake(&c->ssl)) == 0) {
+      return fwsconnect(c, host, port, uri, token);
+    }
+    FLOGE("handshake error: -%#x", -res);
+#else
+    FLOGE("TLS support is not enabled");
+#endif
+  }
+
+  freshen_net_close(c);
+  return false;
+}
+
+static void freshen_sleep(int seconds) {
+#if defined(MBED_LIBRARY_VERSION)
+  wait(seconds);
+#else
+  sleep(seconds);
+#endif
+}
+#endif
+#if defined(FRESHEN_ENABLE_DASH)
+#define FRESHEN_ENABLE_SOCKET
+#define FRESHEN_ENABLE_MBEDTLS
+#endif
+
+#if defined(FRESHEN_ENABLE_DASH)
+
+// WEBSOCKET
+#define WEBSOCKET_OP_CONTINUE 0
+#define WEBSOCKET_OP_TEXT 1
+#define WEBSOCKET_OP_BINARY 2
+#define WEBSOCKET_OP_CLOSE 8
+#define WEBSOCKET_OP_PING 9
+#define WEBSOCKET_OP_PONG 10
+
+#define FLAGS_MASK_FIN (1 << 7)
+#define FLAGS_MASK_OP 0x0f
+
+struct ws_msg {
+  int header_len;
+  int data_len;
+  int flags;
+};
+
+#ifndef IN_BUF_SIZE
+#define IN_BUF_SIZE 4096
+#endif
+
+#ifndef OUT_BUF_SIZE
+#define OUT_BUF_SIZE IN_BUF_SIZE
+#endif
+
+#ifndef QUEUE_BUF_SIZE
+#define QUEUE_BUF_SIZE OUT_BUF_SIZE
+#endif
+
+static int parse_ws_frame(char *buf, int len, struct ws_msg *msg) {
+  int i, n = 0, mask_len = 0;
+  msg->header_len = msg->data_len = 0;
+  if (len >= 2) {
+    n = buf[1] & 0x7f;
+    mask_len = buf[1] & FLAGS_MASK_FIN ? 4 : 0;
+    msg->flags = *(unsigned char *) buf;
+    if (n < 126 && len >= mask_len) {
+      msg->data_len = n;
+      msg->header_len = 2 + mask_len;
+    } else if (n == 126 && len >= 4 + mask_len) {
+      msg->header_len = 4 + mask_len;
+      msg->data_len = ntohs(*(uint16_t *) &buf[2]);
+    } else if (len >= 10 + mask_len) {
+      msg->header_len = 10 + mask_len;
+      msg->data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) +
+                      ntohl(*(uint32_t *) &buf[6]);
+    }
+  }
+  if (msg->header_len + msg->data_len > len) return 0;
+  /* Apply mask  */
+  if (mask_len > 0) {
+    for (i = 0; i < msg->data_len; i++) {
+      buf[i + msg->header_len] ^= (buf + msg->header_len - mask_len)[i % 4];
+    }
+  }
+  return msg->header_len + msg->data_len;
+}
+
+static void ws_send(struct conn *c, int op, char *buf, int len) {
+  unsigned char header[10];
+  int i, header_len = 0;
+  uint8_t mask[4] = {0x71, 0x3e, 0x5a, 0xcc}; /* it is random, i bet ya */
+  header[0] = op | FLAGS_MASK_FIN;
+
+  if (len < 126) {
+    header[1] = (unsigned char) len;
+    header_len = 2;
+  } else if (len < 65535) {
+    uint16_t tmp = htons((uint16_t) len);
+    header[1] = 126;
+    memcpy(&header[2], &tmp, sizeof(tmp));
+    header_len = 4;
+  } else {
+    uint32_t tmp;
+    header[1] = 127;
+    tmp = htonl((uint32_t)((uint64_t) len >> 32));
+    memcpy(&header[2], &tmp, sizeof(tmp));
+    tmp = htonl((uint32_t)(len & 0xffffffff));
+    memcpy(&header[6], &tmp, sizeof(tmp));
+    header_len = 10;
+  }
+  header[1] |= 1 << 7; /* set masking flag */
+  freshen_net_send(c, header, header_len);
+  freshen_net_send(c, mask, sizeof(mask));
+  for (i = 0; i < len; i++) {
+    buf[i] ^= mask[i % sizeof(mask)];
+  }
+  freshen_net_send(c, buf, len);
+}
+
+static int info(const char *args, char *buf, size_t len, void *userdata) {
+  struct freshen_ctx *ctx = (struct freshen_ctx *) userdata;
+  const char *arch =
+#if defined(__APPLE__)
+      "darwin"
+#elif defined(__linux__)
+      "linux"
+#elif defined(ESP_PLATFORM)
+      "esp32"
+#elif defined(MBED_LIBRARY_VERSION)
+      "mbedOS"
+#else
+      "posix"
+#endif
+      ;
+  int ota_enabled = freshen_ota_enabled(ctx);
+  snprintf(buf, len,
+           "{\"fw_version\": \"%s\",\"arch\": \"%s\""
+           ",\"ota_enabled\": %s,\"built\": \"%s\"}",
+           (const char *) userdata, arch, ota_enabled ? "true" : "false",
+           __DATE__ " " __TIME__);
+  return 0;
+  (void) args;
+}
+
+static int rpclist(const char *args, char *buf, size_t len, void *userdata) {
+  struct freshen_ctx *ctx = (struct freshen_ctx *) userdata;
+  struct freshen_method *m;
+  const char *comma = "";
+  buf[0] = '\0';
+  snprintf(buf + strlen(buf), len - strlen(buf), "[");
+  for (m = ctx->methods; m != NULL; m = m->next) {
+    snprintf(buf + strlen(buf), len - strlen(buf), "%s\"%s\"", comma,
+             m->method);
+    comma = ",";
+  }
+  snprintf(buf + strlen(buf), len - strlen(buf), "]");
+  return 0;
+  (void) args;
+}
+
+struct privdata {
+  struct conn conn;
+  char *queue, *in, *out;
+  int in_len;
+};
+
+static void freshen_poll(struct freshen_ctx *ctx, const char *url,
+                         const char *version, const char *token) {
+  struct privdata *pd = (struct privdata *) ctx->privdata;
+  if (pd == NULL) {
+    ctx->privdata = pd = (struct privdata *) calloc(1, sizeof(*pd));
+#if defined(__unix__)
+    signal(SIGPIPE, SIG_IGN);
+#endif
+    ctx->queue.buf = malloc(QUEUE_BUF_SIZE);
+    ctx->queue.buf_sz = QUEUE_BUF_SIZE;
+
+    freshen_net_init_conn(&pd->conn);
+
+    freshen_core_export(ctx, "info", info, (void *) version);
+    freshen_core_export(ctx, "rpc.list", rpclist, ctx);
+
+#if defined(__unix__) || defined(__APPLE__) || defined(ESP_PLATFORM)
+    freshen_core_export(ctx, "ota.begin", freshen_rpc_ota_begin, ctx);
+    freshen_core_export(ctx, "ota.write", freshen_rpc_ota_write, ctx);
+    freshen_core_export(ctx, "ota.end", freshen_rpc_ota_end, ctx);
+#endif
+
+    pd->in = (char *) malloc(IN_BUF_SIZE);
+    pd->out = (char *) malloc(OUT_BUF_SIZE);
+
+    pd->in[0] = pd->out[0] = ((char *) ctx->queue.buf)[0] = '\0';
+
+    freshen_ota_init(ctx);
+  }
+
+  if (!freconnect(url, token, &pd->conn)) {
+    freshen_sleep(1);
+    return;
+  }
+
+  if (pd->in_len >= IN_BUF_SIZE) {
+    FLOGE("input buffer overflow, closing..");
+    freshen_net_close(&pd->conn);
+    return;
+  }
+
+  int n = freshen_net_recv(&pd->conn, pd->in + pd->in_len,
+                           IN_BUF_SIZE - pd->in_len);
+  if (n < 0) {
+    FLOGE("read error, closing..");
+    freshen_net_close(&pd->conn);
+    return;
+  }
+  pd->in_len += n;
+
+  // Still in the WS handshake mode. Buffer and skip HTTP reply
+  if (!pd->conn.is_ws) {
+    for (int i = 4; i <= pd->in_len; i++) {
+      if (memcmp(pd->in + i - 4, "\r\n\r\n", 4) == 0) {
+        memmove(pd->in, pd->in + i, pd->in_len - i);
+        pd->in_len -= i;
+        pd->conn.is_ws = true;
+        break;
+      }
+    }
+    if (!pd->conn.is_ws) return;
+  }
+
+  freshen_ota_commit(ctx);
+
+  struct ws_msg msg;
+  if (parse_ws_frame(pd->in, pd->in_len, &msg)) {
+    char *data = pd->in + msg.header_len;
+    int data_len = msg.data_len;
+    while (data_len > 0 && data[data_len - 1] != '}') data_len--;
+
+    switch (msg.flags & FLAGS_MASK_OP) {
+      case WEBSOCKET_OP_PING:
+        FLOGI("WS PONG");
+        ws_send(&pd->conn, WEBSOCKET_OP_PONG, data, data_len);
+        break;
+      case WEBSOCKET_OP_CLOSE:
+        FLOGI("WS close received, closing connection");
+        freshen_net_close(&pd->conn);
+        return;
+    }
+
+    FLOGI("WS in: %d [%.*s]", data_len, data_len, data);
+    pd->out[0] = pd->out[OUT_BUF_SIZE - 1] = '\0';
+    freshen_core_process(ctx, data, data_len, pd->out, OUT_BUF_SIZE - 1);
+    int out_len = strlen(pd->out);
+    FLOGI("WS out: %d [%.*s]", out_len, out_len, pd->out);
+    if (out_len > 0) {
+      ws_send(&pd->conn, WEBSOCKET_OP_TEXT, pd->out, out_len);
+    }
+
+    int framelen = msg.header_len + msg.data_len;
+    memmove(pd->in, pd->in + framelen, pd->in_len - framelen);
+    pd->in_len -= framelen;
+  }
+}
+
+static void freshen_poll_dash(struct freshen_ctx *ctx, const char *version,
+                              const char *token) {
+  freshen_poll(ctx, "wss://dash.freshen.cc/api/v1/rpc", version, token);
+}
+#endif
+#endif /* FRESHEN_DECLARATIONS_ONLY */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* FRESHEN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed_lib.json	Tue Apr 10 06:50:09 2018 +0000
@@ -0,0 +1,8 @@
+{
+  "name": "freshen",
+  "macros": [
+    "MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES",
+    "MBEDTLS_SSL_MAX_CONTENT_LEN=4096",
+    "MBEDTLS_ENTROPY_HARDWARE_ALT"
+  ]
+}
\ No newline at end of file