/* * Usage: * * new UART("platform_specific_name") * */ static enum v7_err UART_ctor(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t this_obj = v7_get_this(v7); v7_val_t dev = v7_arg(v7, 0); struct user_data *ud; void *uart; const char *name; size_t len; if (!v7_is_string(dev)) { rcode = v7_throwf(v7, "Error", "device must be string"); goto clean; } ud = (struct user_data *) calloc(1, sizeof(struct user_data)); ud->v7 = v7; ud->want = 0; ud->cb = v7_create_undefined(); v7_own(v7, &ud->cb); name = v7_get_string_data(v7, &dev, &len); uart = sj_hal_open_uart(name, (void *) ud); if (uart == NULL) { rcode = v7_throwf(v7, "Error", "cannot open uart"); goto clean; } v7_set(v7, this_obj, "_ud", ~0, V7_PROPERTY_HIDDEN, v7_create_foreign(ud)); v7_set(v7, this_obj, "_dev", ~0, V7_PROPERTY_HIDDEN, v7_create_foreign(uart)); clean: return rcode; }
/* * Publishes a message to a topic. * * Args: * - `topic`: topic, string. * - `message`: message, string. * * Only QOS 0 is implemented at the moment. */ enum v7_err MQTT_publish(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; struct user_data *ud; struct mg_connection *nc; const char *topic; const char *message; size_t message_len; v7_val_t topicv = v7_arg(v7, 0), messagev = v7_arg(v7, 1); (void) res; topic = v7_get_cstring(v7, &topicv); if (topic == NULL || strlen(topic) == 0) { rcode = v7_throwf(v7, "TypeError", "invalid topic"); goto clean; } if (!v7_is_string(messagev)) { rcode = v7_throwf(v7, "TypeError", "invalid message"); goto clean; } message = v7_get_string(v7, &messagev, &message_len); nc = v7_get_ptr(v7, v7_get(v7, v7_get_this(v7), "_nc", ~0)); if (nc == NULL) { rcode = v7_throwf(v7, "Error", "invalid connection"); goto clean; } ud = (struct user_data *) nc->user_data; mg_mqtt_publish(nc, topic, ud->msgid++, MG_MQTT_QOS(0), message, message_len); clean: return rcode; }
/* * Subscribes a mqtt client to a topic. */ enum v7_err MQTT_subscribe(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; struct user_data *ud; struct mg_connection *nc; struct mg_mqtt_topic_expression expr; v7_val_t topicv = v7_arg(v7, 0); const char *topic; nc = v7_get_ptr(v7, v7_get(v7, v7_get_this(v7), "_nc", ~0)); if (nc == NULL) { rcode = v7_throwf(v7, "Error", "unsupported protocol"); goto clean; } ud = (struct user_data *) nc->user_data; topic = v7_get_cstring(v7, &topicv); if (topic == NULL || strlen(topic) == 0) { rcode = v7_throwf(v7, "TypeError", "invalid topic"); goto clean; } expr.topic = topic; expr.qos = 0; mg_mqtt_subscribe(nc, &expr, 1, ud->msgid++); *res = v7_mk_boolean(v7, 1); clean: return rcode; }
/* JS signature: listen(addr, [options]) */ SJ_PRIVATE enum v7_err Http_Server_listen(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; char buf[50], *p = buf; const char *ca_cert = NULL, *cert = NULL; v7_val_t this_obj = v7_get_this(v7); v7_val_t arg0 = v7_arg(v7, 0); v7_val_t opts = v7_arg(v7, 1); if (!v7_is_number(arg0) && !v7_is_string(arg0)) { rcode = v7_throwf(v7, "TypeError", "Function expected"); goto clean; } if (!v7_is_undefined(opts) && !v7_is_object(opts)) { rcode = v7_throwf(v7, "TypeError", "Options must be an object"); goto clean; } if (!v7_is_undefined(opts)) { v7_val_t ca_cert_v = v7_get(v7, opts, "ssl_ca_cert", ~0); v7_val_t cert_v = v7_get(v7, opts, "ssl_cert", ~0); if (!v7_is_undefined(ca_cert_v) && !v7_is_string(ca_cert_v)) { rcode = v7_throwf(v7, "TypeError", "ca_cert must be a string"); goto clean; } if (!v7_is_undefined(cert_v) && !v7_is_string(cert_v)) { rcode = v7_throwf(v7, "TypeError", "cert must be a string"); goto clean; } if (!v7_is_undefined(ca_cert_v)) { ca_cert = v7_to_cstring(v7, &ca_cert_v); } if (!v7_is_undefined(cert_v)) { cert = v7_to_cstring(v7, &cert_v); } } p = v7_stringify(v7, arg0, buf, sizeof(buf), 0); rcode = start_http_server(v7, p, this_obj, ca_cert, cert); if (rcode != V7_OK) { goto clean; } *res = this_obj; clean: if (p != buf) { free(p); } return rcode; }
/* * `on` method on a mqtt client object registers an event handler * for a given event type. Recognized event types: * * - `connect`: invoked on sucessfull connection (mqtt connack message) * - `error`: invoked on connection error * - `close`: invoked when the connection gets closed * - `message`: invoked when a new message is received. The callback * receives (topic, message) arguments, both strings */ enum v7_err MQTT_on(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t evv = v7_arg(v7, 0); v7_val_t cb = v7_arg(v7, 1); const char *ev, *key = NULL; ev = v7_get_cstring(v7, &evv); if (strcmp(ev, "connect") == 0) { key = SJ_MQTT_CONNECT_CB; } else if (strcmp(ev, "message") == 0) { key = SJ_MQTT_MESSAGE_CB; } else if (strcmp(ev, "error") == 0) { key = SJ_MQTT_ERROR_CB; } else if (strcmp(ev, "close") == 0) { key = SJ_MQTT_CLOSE_CB; } else { rcode = v7_throwf(v7, "Error", "unsupported protocol"); goto clean; } v7_def(v7, v7_get_this(v7), key, ~0, V7_DESC_ENUMERABLE(0), cb); (void) res; clean: return rcode; }
static enum v7_err start_http_server(struct v7 *v7, const char *addr, v7_val_t obj, const char *ca_cert, const char *cert) { enum v7_err rcode = V7_OK; struct mg_connection *c; struct user_data *ud; struct mg_bind_opts opts; memset(&opts, 0, sizeof(opts)); #ifdef MG_ENABLE_SSL opts.ssl_ca_cert = ca_cert; opts.ssl_cert = cert; #else (void) ca_cert; (void) cert; #endif c = mg_bind_opt(&sj_mgr, addr, http_ev_handler, opts); if (c == NULL) { rcode = v7_throwf(v7, "Error", "Cannot bind"); goto clean; } mg_set_protocol_http_websocket(c); c->user_data = ud = (struct user_data *) malloc(sizeof(*ud)); ud->v7 = v7; ud->obj = obj; ud->handler = v7_get(v7, obj, "_cb", 3); v7_own(v7, &ud->obj); clean: return rcode; }
SJ_PRIVATE enum v7_err Http_response_writeHead(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; DECLARE_CONN(); unsigned long code = 200; v7_val_t arg0 = v7_arg(v7, 0), arg1 = v7_arg(v7, 1); if (v7_is_truthy(v7, v7_get(v7, v7_get_this(v7), "_whd", ~0))) { rcode = v7_throwf(v7, "Error", "Headers already sent"); goto clean; } if (v7_is_number(arg0)) { code = v7_to_number(arg0); } write_http_status(c, code); http_write_headers(v7, arg1, c); mg_send(c, "\r\n", 2); v7_set(v7, v7_get_this(v7), "_whd", ~0, v7_mk_boolean(1)); *res = v7_get_this(v7); clean: return rcode; }
static enum v7_err esp_sj_uart_get_state(struct v7 *v7, struct esp_sj_uart_state **us) { int uart_no = v7_to_number(v7_get(v7, v7_get_this(v7), "_u", 2)); if (uart_no < 0 || uart_no > 1) { return v7_throwf(v7, "Error", "Invalid UART number"); } *us = &sj_us[uart_no]; return V7_OK; }
static enum v7_err Updater_startupdate(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t manifest_url_v = v7_arg(v7, 0); if (!v7_is_string(manifest_url_v)) { rcode = v7_throwf(v7, "Error", "URL is not a string"); } else { struct update_context *ctx = updater_context_create(); if (ctx == NULL) { rcode = v7_throwf(v7, "Error", "Failed to init updater"); } else if (start_update_download(ctx, v7_get_cstring(v7, &manifest_url_v)) < 0) { rcode = v7_throwf(v7, "Error", ctx->status_msg); } } *res = v7_mk_boolean(v7, rcode == V7_OK); return rcode; }
static enum v7_err UART_get(struct v7 *v7, v7_val_t *res) { enum v7_err ret = V7_OK; v7_val_t arg0 = v7_arg(v7, 0); int uart_no = v7_to_number(arg0); if (v7_is_number(arg0) && (uart_no == 0 || uart_no == 1)) { *res = sj_us[uart_no].obj; } else { ret = v7_throwf(v7, "Error", "Invalid UART number"); } return ret; }
/* Call the callback with a list of ssids found in the air. */ SJ_PRIVATE enum v7_err Wifi_scan(struct v7 *v7, v7_val_t *res) { v7_val_t cb; if (s_wifi_scan_cb.v7 != NULL) { return v7_throwf(v7, "Error", "scan in progress"); } cb = v7_arg(v7, 0); if (!v7_is_callable(v7, cb)) { return v7_throwf(v7, "Error", "Invalid argument"); } s_wifi_scan_cb.v7 = v7; s_wifi_scan_cb.v = cb; v7_own(v7, &s_wifi_scan_cb.v); sj_wifi_scan(sj_wifi_scan_done, &s_wifi_scan_cb); (void) res; return V7_OK; }
static enum v7_err Sys_wdtSetTimeout(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t timeoutv = v7_arg(v7, 0); if (!v7_is_number(timeoutv)) { rcode = v7_throwf(v7, "Error", "Timeout should be a number"); } else { sj_wdt_set_timeout(v7_get_double(v7, timeoutv)); } *res = v7_mk_boolean(v7, rcode == V7_OK); return V7_OK; }
SJ_PRIVATE enum v7_err Http_createServer(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t cb = v7_arg(v7, 0); if (!v7_is_callable(v7, cb)) { rcode = v7_throwf(v7, "Error", "Invalid argument"); goto clean; } *res = v7_mk_object(v7); v7_set_proto(v7, *res, sj_http_server_proto); v7_set(v7, *res, "_cb", ~0, cb); clean: return rcode; }
enum v7_err fill_ssl_connect_opts(struct v7 *v7, v7_val_t opts, int force_ssl, struct mg_connect_opts *copts) { enum v7_err rcode = V7_OK; v7_val_t v_use_ssl = v7_get(v7, opts, "use_ssl", ~0); v7_val_t v_ca_cert = v7_get(v7, opts, "ssl_ca_cert", ~0); v7_val_t v_cert = v7_get(v7, opts, "ssl_cert", ~0); v7_val_t v_server_name = v7_get(v7, opts, "ssl_server_name", ~0); if (!v7_is_undefined(v_ca_cert) && !v7_is_string(v_ca_cert)) { rcode = v7_throwf(v7, "TypeError", "ssl_ca_cert must be a string"); goto clean; } if (!v7_is_undefined(v_cert) && !v7_is_string(v_cert)) { rcode = v7_throwf(v7, "TypeError", "ssl_cert must be a string"); goto clean; } if (!v7_is_undefined(v_server_name) && !v7_is_string(v_server_name)) { rcode = v7_throwf(v7, "TypeError", "ssl_server_name must be a string"); goto clean; } copts->ssl_ca_cert = v7_get_cstring(v7, &v_ca_cert); copts->ssl_cert = v7_get_cstring(v7, &v_cert); copts->ssl_server_name = v7_get_cstring(v7, &v_server_name); if ((force_ssl || (v7_is_boolean(v_use_ssl) && v7_get_bool(v7, v_use_ssl) != 0)) && copts->ssl_ca_cert == NULL) { /* Defaults to configuration */ copts->ssl_ca_cert = get_cfg()->tls.ca_file; } clean: return rcode; }
SJ_PRIVATE enum v7_err Http_get(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; rcode = sj_http_request_common(v7, v7_arg(v7, 0), v7_arg(v7, 1), res); if (rcode != V7_OK) { goto clean; } /* Prepare things to close the connection immediately after response */ struct mg_connection *c = get_mgconn_obj(v7, *res); if (c == NULL) { rcode = v7_throwf(v7, "Error", "Connection is closed"); goto clean; } mg_send_http_chunk(c, "", 0); c->flags |= MG_F_CLOSE_CONNECTION_AFTER_RESPONSE; clean: return rcode; }
static enum v7_err UART_send(struct v7 *v7, v7_val_t *res) { struct esp_sj_uart_state *us; enum v7_err ret = esp_sj_uart_get_state(v7, &us); if (ret != V7_OK) return ret; v7_val_t arg0 = v7_arg(v7, 0); if (!v7_is_string(arg0)) { return v7_throwf(v7, "Error", "String arg required"); } size_t len = 0; const char *data = v7_get_string_data(v7, &arg0, &len); if (data != NULL && len > 0) { cs_rbuf_t *txb = esp_uart_tx_buf(us->uart_no); len = MIN(len, txb->avail); cs_rbuf_append(txb, (uint8_t *) data, len); esp_sj_uart_schedule_dispatcher(us->uart_no); } *res = v7_mk_number(len); return V7_OK; }
/* * Alternative implementation of JSON.parse(), needed when v7 parser is * disabled */ enum v7_err v7_alt_json_parse(struct v7 *v7, v7_val_t json_string, v7_val_t *res) { struct json_parse_ctx *ctx = (struct json_parse_ctx *) calloc(sizeof(struct json_parse_ctx), 1); size_t len; const char *str = v7_get_string(v7, &json_string, &len); int json_res; enum v7_err rcode = V7_OK; ctx->v7 = v7; ctx->result = V7_UNDEFINED; ctx->frame = NULL; v7_own(v7, &ctx->result); json_res = json_walk(str, len, frozen_cb, ctx); if (json_res >= 0) { /* Expression is parsed successfully */ *res = ctx->result; /* There should be no allocated frames */ assert(ctx->frame == NULL); } else { /* There was an error during parsing */ rcode = v7_throwf(v7, "SyntaxError", "Invalid JSON string"); /* There might be some allocated frames in case of malformed JSON */ while (ctx->frame != NULL) { ctx->frame = free_json_frame(ctx, ctx->frame); } } v7_disown(v7, &ctx->result); free(ctx); return rcode; }
/* * Create request object, used by `Http.request()` and `Http.get()` */ WARN_UNUSED_RESULT static enum v7_err sj_http_request_common(struct v7 *v7, v7_val_t opts, v7_val_t cb, v7_val_t *res) { enum v7_err rcode = V7_OK; char addr[200]; struct mg_connection *c; struct user_data *ud; struct mg_connect_opts copts; #ifdef MG_ENABLE_SSL int force_ssl; #endif memset(&copts, 0, sizeof(copts)); /* * Determine type of provided `opts`, and if it's a string, then parse * it to object */ if (v7_is_string(opts)) { rcode = sj_url_parse(v7, opts, &opts); if (rcode != V7_OK) { goto clean; } } else if (!v7_is_object(opts)) { rcode = v7_throwf(v7, "Error", "opts must be an object or a string URL"); goto clean; } /* * Now, `opts` is guaranteed to be an object. * Let's retrieve needed properties */ v7_val_t v_h = v7_get(v7, opts, "hostname", ~0); if (v7_is_undefined(v_h)) { v_h = v7_get(v7, opts, "host", ~0); } v7_val_t v_p = v7_get(v7, opts, "port", ~0); v7_val_t v_uri = v7_get(v7, opts, "path", ~0); v7_val_t v_m = v7_get(v7, opts, "method", ~0); v7_val_t v_hdrs = v7_get(v7, opts, "headers", ~0); /* Perform options validation and set defaults if needed */ int port = v7_is_number(v_p) ? v7_to_number(v_p) : 80; const char *host = v7_is_string(v_h) ? v7_to_cstring(v7, &v_h) : ""; const char *uri = v7_is_string(v_uri) ? v7_to_cstring(v7, &v_uri) : "/"; const char *method = v7_is_string(v_m) ? v7_to_cstring(v7, &v_m) : "GET"; #ifdef MG_ENABLE_SSL v7_val_t v_pr = v7_get(v7, opts, "protocol", ~0); const char *protocol = v7_is_string(v_pr) ? v7_to_cstring(v7, &v_pr) : ""; force_ssl = (strcasecmp(protocol, "https") == 0); if ((rcode = fill_ssl_connect_opts(v7, opts, force_ssl, &copts)) != V7_OK) { goto clean; } #endif /* Compose address like host:port */ snprintf(addr, sizeof(addr), "%s:%d", host, port); /* * Try to connect, passing `http_ev_handler` as the callback, which will * call provided JavaScript function (we'll set it in user data below). * TODO(alashkin): change mg_connect_opt to mg_connect_http_opt */ if ((c = mg_connect_opt(&sj_mgr, addr, http_ev_handler, copts)) == NULL) { rcode = v7_throwf(v7, "Error", "Cannot connect"); goto clean; } /* * Attach mongoose's built-in HTTP event handler to the connection, and send * necessary headers */ mg_set_protocol_http_websocket(c); mg_printf(c, "%s %s HTTP/1.1\r\n", method, uri); mg_printf(c, "Host: %s\r\n", host); http_write_headers(v7, v_hdrs, c); http_write_chunked_encoding_header(c); mg_printf(c, "%s", "\r\n"); /* * Allocate and initialize user data structure that is used by the JS HTTP * interface. Create the request object (which will have the request * prototype `sj_http_request_proto`), and set provided callback function. */ c->user_data = ud = (struct user_data *) calloc(1, sizeof(*ud)); ud->v7 = v7; ud->obj = v7_mk_object(v7); ud->handler = cb; v7_own(v7, &ud->obj); v7_set_proto(v7, ud->obj, sj_http_request_proto); /* internal property: mongoose connection */ v7_set(v7, ud->obj, "_c", ~0, v7_mk_foreign(c)); /* internal property: callback function that was passed as an argument */ v7_set(v7, ud->obj, "_cb", ~0, ud->handler); *res = ud->obj; clean: return rcode; }
/* * Parse URL; used for: * * - `URL.parse()` * - `Http.request()` and `Http.get()`, when provided `opts` is a string. */ static enum v7_err sj_url_parse(struct v7 *v7, v7_val_t url_v, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t opts, protocol_v; size_t i, j, len; int state = 0; const char *url; if (!v7_is_string(url_v)) { rcode = v7_throwf(v7, "TypeError", "URL must be a string"); goto clean; } url = v7_get_string_data(v7, &url_v, &len); opts = v7_mk_object(v7); for (i = j = 0; j < len; j++) { switch (state) { case 0: if (url[j] == '/') { protocol_v = v7_mk_string(v7, url + i, j - i - 1, 1); v7_set(v7, opts, "protocol", ~0, protocol_v); j += 1; i = j + 1; state = 1; } break; case 1: if (url[j] == '/' || (j > i && url[j] == ':') || j == len - 1) { int hl = j - i; if (j == len - 1 && url[j] != '/' && url[j] != ':') hl++; v7_set(v7, opts, "hostname", ~0, v7_mk_string(v7, url + i, hl, 1)); if (url[j] == '/' || j == len - 1) { const char *protocol = v7_to_cstring(v7, &protocol_v); int port = strcasecmp(protocol, "https") == 0 ? 443 : 80; v7_set(v7, opts, "port", ~0, v7_mk_number(port)); i = j; if (j == len - 1) j--; state = 3; } else { i = j + 1; state = 2; } } break; case 2: if (url[j] == '/' || j == len - 1) { char ps[6]; size_t l = j - i; if (j == len - 1) l++; if (l > sizeof(ps) - 1) l = sizeof(ps) - 1; memcpy(ps, url + i, l); ps[l] = '\0'; v7_set(v7, opts, "port", ~0, v7_mk_number(atoi(ps))); i = j; if (j == len - 1) j--; state = 3; } break; case 3: if (j == len - 1) { v7_val_t path_v = j - i > 0 ? v7_mk_string(v7, url + i, j - i + 1, 1) : v7_mk_string(v7, "/", 1, 1); v7_set(v7, opts, "path", ~0, path_v); } break; } } *res = opts; clean: return rcode; }
/* * Make a new MQTT client. * * Arguments: * url: url where to connect to * opts: option object * * Recognized option object properties: * * - clientId: string; mqtt client id. defaults to * Math.random().toString(16).substr(2, 10) * * Example: * * var client = MQTT.connect('mqtt://test.mosquitto.org'); * * client.on('connect', function () { * client.subscribe('presence'); * client.publish('presence', 'Hello mqtt'); * }); * * client.on('message', function (topic, message) { * console.log(message); * }); * * TLS can be enabled by choosing the `mqtts://` protocol. In that * case the default port is 8883 as defined by IANA. * * The API is modeled after https://www.npmjs.com/package/mqtt. * */ enum v7_err sj_mqtt_connect(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; const char *url; size_t len; struct mg_str host, scheme; unsigned int port; struct mg_connection *nc; struct user_data *ud; char *url_with_port = NULL; int use_ssl = 0; v7_val_t urlv = v7_arg(v7, 0), opts = v7_arg(v7, 1); v7_val_t client_id; v7_val_t proto = v7_get(v7, v7_get(v7, v7_get_global(v7), "MQTT", ~0), "proto", ~0); if (!v7_is_string(urlv)) { rcode = v7_throwf(v7, "Error", "invalid url string"); goto clean; } url = v7_get_string(v7, &urlv, &len); if (mg_parse_uri(mg_mk_str(url), &scheme, NULL, &host, &port, NULL, NULL, NULL) < 0) { rcode = v7_throwf(v7, "Error", "invalid url string"); goto clean; } if (mg_vcmp(&scheme, "mqtt") == 0) { url += sizeof("mqtt://") - 1; } else if (mg_vcmp(&scheme, "mqtts") == 0) { url += sizeof("mqtts://") - 1; use_ssl = 1; } else { rcode = v7_throwf(v7, "Error", "unsupported protocol"); goto clean; } client_id = v7_get(v7, opts, "clientId", ~0); if (v7_is_undefined(client_id)) { rcode = v7_exec(v7, "Math.random().toString(16).substr(2,8)", &client_id); if (rcode != V7_OK) { goto clean; } } if (port == 0) { if (asprintf(&url_with_port, "%.*s%s", (int) host.len, host.p, (use_ssl ? ":8883" : ":1883")) < 0) { rcode = v7_throwf(v7, "Error", "Out of memory"); goto clean; } } nc = mg_connect(&sj_mgr, url_with_port ? url_with_port : url, mqtt_ev_handler); if (nc == NULL) { rcode = v7_throwf(v7, "Error", "cannot create connection"); goto clean; } if (use_ssl) { #ifdef MG_ENABLE_SSL mg_set_ssl(nc, NULL, NULL); #else rcode = v7_throwf(v7, "Error", "SSL not enabled"); goto clean; #endif } mg_set_protocol_mqtt(nc); *res = v7_mk_object(v7); v7_set_proto(v7, *res, proto); ud = calloc(1, sizeof(*ud)); if (ud == NULL) { rcode = v7_throwf(v7, "Error", "Out of memory"); goto clean; } ud->v7 = v7; ud->client = *res; ud->client_id = strdup(v7_get_cstring(v7, &client_id)); if (ud->client_id == NULL) { free(ud); rcode = v7_throwf(v7, "Error", "Out of memory"); goto clean; } nc->user_data = ud; v7_own(v7, &ud->client); v7_def(v7, *res, "_nc", ~0, _V7_DESC_HIDDEN(1), v7_mk_foreign(v7, nc)); clean: free(url_with_port); return rcode; }