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