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

freshen.h

Committer:
lsm
Date:
2018-04-10
Revision:
0:b21e585b3aa1

File content as of revision 0:b21e585b3aa1:

/*
 * 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 */