refactoring websockects low-level
[src/app-framework-binder.git] / src / afb-ws.c
1 /*
2  * Copyright 2016 IoT.bzh
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdint.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <sys/uio.h>
25 #include <string.h>
26
27 #include "websock.h"
28 #include "afb-ws.h"
29
30 #include "utils-upoll.h"
31
32 static ssize_t aws_writev(struct afb_ws *ws, const struct iovec *iov, int iovcnt);
33 static ssize_t aws_readv(struct afb_ws *ws, const struct iovec *iov, int iovcnt);
34 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size);
35 static void aws_on_text(struct afb_ws *ws, int last, size_t size);
36 static void aws_on_binary(struct afb_ws *ws, int last, size_t size);
37 static void aws_on_continue(struct afb_ws *ws, int last, size_t size);
38 static void aws_on_readable(struct afb_ws *ws);
39 static void aws_on_hangup(struct afb_ws *ws);
40
41 static struct websock_itf aws_itf = {
42         .writev = (void*)aws_writev,
43         .readv = (void*)aws_readv,
44
45         .on_ping = NULL,
46         .on_pong = NULL,
47         .on_close = (void*)aws_on_close,
48         .on_text = (void*)aws_on_text,
49         .on_binary = (void*)aws_on_binary,
50         .on_continue = (void*)aws_on_continue,
51         .on_extension = NULL
52 };
53
54 struct buf
55 {
56         char *buffer;
57         size_t size;
58 };
59
60 struct afb_ws
61 {
62         int fd;
63         enum { none, text, binary } type;
64         const struct afb_ws_itf *itf;
65         void *closure;
66         struct websock *ws;
67         struct upoll *up;
68         struct buf buffer;
69 };
70
71 struct afb_ws *afb_ws_create(int fd, const struct afb_ws_itf *itf, void *closure)
72 {
73         struct afb_ws *result;
74
75         assert(fd >= 0);
76
77         result = malloc(sizeof * result);
78         if (result == NULL)
79                 goto error;
80
81         result->fd = fd;
82         result->type = none;
83         result->itf = itf;
84         result->closure = closure;
85
86         result->ws = websock_create_v13(&aws_itf, result);
87         if (result->ws == NULL)
88                 goto error2;
89
90         result->up = upoll_open(result->fd, result);
91         if (result->up == NULL)
92                 goto error3;
93
94         result->buffer.buffer = NULL;
95         result->buffer.size = 0;
96
97         upoll_on_readable(result->up, (void*)aws_on_readable);
98         upoll_on_hangup(result->up, (void*)aws_on_hangup);
99
100         return result;
101
102 error3:
103         websock_destroy(result->ws);
104 error2:
105         free(result);
106 error:
107         close(fd);
108         return NULL;
109 }
110
111 void afb_ws_disconnect(struct afb_ws *ws)
112 {
113         struct upoll *up = ws->up;
114         struct websock *wsi = ws->ws;
115         ws->up = NULL;
116         ws->ws = NULL;
117         upoll_close(up);
118         websock_destroy(wsi);
119 }
120
121 void afb_ws_close(struct afb_ws *ws, uint16_t code)
122 {
123         websock_close_code(ws->ws, code, NULL, 0);
124 }
125
126 void afb_ws_text(struct afb_ws *ws, const char *text, size_t length)
127 {
128         websock_text(ws->ws, 1, text, length);
129 }
130
131 void afb_ws_binary(struct afb_ws *ws, const void *data, size_t length)
132 {
133         websock_binary(ws->ws, 1, data, length);
134 }
135
136 static ssize_t aws_writev(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
137 {
138         ssize_t rc;
139         do {
140                 rc = writev(ws->fd, iov, iovcnt);
141         } while(rc == -1 && errno == EINTR);
142         return rc;
143 }
144
145 static ssize_t aws_readv(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
146 {
147         ssize_t rc;
148         do {
149                 rc = readv(ws->fd, iov, iovcnt);
150         } while(rc == -1 && errno == EINTR);
151         return rc;
152 }
153
154 static void aws_on_readable(struct afb_ws *ws)
155 {
156         websock_dispatch(ws->ws);
157 }
158
159 static void aws_on_hangup(struct afb_ws *ws)
160 {
161         afb_ws_disconnect(ws);
162 }
163
164 static inline struct buf aws_pick_buffer(struct afb_ws *ws)
165 {
166         struct buf result = ws->buffer;
167         ws->buffer.buffer = NULL;
168         ws->buffer.size = 0;
169         return result;
170 }
171
172 static int aws_read(struct afb_ws *ws, size_t size)
173 {
174         ssize_t sz;
175         char *buffer;
176
177         if (size != 0) {
178                 buffer = realloc(ws->buffer.buffer, ws->buffer.size + size + 1);
179                 if (buffer == NULL)
180                         return 0;
181                 ws->buffer.buffer = buffer;
182                 sz = websock_read(ws->ws, &buffer[ws->buffer.size], size);
183                 if ((size_t)sz != size)
184                         return 0;
185                 ws->buffer.size += size;
186         }
187         return 1;
188 }
189
190 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size)
191 {
192         struct buf b;
193
194         ws->type = none;
195         if (ws->itf->on_close == NULL)
196                 websock_drop(ws->ws);
197         else {
198                 aws_read(ws, size);
199                 b = aws_pick_buffer(ws);
200                 ws->itf->on_close(ws->closure, code, b.buffer, b.size);
201         }
202 }
203
204 static void aws_on_text(struct afb_ws *ws, int last, size_t size)
205 {
206         if (ws->type != none) {
207                 websock_drop(ws->ws);
208                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR, NULL, 0);
209         } else if (ws->itf->on_text == NULL) {
210                 websock_drop(ws->ws);
211                 websock_close_code(ws->ws, WEBSOCKET_CODE_CANT_ACCEPT, NULL, 0);
212         } else {
213                 ws->type = text;
214                 aws_on_continue(ws, last, size);
215         }
216 }
217
218 static void aws_on_binary(struct afb_ws *ws, int last, size_t size)
219 {
220         if (ws->type != none) {
221                 websock_drop(ws->ws);
222                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR, NULL, 0);
223         } else if (ws->itf->on_binary == NULL) {
224                 websock_drop(ws->ws);
225                 websock_close_code(ws->ws, WEBSOCKET_CODE_CANT_ACCEPT, NULL, 0);
226         } else {
227                 ws->type = text;
228                 aws_on_continue(ws, last, size);
229         }
230 }
231
232 static void aws_on_continue(struct afb_ws *ws, int last, size_t size)
233 {
234         struct buf b;
235         int istxt;
236
237         if (ws->type == none) {
238                 websock_drop(ws->ws);
239                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR, NULL, 0);
240         } else {
241                 if (!aws_read(ws, size)) {
242                         aws_on_close(ws, WEBSOCKET_CODE_ABNORMAL, 0);
243                 } else if (last) {
244                         istxt = ws->type == text;
245                         ws->type = none;
246                         b = aws_pick_buffer(ws);
247                         b.buffer[b.size] = 0;
248                         (istxt ? ws->itf->on_text : ws->itf->on_binary)(ws->closure, b.buffer, b.size);
249                 }
250         }
251 }
252