websocket client library
authorJosé Bollo <jose.bollo@iot.bzh>
Thu, 12 May 2016 20:50:35 +0000 (22:50 +0200)
committerJosé Bollo <jose.bollo@iot.bzh>
Thu, 12 May 2016 20:50:35 +0000 (22:50 +0200)
This introduce 2 files for creating client
of websockets x-afb-json1:
 - afb-wsj1.c
 - afb-ws-client.c

The file afb-wsj1.c implements the protocol
x-afb-json1 on top of afb-ws.c.
It could be used to rewrite afb-ws-json1.

The file afb-ws-client.c implements a
light version of the websocket handshaking
to open a afb-wsj1 based on an uri.

Change-Id: Ie53a3b4ff91a9efac32b667b57f8005266db6001
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
src/CMakeLists.txt
src/afb-ws-client.c [new file with mode: 0644]
src/afb-ws.c
src/afb-ws.h
src/afb-wsj1.c [new file with mode: 0644]
src/afb-wsj1.h [new file with mode: 0644]

index 2ff6edf..68beea0 100644 (file)
@@ -12,8 +12,10 @@ ADD_LIBRARY(src OBJECT
        afb-msg-json.c
        afb-sig-handler.c
        afb-websock.c
+       afb-ws-client.c
        afb-ws-json1.c
        afb-ws.c
+       afb-wsj1.c
        main.c
        session.c
        verbose.c
diff --git a/src/afb-ws-client.c b/src/afb-ws-client.c
new file mode 100644 (file)
index 0000000..6ed3f50
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2016 IoT.bzh
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed 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 _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "afb-wsj1.h"
+
+/**************** WebSocket handshake ****************************/
+
+static const char *compkeys[32] = {
+       "lYKr2sn9+ILcLpkqdrE2VQ==", "G5J7ncQnmS/MubIYcqKWM+E6k8I=",
+       "gjN6eOU/6Yy7dBTJ+EaQSw==", "P5QzN7mRt4DeRWxKdG7s4/NCEwk=",
+       "ziLin6OQ0/a1+cGaI9Mupg==", "yvpxcFJAGam6huL77vz34CdShyU=",
+       "KMfd2bHKah0U5mk2Kg/LIg==", "lyYxfDP5YunhkBF+nAWb/w6K4yg=",
+       "fQ/ISF1mNCPRMyAj3ucqNg==", "91YY1EUelb4eMU24Z8WHhJ9cHmc=",
+       "RHlfiVVE1lM1AJnErI8dFg==", "UdZQc0JaihQJV5ETCZ84Av88pxQ=",
+       "NVy3L2ujXN7v3KEJwK92ww==", "+dE7iITxhExjBtf06VYNWChHqx8=",
+       "cCNAgttlgELfbDDIfhujww==", "W2JiswqbTAXx5u84EtjbtqAW2Bg=",
+       "K+oQvEDWJP+WXzRS5BJDFw==", "szgW10a9AuD+HtfS4ylaqWfzWAs=",
+       "nmg43S4DpVaxye+oQv9KTw==", "8XK74jB9xFfTzzl0wTqW04k3tPE=",
+       "LIqZ23sEppbF4YJR9LQ4/w==", "f8lJBQEbR8QmmvPHZpA0smlIeeA=",
+       "WY1vvvY2j/3V9DAGW3ZZcA==", "lROlE4vL4cjU1Vnk6rISc9gVKN0=",
+       "Ia+dgHnA9QaBrbxuqh4wgQ==", "GiGjxFdSaF0EGTl2cjvFsVmJnfM=",
+       "MfpIVG082jFTV7SxTNNijQ==", "f5I2h53hBsT5ES3EHhnxAJ2nqsw=",
+       "kFumnAw5d/WctG0yAUHPiQ==", "aQQmOjoABl7mrbliTPS1bOkndOs=",
+       "MHiEc+Qc8w/SJ3zMHEM8pA==", "FVCxLBmoil3gY0jSX3aNJ6kR/t4="
+};
+
+static const char websocket_s[] = "websocket";
+static const char sec_websocket_key_s[] = "Sec-WebSocket-Key";
+static const char sec_websocket_version_s[] = "Sec-WebSocket-Version";
+static const char sec_websocket_accept_s[] = "Sec-WebSocket-Accept";
+static const char sec_websocket_protocol_s[] = "Sec-WebSocket-Protocol";
+
+static const char vseparators[] = " \t,";
+
+/* get randomly a pair of key/accept value */
+static void getkeypair(const char **key, const char **ack)
+{
+       int r;
+       r = rand();
+       while (r > 15)
+               r = (r & 15) + (r >> 4);
+       r = (r & 15) << 1;
+       *key = compkeys[r];
+       *ack = compkeys[r+1];
+}
+
+/* joins the strings using the separator */
+static char *strjoin(int count, const char **strings, const char *separ)
+{
+       char *result, *iter;
+       size_t length;
+       int idx;
+
+       /* creates the count if needed */
+       if (count < 0)
+               for(count = 0 ; strings[count] != NULL ; count++);
+
+       /* compute the length of the result */
+       length = 0;
+       if (count != 0) {
+       }
+
+       /* allocates the result */
+       result = malloc(length + 1);
+       if (result == NULL)
+               errno = ENOMEM;
+       else {
+               /* create the result */
+               if (count != 0) {
+                       iter = stpcpy(result, strings[idx = 0]);
+                       while (++idx < count)
+                               iter = stpcpy(stpcpy(iter, separ), strings[idx]);
+                       // assert(iter - result == length);
+               }
+               result[length] = 0;
+       }
+       return result;
+}
+
+/* creates the http message for the request */
+static int make_request(char **request, const char *path, const char *key, const char *protocols)
+{
+       int rc = asprintf(request, 
+                       "GET %s HTTP1.1\r\n"
+                       "Upgrade: websocket\r\n"
+                       "Connection: Upgrade\r\n"
+                       "Sec-WebSocket-Version: 13\r\n"
+                       "Sec-WebSocket-Key: %s\r\n"
+                       "Sec-WebSocket-Protocol: %s\r\n"
+                       "Content-Length: 0\r\n"
+                       "\r\n"
+                       , path
+                       , key
+                       , protocols
+               );
+       if (rc < 0) {
+               errno = ENOMEM;
+               *request = NULL;
+               return -1;
+       }
+       return rc;
+}
+
+/* create the request and send it to fd, returns the expected accept string */
+static const char *send_request(int fd, const char **protocols, const char *path)
+{
+       const char *key, *ack;
+       char *protolist, *request;
+       int length, rc;
+
+       /* make the list of accepted protocols */
+       protolist = strjoin(-1, protocols, ", ");
+       if (protolist == NULL)
+               return NULL;
+
+       /* create the request */
+       getkeypair(&key, &ack);
+       length = make_request(&request, path, key, protolist);
+       free(protolist);
+       if (length < 0)
+               return NULL;
+
+       /* send the request */
+       do { rc = (int)write(fd, request, length); } while(rc < 0 && errno == EINTR);
+       free(request);
+       return rc < 0 ? NULL : ack;
+}
+
+/* read a line not efficiently but without buffering */
+static int receive_line(int fd, char *line, int size)
+{
+       int rc, length = 0, cr = 0;
+       for(;;) {
+               if (length >= size) {
+                       errno = EFBIG;
+                       return -1;
+               }
+               do { rc = (int)read(fd, line + length, 1); } while (rc < 0 && errno == EINTR);
+               if (rc < 0)
+                       return -1;
+               if (line[length] == '\r')
+                       cr = 1;
+               else if (cr != 0 && line[length] == '\n') {
+                       line[--length] = 0;
+                       return length;
+               } else
+                       cr = 0;
+               length++;
+       }
+}
+
+/* check a header */
+static inline int isheader(const char *head, size_t klen, const char *key)
+{
+       return strncasecmp(head, key, klen) == 0 && key[klen] == 0;
+}
+
+/* receives and scan the response */
+static int receive_response(int fd, const char **protocols, const char *ack)
+{
+       char line[4096], *it;
+       int rc, haserr, result = -1;
+       size_t len, clen;
+
+       /* check the header line to be something like: "HTTP/1.1 101 Switching Protocols" */
+       rc = receive_line(fd, line, (int)sizeof(line));
+       if (rc < 0)
+               goto error;
+       len = strcspn(line, " ");
+       if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
+               goto error;
+       it = line + len;
+       len = strspn(line, " ");
+       if (len == 0)
+               goto error;
+       it += len;
+       len = strcspn(line, " ");
+       if (len != 3 || 0 != strncmp(line, "101", 3))
+               goto error;
+
+       /* reads the rest of the response until empty line */
+       clen = 0;
+       haserr = 0;
+       for(;;) {
+               rc = receive_line(fd, line, (int)sizeof(line));
+               if (rc < 0)
+                       goto error;
+               if (rc == 0)
+                       break;
+               len = strcspn(line, ": ");
+               if (len != 0 && line[len] == ':') {
+                       /* checks the headers values */
+                       it = line + len + 1;
+                       it += strspn(it, " ,");
+                       it[strcspn(it, " ,")] = 0;
+                       if (isheader(line, len, "Sec-WebSocket-Accept")) {
+                               if (strcmp(it, ack) != 0)
+                                       haserr = 1;
+                       } else if (isheader(line, len, "Sec-WebSocket-Protocol")) {
+                               result = 0;
+                               while(protocols[result] != NULL && strcmp(it, protocols[result]) != 0)
+                                       result++;
+                       } else if (isheader(line, len, "Upgrade")) {
+                               if (strcmp(it, "websocket") != 0)
+                                       haserr = 1;
+                       } else if (isheader(line, len, "Content-Length")) {
+                               clen = atol(it);
+                       }
+               }
+       }
+
+       /* skips the remaining of the message */
+       while (clen >= sizeof line) {
+               while (read(fd, line, sizeof line) < 0 && errno == EINTR);
+               clen -= sizeof line;
+       }
+       if (clen > 0) {
+               while (read(fd, line, len) < 0 && errno == EINTR);
+       }
+       if (haserr != 0)
+               result = -1;
+       else if (result < 0) {
+               result = 0;
+               while(protocols[result] != NULL)
+                       result++;
+       }
+error:
+       return result;
+}
+
+static int negociate(int fd, const char **protocols, const char *path)
+{
+       const char *ack = send_request(fd, protocols, path);
+       return ack == NULL ? -1 : receive_response(fd, protocols, ack);
+}
+
+/* tiny parse a "standard" websock uri ws://host:port/path... */
+static int parse_uri(const char *uri, char **host, char **service, const char **path)
+{
+       const char *h, *p;
+       size_t hlen, plen;
+
+       /* the scheme */
+       if (strncmp(uri, "ws://", 5) == 0)
+               uri += 5;
+
+       /* the host */
+       h = uri;
+       hlen = strcspn(h, ":/");
+       if (hlen == 0)
+               goto invalid;
+       uri += hlen;
+
+       /* the port (optional) */
+       if (*uri == ':') {
+               p = ++uri;
+               plen = strcspn(p, "/");
+               if (plen == 0)
+                       goto invalid;
+               uri += plen;
+       } else {
+               p = NULL;
+               plen = 0;
+       }
+
+       /* the path */
+       if (*uri != '/')
+               goto invalid;
+
+       /* make the result */
+       *host = strndup(h, hlen);
+       if (*host != NULL) {
+               return -1;
+               *service = plen ? strndup(h, hlen) : strdup("http");
+               if (*service != NULL) {
+                       *path = uri;
+                       return 0;
+               }
+               free(*host);
+       }
+       errno = ENOMEM;
+       goto error;
+invalid:
+       errno = EINVAL;
+error:
+       return -1;
+       
+}
+
+
+
+
+static const char *proto_json1[2] = { "x-afb-ws-json1",        NULL };
+
+struct afb_wsj1 *afb_ws_client_connect_wsj1(const char *uri, struct afb_wsj1_itf *itf, void *closure)
+{
+       int rc, fd;
+       char *host, *service;
+       const char *path;
+       struct addrinfo hint, *rai, *iai;
+       struct afb_wsj1 *result;
+
+       /* scan the uri */
+       rc = parse_uri(uri, &host, &service, &path);
+       if (rc < 0)
+               return NULL;
+
+       /* get addr */
+       memset(&hint, 0, sizeof hint);
+       hint.ai_family = AF_INET;
+       hint.ai_socktype = SOCK_STREAM;
+       rc = getaddrinfo(host, service, &hint, &rai);
+       free(host);
+       free(service);
+       if (rc != 0) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       /* get the socket */
+       result = NULL;
+       iai = rai;
+       while (iai != NULL) {
+               fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
+               if (fd >= 0) {
+                       rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
+                       if (rc == 0) {
+                               rc = negociate(fd, proto_json1, path);
+                               if (rc == 0) {
+                                       result = afb_wsj1_create(fd, itf, closure);
+                                       if (result != NULL)
+                                               break;
+                               }
+                       }
+                       close(fd);
+               }
+               iai = iai->ai_next;
+       }
+       freeaddrinfo(rai);
+       return result;
+}
+
+#if 0
+/* compute the queried path */
+static char *makequery(const char *path, const char *uuid, const char *token)
+{
+       char *result;
+       int rc;
+
+       while(*path == '/')
+               path++;
+       if (uuid == NULL) {
+               if (token == NULL)
+                       rc = asprintf(&result, "/%s", path);
+               else
+                       rc = asprintf(&result, "/%s?x-afb-token=%s", path, token);
+       } else {
+               if (token == NULL)
+                       rc = asprintf(&result, "/%s?x-afb-uuid=%s", path, uuid);
+               else
+                       rc = asprintf(&result, "/%s?x-afb-uuid=%s&x-afb-token=%s", path, uuid, token);
+       }
+       if (rc < 0) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       return result;
+}
+#endif
+
+
index d0cfc8a..5e8732d 100644 (file)
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <sys/uio.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include <systemd/sd-event.h>
 
@@ -244,6 +245,39 @@ int afb_ws_text(struct afb_ws *ws, const char *text, size_t length)
        return websock_text(ws->ws, 1, text, length);
 }
 
