diff options
author | José Bollo <jose.bollo@iot.bzh> | 2016-05-12 22:50:35 +0200 |
---|---|---|
committer | José Bollo <jose.bollo@iot.bzh> | 2016-05-12 22:50:35 +0200 |
commit | 7b97a7f27578d7f4ce7cc994e5df303ac5d9c886 (patch) | |
tree | 327277b7af9e21b28fcbc2292f17a6338b33d28f | |
parent | 3e775a79aa6ea82f93b2fc03f58ad261a17c73ca (diff) |
websocket client library
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>
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/afb-ws-client.c | 397 | ||||
-rw-r--r-- | src/afb-ws.c | 35 | ||||
-rw-r--r-- | src/afb-ws.h | 1 | ||||
-rw-r--r-- | src/afb-wsj1.c | 578 | ||||
-rw-r--r-- | src/afb-wsj1.h | 64 |
6 files changed, 1076 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ff6edfb..68beea00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 index 00000000..6ed3f50f --- /dev/null +++ b/src/afb-ws-client.c @@ -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 + + diff --git a/src/afb-ws.c b/src/afb-ws.c index d0cfc8a8..5e8732db 100644 --- a/src/afb-ws.c +++ b/src/afb-ws.c @@ -23,6 +23,7 @@ #include <errno.h> #include <sys/uio.h> #include <string.h> +#include <stdarg.h> #include <systemd/sd-event.h> @@ -245,6 +246,39 @@ int afb_ws_text(struct afb_ws *ws, const char *text, size_t 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 } - diff --git a/src/afb-ws.h b/src/afb-ws.h index 1ef61ad8..48df9e4f 100644 --- a/src/afb-ws.h +++ b/src/afb-ws.h @@ -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 index 00000000..0a7dfd80 --- /dev/null +++ b/src/afb-wsj1.c @@ -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 index 00000000..d96367f9 --- /dev/null +++ b/src/afb-wsj1.h @@ -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); + |