new websocket handling
[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_disconnect(struct afb_ws *ws);
35 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size);
36 static void aws_on_text(struct afb_ws *ws, int last, size_t size);
37 static void aws_on_binary(struct afb_ws *ws, int last, size_t size);
38 static void aws_on_continue(struct afb_ws *ws, int last, size_t size);
39 static void aws_on_readable(struct afb_ws *ws);
40 static void aws_on_hangup(struct afb_ws *ws);
41
42 static struct websock_itf aws_itf = {
43         .writev = (void*)aws_writev,
44         .readv = (void*)aws_readv,
45         .disconnect = (void*)aws_disconnect,
46
47         .on_ping = NULL,
48         .on_pong = NULL,
49         .on_close = (void*)aws_on_close,
50         .on_text = (void*)aws_on_text,
51         .on_binary = (void*)aws_on_binary,
52         .on_continue = (void*)aws_on_continue,
53         .on_extension = NULL
54 };
55
56 struct buf
57 {
58         char *buffer;
59         size_t size;
60 };
61
62 struct afb_ws
63 {
64         int fd;
65         enum { none, text, binary } type;
66         const struct afb_ws_itf *itf;
67         void *closure;
68         struct websock *ws;
69         struct upoll *up;
70         struct buf buffer;
71 };
72
73 struct afb_ws *afb_ws_create(int fd, const struct afb_ws_itf *itf, void *closure)
74 {
75         struct afb_ws *result;
76
77         assert(fd >= 0);
78
79         result = malloc(sizeof * result);
80         if (result == NULL)
81                 goto error;
82
83         result->fd = fd;
84         result->type = none;
85         result->itf = itf;
86         result->closure = closure;
87
88         result->ws = websock_create_v13(&aws_itf, result);
89         if (result->ws == NULL)
90                 goto error2;
91
92         result->up = upoll_open(result->fd, result);
93         if (result->up == NULL)
94                 goto error3;
95
96         result->buffer.buffer = NULL;
97         result->buffer.size = 0;
98
99         upoll_on_readable(result->up, (void*)aws_on_readable);
100         upoll_on_hangup(result->up, (void*)aws_on_hangup);
101
102         return result;
103
104 error3:
105         websock_destroy(result->ws);
106 error2:
107         free(result);
108 error:
109         close(fd);
110         return NULL;
111 }
112
113 void afb_ws_disconnect(struct afb_ws *ws)
114 {
115         struct upoll *up = ws->up;
116         struct websock *wsi = ws->ws;
117         ws->up = NULL;
118         ws->ws = NULL;
119         upoll_close(up);
120         websock_destroy(wsi);
121 }
122
123 void afb_ws_close(struct afb_ws *ws, uint16_t code)
124 {
125         websock_close_code(ws->ws, code);
126 }
127
128 void afb_ws_text(struct afb_ws *ws, const char *text, size_t length)
129 {
130         websock_text(ws->ws, text, length);
131 }
132
133 void afb_ws_binary(struct afb_ws *ws, const void *data, size_t length)
134 {
135         websock_binary(ws->ws, data, length);
136 }
137
138 static ssize_t aws_writev(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
139 {
140         ssize_t rc;
141         do {
142                 rc = writev(ws->fd, iov, iovcnt);
143         } while(rc == -1 && errno == EINTR);
144         return rc;
145 }
146
147 static ssize_t aws_readv(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
148 {
149         ssize_t rc;
150         do {
151                 rc = readv(ws->fd, iov, iovcnt);
152         } while(rc == -1 && errno == EINTR);
153         return rc;
154 }
155
156 static void aws_on_readable(struct afb_ws *ws)
157 {
158         websock_dispatch(ws->ws);
159 }
160
161 static void aws_on_hangup(struct afb_ws *ws)
162 {
163         afb_ws_disconnect(ws);
164 }
165
166 static void aws_disconnect(struct afb_ws *ws)
167 {
168         afb_ws_disconnect(ws);
169 }
170
171 static inline struct buf aws_pick_buffer(struct afb_ws *ws)
172 {
173         struct buf result = ws->buffer;
174         ws->buffer.buffer = NULL;
175         ws->buffer.size = 0;
176         return result;
177 }
178
179 static int aws_read(struct afb_ws *ws, size_t size)
180 {
181         ssize_t sz;
182         char *buffer;
183
184         if (size != 0) {
185                 buffer = realloc(ws->buffer.buffer, ws->buffer.size + size + 1);
186                 if (buffer == NULL)
187                         return 0;
188                 ws->buffer.buffer = buffer;
189                 sz = websock_read(ws->ws, &buffer[ws->buffer.size], size);
190                 if ((size_t)sz != size)
191                         return 0;
192                 ws->buffer.size += size;
193         }
194         return 1;
195 }
196
197 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size)
198 {
199         struct buf b;
200
201         ws->type = none;
202         if (ws->itf->on_close == NULL)
203                 websock_drop(ws->ws);
204         else {
205                 aws_read(ws, size);
206                 b = aws_pick_buffer(ws);
207                 ws->itf->on_close(ws->closure, code, b.buffer, b.size);
208         }
209 }
210
211 static void aws_on_text(struct afb_ws *ws, int last, size_t size)
212 {
213         if (ws->type != none) {
214                 websock_drop(ws->ws);
215                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR);
216         } else if (ws->itf->on_text == NULL) {
217                 websock_drop(ws->ws);
218                 websock_close_code(ws->ws, WEBSOCKET_CODE_CANT_ACCEPT);
219         } else {
220                 ws->type = text;
221                 aws_on_continue(ws, last, size);
222         }
223 }
224
225 static void aws_on_binary(struct afb_ws *ws, int last, size_t size)
226 {
227         if (ws->type != none) {
228                 websock_drop(ws->ws);
229                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR);
230         } else if (ws->itf->on_binary == NULL) {
231                 websock_drop(ws->ws);
232                 websock_close_code(ws->ws, WEBSOCKET_CODE_CANT_ACCEPT);
233         } else {
234                 ws->type = text;
235                 aws_on_continue(ws, last, size);
236         }
237 }
238
239 static void aws_on_continue(struct afb_ws *ws, int last, size_t size)
240 {
241         struct buf b;
242         int istxt;
243
244         if (ws->type == none) {
245                 websock_drop(ws->ws);
246                 websock_close_code(ws->ws, WEBSOCKET_CODE_PROTOCOL_ERROR);
247         } else {
248                 if (!aws_read(ws, size)) {
249                         aws_on_close(ws, WEBSOCKET_CODE_ABNORMAL, 0);
250                 } else if (last) {
251                         istxt = ws->type == text;
252                         ws->type = none;
253                         b = aws_pick_buffer(ws);
254                         b.buffer[b.size] = 0;
255                         (istxt ? ws->itf->on_text : ws->itf->on_binary)(ws->closure, b.buffer, b.size);
256                 }
257         }
258 }
259