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 /src/afb-wsj1.c | |
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>
Diffstat (limited to 'src/afb-wsj1.c')
-rw-r--r-- | src/afb-wsj1.c | 578 |
1 files changed, 578 insertions, 0 deletions
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); +} + |