Device management with online dashboard See https://freshen.cc for full description
Revision 0:b21e585b3aa1, committed 2018-04-10
- Comitter:
- lsm
- Date:
- Tue Apr 10 06:50:09 2018 +0000
- Child:
- 1:dd0e3cc2211a
- Commit message:
- Version 1.3
Changed in this revision
--- /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, ¶ms, ¶ms_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