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