Beispiel #1
0
/*
 * 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;
}
Beispiel #2
0
/*
 * 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;
}
Beispiel #3
0
/*
 * 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;
}
Beispiel #4
0
/* 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;
}
Beispiel #5
0
/*
 * `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;
}
Beispiel #6
0
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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
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;
}
Beispiel #10
0
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;
}
Beispiel #11
0
/* 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;
}
Beispiel #12
0
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;
}
Beispiel #13
0
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;
}
Beispiel #14
0
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;
}
Beispiel #15
0
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;
}
Beispiel #16
0
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;
}
Beispiel #17
0
/*
 * 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;
}
Beispiel #18
0
/*
 * 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;
}
Beispiel #19
0
/*
 * 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;
}
Beispiel #20
0
/*
 * 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;
}