afb-client-demo/afb-ws: remove dependency on afb-common.*
[src/app-framework-binder.git] / src / afb-ws-client.c
1 /*
2  * Copyright (C) 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
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <assert.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netdb.h>
29 #include <fcntl.h>
30
31 #include "afb-wsj1.h"
32 #include "afb-common.h"
33
34 /**************** WebSocket handshake ****************************/
35
36 static const char *compkeys[32] = {
37         "lYKr2sn9+ILcLpkqdrE2VQ==", "G5J7ncQnmS/MubIYcqKWM+E6k8I=",
38         "gjN6eOU/6Yy7dBTJ+EaQSw==", "P5QzN7mRt4DeRWxKdG7s4/NCEwk=",
39         "ziLin6OQ0/a1+cGaI9Mupg==", "yvpxcFJAGam6huL77vz34CdShyU=",
40         "KMfd2bHKah0U5mk2Kg/LIg==", "lyYxfDP5YunhkBF+nAWb/w6K4yg=",
41         "fQ/ISF1mNCPRMyAj3ucqNg==", "91YY1EUelb4eMU24Z8WHhJ9cHmc=",
42         "RHlfiVVE1lM1AJnErI8dFg==", "UdZQc0JaihQJV5ETCZ84Av88pxQ=",
43         "NVy3L2ujXN7v3KEJwK92ww==", "+dE7iITxhExjBtf06VYNWChHqx8=",
44         "cCNAgttlgELfbDDIfhujww==", "W2JiswqbTAXx5u84EtjbtqAW2Bg=",
45         "K+oQvEDWJP+WXzRS5BJDFw==", "szgW10a9AuD+HtfS4ylaqWfzWAs=",
46         "nmg43S4DpVaxye+oQv9KTw==", "8XK74jB9xFfTzzl0wTqW04k3tPE=",
47         "LIqZ23sEppbF4YJR9LQ4/w==", "f8lJBQEbR8QmmvPHZpA0smlIeeA=",
48         "WY1vvvY2j/3V9DAGW3ZZcA==", "lROlE4vL4cjU1Vnk6rISc9gVKN0=",
49         "Ia+dgHnA9QaBrbxuqh4wgQ==", "GiGjxFdSaF0EGTl2cjvFsVmJnfM=",
50         "MfpIVG082jFTV7SxTNNijQ==", "f5I2h53hBsT5ES3EHhnxAJ2nqsw=",
51         "kFumnAw5d/WctG0yAUHPiQ==", "aQQmOjoABl7mrbliTPS1bOkndOs=",
52         "MHiEc+Qc8w/SJ3zMHEM8pA==", "FVCxLBmoil3gY0jSX3aNJ6kR/t4="
53 };
54
55 static const char websocket_s[] = "websocket";
56 static const char sec_websocket_key_s[] = "Sec-WebSocket-Key";
57 static const char sec_websocket_version_s[] = "Sec-WebSocket-Version";
58 static const char sec_websocket_accept_s[] = "Sec-WebSocket-Accept";
59 static const char sec_websocket_protocol_s[] = "Sec-WebSocket-Protocol";
60
61 static const char vseparators[] = " \t,";
62
63 /* get randomly a pair of key/accept value */
64 static void getkeypair(const char **key, const char **ack)
65 {
66         int r;
67         r = rand();
68         while (r > 15)
69                 r = (r & 15) + (r >> 4);
70         r = (r & 15) << 1;
71         *key = compkeys[r];
72         *ack = compkeys[r+1];
73 }
74
75 /* joins the strings using the separator */
76 static char *strjoin(int count, const char **strings, const char *separ)
77 {
78         char *result, *iter;
79         size_t length;
80         int idx;
81
82         /* creates the count if needed */
83         if (count < 0)
84                 for(count = 0 ; strings[count] != NULL ; count++);
85
86         /* compute the length of the result */
87         if (count == 0)
88                 length = 0;
89         else {
90                 length = (unsigned)(count - 1) * strlen(separ);
91                 for (idx = 0 ; idx < count ; idx ++)
92                         length += strlen(strings[idx]);
93         }
94
95         /* allocates the result */
96         result = malloc(length + 1);
97         if (result == NULL)
98                 errno = ENOMEM;
99         else {
100                 /* create the result */
101                 if (count != 0) {
102                         iter = stpcpy(result, strings[idx = 0]);
103                         while (++idx < count)
104                                 iter = stpcpy(stpcpy(iter, separ), strings[idx]);
105                         // assert(iter - result == length);
106                 }
107                 result[length] = 0;
108         }
109         return result;
110 }
111
112 /* creates the http message for the request */
113 static int make_request(char **request, const char *path, const char *host, const char *key, const char *protocols)
114 {
115         int rc = asprintf(request,
116                         "GET %s HTTP/1.1\r\n"
117                         "Host: %s\r\n"
118                         "Upgrade: websocket\r\n"
119                         "Connection: Upgrade\r\n"
120                         "Sec-WebSocket-Version: 13\r\n"
121                         "Sec-WebSocket-Key: %s\r\n"
122                         "Sec-WebSocket-Protocol: %s\r\n"
123                         "Content-Length: 0\r\n"
124                         "\r\n"
125                         , path
126                         , host
127                         , key
128                         , protocols
129                 );
130         if (rc < 0) {
131                 errno = ENOMEM;
132                 *request = NULL;
133                 return -1;
134         }
135         return rc;
136 }
137
138 /* create the request and send it to fd, returns the expected accept string */
139 static const char *send_request(int fd, const char **protocols, const char *path, const char *host)
140 {
141         const char *key, *ack;
142         char *protolist, *request;
143         int length, rc;
144
145         /* make the list of accepted protocols */
146         protolist = strjoin(-1, protocols, ", ");
147         if (protolist == NULL)
148                 return NULL;
149
150         /* create the request */
151         getkeypair(&key, &ack);
152         length = make_request(&request, path, host, key, protolist);
153         free(protolist);
154         if (length < 0)
155                 return NULL;
156
157         /* send the request */
158         do { rc = (int)write(fd, request, length); } while(rc < 0 && errno == EINTR);
159         free(request);
160         return rc < 0 ? NULL : ack;
161 }
162
163 /* read a line not efficiently but without buffering */
164 static int receive_line(int fd, char *line, int size)
165 {
166         int rc, length = 0, cr = 0;
167         for(;;) {
168                 if (length >= size) {
169                         errno = EFBIG;
170                         return -1;
171                 }
172                 do { rc = (int)read(fd, line + length, 1); } while (rc < 0 && errno == EINTR);
173                 if (rc < 0)
174                         return -1;
175                 if (line[length] == '\r')
176                         cr = 1;
177                 else if (cr != 0 && line[length] == '\n') {
178                         line[--length] = 0;
179                         return length;
180                 } else
181                         cr = 0;
182                 length++;
183         }
184 }
185
186 /* check a header */
187 static inline int isheader(const char *head, size_t klen, const char *key)
188 {
189         return strncasecmp(head, key, klen) == 0 && key[klen] == 0;
190 }
191
192 /* receives and scan the response */
193 static int receive_response(int fd, const char **protocols, const char *ack)
194 {
195         char line[4096], *it;
196         int rc, haserr, result = -1;
197         size_t len, clen;
198
199         /* check the header line to be something like: "HTTP/1.1 101 Switching Protocols" */
200         rc = receive_line(fd, line, (int)sizeof(line));
201         if (rc < 0)
202                 goto error;
203         len = strcspn(line, " ");
204         if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
205                 goto abort;
206         it = line + len;
207         len = strspn(it, " ");
208         if (len == 0)
209                 goto abort;
210         it += len;
211         len = strcspn(it, " ");
212         if (len != 3 || 0 != strncmp(it, "101", 3))
213                 goto abort;
214
215         /* reads the rest of the response until empty line */
216         clen = 0;
217         haserr = 0;
218         for(;;) {
219                 rc = receive_line(fd, line, (int)sizeof(line));
220                 if (rc < 0)
221                         goto error;
222                 if (rc == 0)
223                         break;
224                 len = strcspn(line, ": ");
225                 if (len != 0 && line[len] == ':') {
226                         /* checks the headers values */
227                         it = line + len + 1;
228                         it += strspn(it, " ,");
229                         it[strcspn(it, " ,")] = 0;
230                         if (isheader(line, len, "Sec-WebSocket-Accept")) {
231                                 if (strcmp(it, ack) != 0)
232                                         haserr = 1;
233                         } else if (isheader(line, len, "Sec-WebSocket-Protocol")) {
234                                 result = 0;
235                                 while(protocols[result] != NULL && strcmp(it, protocols[result]) != 0)
236                                         result++;
237                         } else if (isheader(line, len, "Upgrade")) {
238                                 if (strcmp(it, "websocket") != 0)
239                                         haserr = 1;
240                         } else if (isheader(line, len, "Content-Length")) {
241                                 clen = atol(it);
242                         }
243                 }
244         }
245
246         /* skips the remaining of the message */
247         while (clen >= sizeof line) {
248                 while (read(fd, line, sizeof line) < 0 && errno == EINTR);
249                 clen -= sizeof line;
250         }
251         if (clen > 0) {
252                 while (read(fd, line, len) < 0 && errno == EINTR);
253         }
254         if (haserr != 0 || result < 0)
255                 goto abort;
256         return result;
257 abort:
258         errno = ECONNABORTED;
259 error:
260         return -1;
261 }
262
263 static int negociate(int fd, const char **protocols, const char *path, const char *host)
264 {
265         const char *ack = send_request(fd, protocols, path, host);
266         return ack == NULL ? -1 : receive_response(fd, protocols, ack);
267 }
268
269 /* tiny parse a "standard" websock uri ws://host:port/path... */
270 static int parse_uri(const char *uri, char **host, char **service, const char **path)
271 {
272         const char *h, *p;
273         size_t hlen, plen;
274
275         /* the scheme */
276         if (strncmp(uri, "ws://", 5) == 0)
277                 uri += 5;
278
279         /* the host */
280         h = uri;
281         hlen = strcspn(h, ":/");
282         if (hlen == 0)
283                 goto invalid;
284         uri += hlen;
285
286         /* the port (optional) */
287         if (*uri == ':') {
288                 p = ++uri;
289                 plen = strcspn(p, "/");
290                 if (plen == 0)
291                         goto invalid;
292                 uri += plen;
293         } else {
294                 p = NULL;
295                 plen = 0;
296         }
297
298         /* the path */
299         if (*uri != '/')
300                 goto invalid;
301
302         /* make the result */
303         *host = strndup(h, hlen);
304         if (*host != NULL) {
305                 *service = plen ? strndup(p, plen) : strdup("http");
306                 if (*service != NULL) {
307                         *path = uri;
308                         return 0;
309                 }
310                 free(*host);
311         }
312         errno = ENOMEM;
313         goto error;
314 invalid:
315         errno = EINVAL;
316 error:
317         return -1;
318
319 }
320
321
322
323
324 static const char *proto_json1[2] = { "x-afb-ws-json1", NULL };
325
326 struct afb_wsj1 *afb_ws_client_connect_wsj1(const char *uri, struct afb_wsj1_itf *itf, void *closure)
327 {
328         int rc, fd;
329         char *host, *service, xhost[32];
330         const char *path;
331         struct addrinfo hint, *rai, *iai;
332         struct afb_wsj1 *result;
333
334         /* scan the uri */
335         rc = parse_uri(uri, &host, &service, &path);
336         if (rc < 0)
337                 return NULL;
338
339         /* get addr */
340         memset(&hint, 0, sizeof hint);
341         hint.ai_family = AF_INET;
342         hint.ai_socktype = SOCK_STREAM;
343         rc = getaddrinfo(host, service, &hint, &rai);
344         free(host);
345         free(service);
346         if (rc != 0) {
347                 errno = EINVAL;
348                 return NULL;
349         }
350
351         /* get the socket */
352         result = NULL;
353         iai = rai;
354         while (iai != NULL) {
355                 struct sockaddr_in *a = (struct sockaddr_in*)(iai->ai_addr);
356                 unsigned char *ipv4 = (unsigned char*)&(a->sin_addr.s_addr);
357                 unsigned char *port = (unsigned char*)&(a->sin_port);
358                 sprintf(xhost, "%d.%d.%d.%d:%d",
359                         (int)ipv4[0], (int)ipv4[1], (int)ipv4[2], (int)ipv4[3],
360                         (((int)port[0]) << 8)|(int)port[1]);
361                 fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
362                 if (fd >= 0) {
363                         rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
364                         if (rc == 0) {
365                                 rc = negociate(fd, proto_json1, path, xhost);
366                                 if (rc == 0) {
367                                         result = afb_wsj1_create(fd, itf, closure);
368                                         if (result != NULL) {
369                                                 fcntl(fd, F_SETFL, O_NONBLOCK);
370                                                 break;
371                                         }
372                                 }
373                         }
374                         close(fd);
375                 }
376                 iai = iai->ai_next;
377         }
378         freeaddrinfo(rai);
379         return result;
380 }
381
382 #if 0
383 /* compute the queried path */
384 static char *makequery(const char *path, const char *uuid, const char *token)
385 {
386         char *result;
387         int rc;
388
389         while(*path == '/')
390                 path++;
391         if (uuid == NULL) {
392                 if (token == NULL)
393                         rc = asprintf(&result, "/%s", path);
394                 else
395                         rc = asprintf(&result, "/%s?x-afb-token=%s", path, token);
396         } else {
397                 if (token == NULL)
398                         rc = asprintf(&result, "/%s?x-afb-uuid=%s", path, uuid);
399                 else
400                         rc = asprintf(&result, "/%s?x-afb-uuid=%s&x-afb-token=%s", path, uuid, token);
401         }
402         if (rc < 0) {
403                 errno = ENOMEM;
404                 return NULL;
405         }
406         return result;
407 }
408 #endif
409
410 /*
411  *
412  * Returns the internal event loop coming from afb-common
413  *
414  * Returns the handle to the event loop
415  */
416 struct sd_event *afb_ws_client_get_event_loop()
417 {
418         return afb_common_get_event_loop();
419 }
420
421