+/*
+ * Sends a variable list of texts to the endpoint of 'ws'.
+ * Returns 0 on success or -1 in case of error.
+ */
+int afb_ws_texts(struct afb_ws *ws, ...)
+{
+       va_list args;
+       struct iovec ios[32];
+       int count;
+       const char *s;
+
+       if (ws->ws == NULL) {
+               /* disconnected */
+               errno = EPIPE;
+               return -1;
+       }
+
+       count = 0;
+       va_start(args, ws);
+       s = va_arg(args, const char *);
+       while (s != NULL) {
+               if (count == 32) {
+                       errno = EINVAL;
+                       return -1;
+               }
+               ios[count].iov_base = (void*)s;
+               ios[count].iov_len = strlen(s);
+               s = va_arg(args, const char *);
+       }
+       va_end(args);
+       return websock_text_v(ws->ws, 1, ios, count);
+}
+
 /*
  * Sends a binary 'data' of 'length' to the endpoint of 'ws'.
  * Returns 0 on success or -1 in case of error.
@@ -425,4 +459,3 @@ static void aws_on_error(struct afb_ws *ws, uint16_t code, const void *data, siz
 }
 
 
-
index 1ef61ad..48df9e4 100644 (file)
@@ -34,5 +34,6 @@ extern void afb_ws_hangup(struct afb_ws *ws);
 extern int afb_ws_close(struct afb_ws *ws, uint16_t code, const char *reason);
 extern int afb_ws_error(struct afb_ws *ws, uint16_t code, const char *reason);
 extern int afb_ws_text(struct afb_ws *ws, const char *text, size_t length);
+extern int afb_ws_texts(struct afb_ws *ws, ...);
 extern int afb_ws_binary(struct afb_ws *ws, const void *data, size_t length);
 
diff --git a/src/afb-wsj1.c b/src/afb-wsj1.c
new file mode 100644 (file)
index 0000000..0a7dfd8
--- /dev/null
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2016 IoT.bzh
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed 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 _GNU_SOURCE
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <json-c/json.h>
+
+#include "afb-ws.h"
+#include "afb-wsj1.h"
+
+#define CALL 2
+#define RETOK 3
+#define RETERR 4
+#define EVENT 5
+
+static void wsj1_on_hangup(struct afb_wsj1 *ws);
+static void wsj1_on_text(struct afb_wsj1 *ws, char *text, size_t size);
+
+static struct afb_ws_itf wsj1_itf = {
+       .on_hangup = (void*)wsj1_on_hangup,
+       .on_text = (void*)wsj1_on_text
+};
+
+struct wsj1_call
+{
+       struct wsj1_call *next;
+       void (*callback)(void *, struct afb_wsj1_msg *);
+       void *closure;
+       char id[16];
+};
+
+struct afb_wsj1_msg
+{
+       int refcount;
+       struct afb_wsj1 *wsj1;
+       struct afb_wsj1_msg *next, *previous;
+       char *text;
+       int code;
+       char *id;
+       char *api;
+       char *verb;
+       char *event;
+       char *object_s;
+       size_t object_s_length;
+       char *token;
+       struct json_object *object_j;
+};
+
+struct afb_wsj1
+{
+       int refcount;
+       int genid;
+       struct afb_wsj1_itf *itf;
+       void *closure;
+       struct json_tokener *tokener;
+       struct afb_ws *ws;
+       struct afb_wsj1_msg *messages;
+       struct wsj1_call *calls;
+};
+
+struct afb_wsj1 *afb_wsj1_create(int fd, struct afb_wsj1_itf *itf, void *closure)
+{
+       struct afb_wsj1 *result;
+
+       assert(fd >= 0);
+
+       result = calloc(1, sizeof * result);
+       if (result == NULL)
+               goto error;
+
+       result->refcount = 1;
+       result->itf = itf;
+       result->closure = closure;
+
+       result->tokener = json_tokener_new();
+       if (result->tokener == NULL)
+               goto error2;
+
+       result->ws = afb_ws_create(fd, &wsj1_itf, result);
+       if (result->ws == NULL)
+               goto error3;
+
+       return result;
+
+error3:
+       json_tokener_free(result->tokener);
+error2:
+       free(result);
+error:
+       close(fd);
+       return NULL;
+}
+
+void afb_wsj1_addref(struct afb_wsj1 *wsj1)
+{
+       if (wsj1 != NULL)
+               wsj1->refcount++;
+}
+
+void afb_wsj1_unref(struct afb_wsj1 *wsj1)
+{
+       if (wsj1 != NULL && !--wsj1->refcount) {
+               afb_ws_destroy(wsj1->ws);
+               json_tokener_free(wsj1->tokener);
+               free(wsj1);
+       }
+}
+
+static void wsj1_on_hangup(struct afb_wsj1 *ws)
+{
+       if (ws->itf->on_hangup != NULL)
+               ws->itf->on_hangup(ws->closure);
+}
+
+
+static struct wsj1_call *wsj1_call_search(struct afb_wsj1 *wsj1, const char *id, int remove)
+{
+       struct wsj1_call *r, **p;
+       p = &wsj1->calls;
+       while((r = *p) != NULL) {
+               if (strcmp(r->id, id) == 0) {
+                       if (remove)
+                               *p = r->next;
+                       break;
+               }
+               p = &r->next;
+       }
+       return r;
+}
+
+static struct wsj1_call *wsj1_call_create(struct afb_wsj1 *wsj1, void (*on_reply)(void*,struct afb_wsj1_msg*), void *closure)
+{
+       struct wsj1_call *call = malloc(sizeof *call);
+       if (call == NULL)
+               errno = ENOMEM;
+       else {
+               do {
+                       if (wsj1->genid == 0)
+                               wsj1->genid = 999999;
+                       sprintf(call->id, "%d", wsj1->genid--);
+               } while (wsj1_call_search(wsj1, call->id, 0) != NULL);
+               call->callback = on_reply;
+               call->closure = closure;
+               call->next = wsj1->calls;
+               wsj1->calls = call;
+       }
+       return call;
+}
+
+
+static int wsj1_msg_scan(char *text, size_t items[10][2])
+{
+       char *pos, *beg, *end, c;
+       int aux, n = 0;
+
+       /* scan */
+       pos = text;
+
+       /* scans: [ */
+       while(*pos == ' ') pos++;
+       if (*pos++ != '[') goto bad_scan;
+
+       /* scans list */
+       while(*pos == ' ') pos++;
+       if (*pos != ']') {
+               for (;;) {
+                       if (n == 10)
+                               goto bad_scan;
+                       beg = pos;
+                       aux = 0;
+                       while (aux != 0 || (*pos != ',' && *pos != ']')) {
+                               switch(*pos++) {
+                               case '{': case '[': aux++; break;
+                               case '}': case ']': if (aux--) break;
+                               case 0: goto bad_scan;
+                               case '"':
+                                       do {
+                                               switch(c = *pos++) {
+                                               case '\\': if (*pos++) break;
+                                               case 0: goto bad_scan;
+                                               }
+                                       } while(c != '"');
+                               }
+                       }
+                       end = pos;
+                       while (end > beg && end[-1] == ' ')
+                               end--;
+                       items[n][0] = beg - text; /* start offset */
+                       items[n][1] = end - beg;  /* length */
+                       n++;
+                       if (*pos == ']')
+                               break;
+                       while(*++pos == ' ');
+               }
+       }
+       while(*++pos == ' ');
+       if (*pos) goto bad_scan;
+       return n;
+
+bad_scan:
+       return -1;
+}
+
+static char *wsj1_msg_parse_extract(char *text, size_t offset, size_t size)
+{
+       text[offset + size] = 0;
+       return text + offset;
+}
+
+static char *wsj1_msg_parse_string(char *text, size_t offset, size_t size)
+{
+       if (size > 1 && text[offset] == '"') {
+               offset += 1;
+               size -= 2;
+       }
+       return wsj1_msg_parse_extract(text, offset, size);
+}
+
+static void wsj1_on_text(struct afb_wsj1 *wsj1, char *text, size_t size)
+{
+       size_t items[10][2];
+       int n;
+       struct afb_wsj1_msg *msg;
+       struct wsj1_call *call;
+
+       /* allocate */
+       msg = calloc(1, sizeof *msg);
+       if (msg == NULL)
+               goto alloc_error;
+
+       /* scan */
+       n = wsj1_msg_scan(text, items);
+       if (n < 0)
+               goto bad_header;
+
+       /* scans code: 2|3|4|5 */
+       if (items[0][1] != 1) goto bad_header;
+       switch (text[items[0][0]]) {
+       case '2': msg->code = CALL; break;
+       case '3': msg->code = RETOK; break;
+       case '4': msg->code = RETERR; break;
+       case '5': msg->code = EVENT; break;
+       default: goto bad_header;
+       }
+
+       /* fills the message */
+       switch (msg->code) {
+       case CALL:
+               if (n != 4 && n != 5) goto bad_header;
+               msg->id = wsj1_msg_parse_string(text, items[1][0], items[1][1]);
+               msg->api = wsj1_msg_parse_string(text, items[2][0], items[2][1]);
+               msg->verb = strchr(msg->api, '/');
+               if (msg->verb == NULL) goto bad_header;
+               *msg->verb++ = 0;
+               msg->object_s = wsj1_msg_parse_extract(text, items[3][0], items[3][1]);
+               msg->object_s_length = items[3][1];
+               msg->token = n == 5 ? wsj1_msg_parse_string(text, items[4][0], items[4][1]) : NULL;
+               break;
+       case RETOK:
+       case RETERR:
+               if (n != 3 && n != 4) goto bad_header;
+               msg->id = wsj1_msg_parse_string(text, items[1][0], items[1][1]);
+               call = wsj1_call_search(wsj1, msg->id, 1);
+               if (call == NULL) goto bad_header;
+               msg->object_s = wsj1_msg_parse_extract(text, items[2][0], items[2][1]);
+               msg->object_s_length = items[2][1];
+               msg->token = n == 5 ? wsj1_msg_parse_string(text, items[3][0], items[3][1]) : NULL;
+               break;
+       case EVENT:
+               if (n != 3) goto bad_header;
+               msg->event = wsj1_msg_parse_string(text, items[1][0], items[1][1]);
+               msg->object_s = wsj1_msg_parse_extract(text, items[2][0], items[2][1]);
+               msg->object_s_length = items[2][1];
+               break;
+       }
+       /* done */
+       msg->text = text;
+
+       /* fill and record the request */
+       msg->refcount = 1;
+       afb_wsj1_addref(wsj1);
+       msg->wsj1 = wsj1;
+       msg->next = wsj1->messages;
+       msg->next->previous = msg;
+       wsj1->messages = msg;
+
+       /* incoke the handler */
+       switch (msg->code) {
+       case CALL:
+               wsj1->itf->on_call(wsj1->closure, msg->api, msg->verb, msg);
+               break;
+       case RETOK:
+       case RETERR:
+               call->callback(call->closure, msg);
+               free(call);
+               break;
+       case EVENT:
+               wsj1->itf->on_event(wsj1->closure, msg->event, msg);
+               break;
+       }
+       afb_wsj1_msg_unref(msg);
+       return;
+
+bad_header:
+       free(msg);
+alloc_error:
+       free(text);
+       afb_ws_close(wsj1->ws, 1008, NULL);
+}
+
+void afb_wsj1_msg_addref(struct afb_wsj1_msg *msg)
+{
+       if (msg != NULL)
+               msg->refcount++;
+}
+
+void afb_wsj1_msg_unref(struct afb_wsj1_msg *msg)
+{
+       if (msg != NULL && --msg->refcount == 0) {
+               /* unlink the message */
+               if (msg->next != NULL)
+                       msg->next->previous = msg->previous;
+               if (msg->previous == NULL)
+                       msg->wsj1->messages = msg->next;
+               else
+                       msg->previous->next = msg->next;
+               /* free ressources */
+               afb_wsj1_unref(msg->wsj1);
+               json_object_put(msg->object_j);
+               free(msg->text);
+               free(msg);
+       }
+}
+
+const char *afb_wsj1_msg_object_s(struct afb_wsj1_msg *msg)
+{
+       return msg->object_s;
+}
+
+struct json_object *afb_wsj1_msg_object_j(struct afb_wsj1_msg *msg)
+{
+       struct json_object *object = msg->object_j;
+       if (object == NULL) {
+               json_tokener_reset(msg->wsj1->tokener);
+               object = json_tokener_parse_ex(msg->wsj1->tokener, msg->object_s, (int)msg->object_s_length);
+               if (object == NULL) {
+                       /* lazy error detection of json request. Is it to improve? */
+                       object = json_object_new_string_len(msg->object_s, (int)msg->object_s_length);
+               }
+               msg->object_j = object;
+       }
+       return object;
+}
+
+int afb_wsj1_msg_is_call(struct afb_wsj1_msg *msg)
+{
+       return msg->code == CALL;
+}
+
+int afb_wsj1_msg_is_reply(struct afb_wsj1_msg *msg)
+{
+       return msg->code == RETOK || msg->code == RETERR;
+}
+
+int afb_wsj1_msg_is_reply_ok(struct afb_wsj1_msg *msg)
+{
+       return msg->code == RETOK;
+}
+
+int afb_wsj1_msg_is_reply_error(struct afb_wsj1_msg *msg)
+{
+       return msg->code == RETERR;
+}
+
+int afb_wsj1_msg_is_event(struct afb_wsj1_msg *msg)
+{
+       return msg->code == EVENT;
+}
+
+const char *afb_wsj1_msg_api(struct afb_wsj1_msg *msg)
+{
+       return msg->api;
+}
+
+const char *afb_wsj1_msg_verb(struct afb_wsj1_msg *msg)
+{
+       return msg->verb;
+}
+
+const char *afb_wsj1_msg_event(struct afb_wsj1_msg *msg)
+{
+       return msg->event;
+}
+
+const char *afb_wsj1_msg_token(struct afb_wsj1_msg *msg)
+{
+       return msg->token;
+}
+
+
+
+
+
+
+
+
+
+
+#if 0
+
+
+
+
+
+static void wsj1_emit(struct afb_wsj1 *wsj1, int code, const char *id, size_t idlen, struct json_object *data, const char *token)
+{
+       json_object *msg;
+       const char *txt;
+
+       /* pack the message */
+       msg = json_object_new_array();
+       json_object_array_add(msg, json_object_new_int(code));
+       json_object_array_add(msg, json_object_new_string_len(id, (int)idlen));
+       json_object_array_add(msg, data);
+       if (token)
+               json_object_array_add(msg, json_object_new_string(token));
+
+       /* emits the reply */
+       txt = json_object_to_json_string(msg);
+       afb_ws_text(wsj1->ws, txt, strlen(txt));
+       json_object_put(msg);
+}
+
+static void wsj1_msg_reply(struct afb_wsj1_msg *msg, int retcode, const char *status, const char *info, json_object *resp)
+{
+       const char *token = afb_context_sent_token(&msg->context);
+       wsj1_emit(msg->wsj1, retcode, msg->id, msg->idlen, afb_msg_json_reply(status, info, resp, token, NULL), token);
+}
+
+static void wsj1_msg_fail(struct afb_wsj1_msg *msg, const char *status, const char *info)
+{
+       wsj1_msg_reply(msg, RETERR, status, info, NULL);
+}
+
+static void wsj1_msg_success(struct afb_wsj1_msg *msg, json_object *obj, const char *info)
+{
+       wsj1_msg_reply(msg, RETOK, "success", info, obj);
+}
+
+static const char *wsj1_msg_raw(struct afb_wsj1_msg *msg, size_t *size)
+{
+       *size = msg->objlen;
+       return msg->obj;
+}
+
+static void wsj1_msg_send(struct afb_wsj1_msg *msg, const char *buffer, size_t size)
+{
+       afb_ws_text(msg->wsj1->ws, buffer, size);
+}
+
+static void wsj1_send_event(struct afb_wsj1 *wsj1, const char *event, struct json_object *object)
+{
+       wsj1_emit(wsj1, EVENT, event, strlen(event), afb_msg_json_event(event, object), NULL);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#endif
+
+
+static int wsj1_send_isot(struct afb_wsj1 *wsj1, int i1, const char *s1, const char *o1, const char *t1)
+{
+       char code[2] = { (char)('0' + i1), 0 };
+       return afb_ws_texts(wsj1->ws, "[", code, ",\"", s1, "\",", o1, t1 != NULL ? ",\"" : "]", t1, "\"]", NULL);
+}
+
+static int wsj1_send_issot(struct afb_wsj1 *wsj1, int i1, const char *s1, const char *s2, const char *o1, const char *t1)
+{
+       char code[2] = { (char)('0' + i1), 0 };
+       return afb_ws_texts(wsj1->ws, "[", code, ",\"", s1, "\",\"", s2, "\",", o1, t1 != NULL ? ",\"" : "]", t1, "\"]", NULL);
+}
+
+int afb_wsj1_send_event_j(struct afb_wsj1 *wsj1, const char *event, struct json_object *object)
+{
+       return afb_wsj1_send_event_s(wsj1, event, json_object_to_json_string(object));
+}
+
+int afb_wsj1_send_event_s(struct afb_wsj1 *wsj1, const char *event, const char *object)
+{
+       return wsj1_send_isot(wsj1, EVENT, event, object, NULL);
+}
+
+int afb_wsj1_call_j(struct afb_wsj1 *wsj1, const char *api, const char *verb, struct json_object *object, void (*on_reply)(void *closure, struct afb_wsj1_msg *msg), void *closure)
+{
+       return afb_wsj1_call_s(wsj1, api, verb, json_object_to_json_string(object), on_reply, closure);
+}
+
+int afb_wsj1_call_s(struct afb_wsj1 *wsj1, const char *api, const char *verb, const char *object, void (*on_reply)(void *closure, struct afb_wsj1_msg *msg), void *closure)
+{
+       int rc;
+       struct wsj1_call *call;
+       char *tag;
+
+       /* allocates the call */
+       call = wsj1_call_create(wsj1, on_reply, closure);
+       if (call == NULL) {
+               errno = ENOMEM;
+               return -1;
+       }
+
+       /* makes the tag */
+       tag = alloca(2 + strlen(api) + strlen(verb));
+       stpcpy(stpcpy(stpcpy(tag, api), "/"), verb);
+
+       /* makes the call */
+       rc = wsj1_send_issot(wsj1, CALL, call->id, tag, object, NULL);
+       if (rc < 0) {
+               wsj1_call_search(wsj1, call->id, 1);
+               free(call);
+       }
+       return rc;
+}
+
+
+int afb_wsj1_reply_ok_j(struct afb_wsj1_msg *msg, struct json_object *object, const char *token)
+{
+       return afb_wsj1_reply_ok_s(msg, json_object_to_json_string(object), token);
+}
+
+int afb_wsj1_reply_ok_s(struct afb_wsj1_msg *msg, const char *object, const char *token)
+{
+       return wsj1_send_isot(msg->wsj1, RETOK, msg->id, object, token);
+}
+
+int afb_wsj1_reply_error_j(struct afb_wsj1_msg *msg, struct json_object *object, const char *token)
+{
+       return afb_wsj1_reply_error_s(msg, json_object_to_json_string(object), token);
+}
+
+int afb_wsj1_reply_error_s(struct afb_wsj1_msg *msg, const char *object, const char *token)
+{
+       return wsj1_send_isot(msg->wsj1, RETERR, msg->id, object, token);
+}
+
diff --git a/src/afb-wsj1.h b/src/afb-wsj1.h
new file mode 100644 (file)
index 0000000..d96367f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 IoT.bzh
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed 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.
+ */
+
+#pragma once
+
+struct afb_wsj1;
+struct afb_wsj1_msg;
+
+struct json_object;
+
+struct afb_wsj1_itf {
+       void (*on_hangup)(void *closure);
+       void (*on_call)(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg);
+       void (*on_event)(void *closure, const char *event, struct afb_wsj1_msg *msg);
+};
+
+extern struct afb_wsj1 *afb_wsj1_create(int fd, struct afb_wsj1_itf *itf, void *closure);
+
+extern void afb_wsj1_addref(struct afb_wsj1 *wsj1);
+extern void afb_wsj1_unref(struct afb_wsj1 *wsj1);
+
+extern int afb_wsj1_send_event_s(struct afb_wsj1 *wsj1, const char *event, const char *object);
+extern int afb_wsj1_send_event_j(struct afb_wsj1 *wsj1, const char *event, struct json_object *object);
+
+extern int afb_wsj1_call_s(struct afb_wsj1 *wsj1, const char *api, const char *verb, const char *object, void (*on_reply)(void *closure, struct afb_wsj1_msg *msg), void *closure);
+extern int afb_wsj1_call_j(struct afb_wsj1 *wsj1, const char *api, const char *verb, struct json_object *object, void (*on_reply)(void *closure, struct afb_wsj1_msg *msg), void *closure);
+
+extern int afb_wsj1_reply_ok_s(struct afb_wsj1_msg *msg, const char *object, const char *token);
+extern int afb_wsj1_reply_ok_j(struct afb_wsj1_msg *msg, struct json_object *object, const char *token);
+
+extern int afb_wsj1_reply_error_s(struct afb_wsj1_msg *msg, const char *object, const char *token);
+extern int afb_wsj1_reply_error_j(struct afb_wsj1_msg *msg, struct json_object *object, const char *token);
+
+extern void afb_wsj1_msg_addref(struct afb_wsj1_msg *msg);
+extern void afb_wsj1_msg_unref(struct afb_wsj1_msg *msg);
+
+extern int afb_wsj1_msg_is_call(struct afb_wsj1_msg *msg);
+extern int afb_wsj1_msg_is_reply(struct afb_wsj1_msg *msg);
+extern int afb_wsj1_msg_is_reply_ok(struct afb_wsj1_msg *msg);
+extern int afb_wsj1_msg_is_reply_error(struct afb_wsj1_msg *msg);
+extern int afb_wsj1_msg_is_event(struct afb_wsj1_msg *msg);
+
+extern const char *afb_wsj1_msg_api(struct afb_wsj1_msg *msg);
+extern const char *afb_wsj1_msg_verb(struct afb_wsj1_msg *msg);
+extern const char *afb_wsj1_msg_event(struct afb_wsj1_msg *msg);
+extern const char *afb_wsj1_msg_token(struct afb_wsj1_msg *msg);
+
+extern const char *afb_wsj1_msg_object_s(struct afb_wsj1_msg *msg);
+extern struct json_object *afb_wsj1_msg_object_j(struct afb_wsj1_msg *msg);
+