f20b9335e5c08600dfaf75cc4ae797f45a05c946
[platform/upstream/libwebsockets.git] / plugins / protocol_lws_meta.c
1 /*
2  * lws meta protocol handler
3  *
4  * Copyright (C) 2017 Andy Green <andy@warmcat.com>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation:
9  *  version 2.1 of the License.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA  02110-1301  USA
20  *
21  */
22
23 #if !defined (LWS_PLUGIN_STATIC)
24 #define LWS_DLL
25 #define LWS_INTERNAL
26 #include "../lib/libwebsockets.h"
27 #endif
28
29 #include <string.h>
30 #include <stdlib.h>
31
32 #define MAX_SUBCHANNELS 8
33
34 enum lws_meta_parser_state {
35         MP_IDLE, /* in body of message */
36
37         MP_CMD, /* await cmd */
38
39         MP_OPEN_SUBCHANNEL_PROTOCOL,
40         MP_OPEN_SUBCHANNEL_URL,
41         MP_OPEN_SUBCHANNEL_COOKIE,
42
43         MP_CLOSE_CHID,
44         MP_CLOSE_LEN,
45         MP_CLOSE_CODEM,
46         MP_CLOSE_CODEL,
47         MP_CLOSE_PAYLOAD,
48
49         MP_WRITE_CHID,
50 };
51
52 enum {
53         PENDING_TYPE_OPEN_RESULT = 0,
54         PENDING_TYPE_CHILD_CLOSE
55 };
56
57 /*
58  * while we haven't reported the result yet, we keep a linked-list of
59  * connection opens and their result.
60  */
61 struct pending_conn {
62         struct pending_conn *next;
63         char protocol[123];
64         char cookie[8];
65         int ch;
66         int len;
67
68         unsigned char type;
69 };
70
71 /*
72  * the parent, lws-meta connection
73  */
74 struct per_session_data__lws_meta {
75         struct lws *wsi[MAX_SUBCHANNELS + 1];
76         char told_closing[MAX_SUBCHANNELS + 1];
77         struct pending_conn *first;
78         struct pending_conn *pend;
79         char suburl[64];
80         unsigned char close[126];
81         int active_subchannel_tx, active_subchannel_rx;
82         enum lws_meta_parser_state state;
83         int pos;
84         int count_pending;
85         int round_robin;
86         int close_status_16;
87         int close_len;
88         int which_close;
89         int ch;
90 };
91
92 static int
93 lws_find_free_channel(struct per_session_data__lws_meta *pss)
94 {
95         int n;
96
97         for (n = 1; n <= MAX_SUBCHANNELS; n++)
98                 if (pss->wsi[n] == NULL)
99                         return n;
100
101         return 0; /* none free */
102 }
103
104 static struct lws *
105 lws_get_channel_wsi(struct per_session_data__lws_meta *pss, int ch)
106 {
107         if (!ch)
108                 return 0;
109         return pss->wsi[ch];
110 }
111
112 static int
113 lws_get_channel_id(struct lws *wsi)
114 {
115         return (lws_intptr_t)lws_get_opaque_parent_data(wsi);
116 }
117
118 static void
119 lws_set_channel_id(struct lws *wsi, int id)
120 {
121         lws_set_opaque_parent_data(wsi, (void *)(lws_intptr_t)id);
122 }
123
124 static struct pending_conn *
125 new_pending(struct per_session_data__lws_meta *pss)
126 {
127         struct pending_conn *pend;
128
129         if (pss->count_pending >= MAX_SUBCHANNELS * 2) {
130                 lwsl_notice("too many pending open subchannel\n");
131
132                 return NULL;
133         }
134
135         pss->count_pending++;
136
137         pend = malloc(sizeof(*pend));
138         if (!pend) {
139                 lwsl_notice("OOM\n");
140
141                 return NULL;
142         }
143
144         memset(pend, 0, sizeof(*pend));
145
146         return pend;
147 }
148
149 static int
150 callback_lws_meta(struct lws *wsi, enum lws_callback_reasons reason,
151                     void *user, void *in, size_t len)
152 {
153         struct per_session_data__lws_meta *pss =
154                         (struct per_session_data__lws_meta *)user;
155         struct lws_write_passthru *pas;
156         struct pending_conn *pend, *pend1;
157         struct lws *cwsi;
158         lws_sock_file_fd_type fd;
159         unsigned char *bin, buf[LWS_PRE + 512], *start = &buf[LWS_PRE],
160                         *end = &buf[sizeof(buf) - 1], *p = start;
161         int n, m;
162
163         switch (reason) {
164
165         case LWS_CALLBACK_ESTABLISHED:
166                 lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
167                 pss->state = MP_CMD;
168                 pss->pos = 0;
169                 break;
170
171         case LWS_CALLBACK_CLOSED:
172                 break;
173
174         case LWS_CALLBACK_CHILD_CLOSING:
175                 cwsi = (struct lws *)in;
176
177                 /* remove it from our tracking */
178                 pss->wsi[lws_get_channel_id(cwsi)] = NULL;
179
180                 if (pss->told_closing[lws_get_channel_id(cwsi)]) {
181                         pss->told_closing[lws_get_channel_id(cwsi)] = 0;
182                         break;
183                 }
184
185                 pend = new_pending(pss);
186                 if (!pend)
187                         return -1;
188
189                 /* note which channel id */
190                 pend->ch = lws_get_channel_id(cwsi);
191
192                 if (lws_get_close_length(cwsi)) {
193                         pend->len = lws_get_close_length(cwsi);
194                         memcpy(pend->protocol, lws_get_close_payload(cwsi),
195                                         pend->len);
196                 }
197
198                 pend->type = PENDING_TYPE_CHILD_CLOSE;
199                 pend->next = pss->first;
200                 pss->first = pend;
201
202                 /*
203                  * nothing else will complete from this wsi, so abandon
204                  * tracking in-process messages from this wsi.
205                  */
206
207                 if (pss->active_subchannel_tx == pend->ch)
208                         pss->active_subchannel_tx = 0;
209
210                 if (pss->active_subchannel_rx == pend->ch)
211                         pss->active_subchannel_rx = 0;
212                 break;
213
214         case LWS_CALLBACK_SERVER_WRITEABLE:
215
216                 if (!pss->active_subchannel_tx) {
217
218                         /* not in the middle of a message...
219                          *
220                          * PRIORITY 1: pending open and close notifications
221                          */
222
223                         pend = pss->first;
224                         while (pend && p < end - 128) {
225                                 switch (pend->type) {
226                                 case PENDING_TYPE_OPEN_RESULT:
227                                         lwsl_debug("open result %s %s\n",
228                                                 pend->cookie, pend->protocol);
229                                         *p++ = LWS_META_CMD_OPEN_RESULT;
230                                         memcpy(p, pend->cookie,
231                                                strlen(pend->cookie) + 1);
232                                         p += strlen(pend->cookie) + 1;
233                                         *p++ = LWS_META_TRANSPORT_OFFSET +
234                                                         pend->ch;
235                                         memcpy(p, pend->protocol,
236                                                strlen(pend->protocol) + 1);
237                                         p += strlen(pend->protocol) + 1;
238                                         break;
239                                 case PENDING_TYPE_CHILD_CLOSE:
240                                         *p++ = LWS_META_CMD_CLOSE_NOTIFY;
241                                         *p++ = LWS_META_TRANSPORT_OFFSET +
242                                                         pend->ch;
243                                         for (n = 0; n < pend->len; n++)
244                                                 *p++ = pend->protocol[n];
245                                         break;
246                                 }
247
248                                 pss->count_pending--;
249                                 pend1 = pend;
250                                 pend = pend->next;
251                                 free(pend1);
252                                 pss->first = pend;
253                         }
254
255                         if (p != start) {
256                                 if (lws_write(wsi, start, p - start,
257                                               LWS_WRITE_BINARY) < 0)
258                                         return 1;
259                                 if (pend) /* still more */
260                                         lws_callback_on_writable(wsi);
261                                 break;
262                         }
263
264                         /* PRIORITY 2: pick a child for the writable callback */
265
266                         cwsi = NULL;
267                         for (n = 0; n < MAX_SUBCHANNELS; n++) {
268                                 m = ((pss->round_robin + n) % MAX_SUBCHANNELS) + 1;
269                                 if (pss->wsi[m] &&
270                                     lws_get_child_pending_on_writable(pss->wsi[m])) {
271                                         pss->round_robin = m;
272                                         cwsi = pss->wsi[m];
273                                         break;
274                                 }
275                         }
276                 } else
277                         /* one child is in middle of message, stay with it */
278                         cwsi = pss->wsi[pss->active_subchannel_tx];
279
280                 if (!cwsi)
281                         break;
282
283                 lws_clear_child_pending_on_writable(cwsi);
284                 if (lws_handle_POLLOUT_event(cwsi, NULL))
285                         return -1;
286                 break;
287
288         case LWS_CALLBACK_RECEIVE:
289                 bin = (unsigned char *)in;
290
291                 /*
292                  * at the start of a message, we may have one or more
293                  * lws_meta command blocks.
294                  */
295                 while (pss->state != MP_IDLE &&
296                        (unsigned int)(bin - (unsigned char *)in) < len) {
297
298                         switch (pss->state) {
299                         case MP_IDLE: /* in body of message */
300
301                                 if (!lws_is_first_fragment(wsi))
302                                         break;
303
304                                 pss->state = MP_CMD;
305
306                                 /* fallthru */
307
308                         case MP_CMD: /* await cmd */
309
310                                 pss->pos = 0;
311
312                                 switch (*bin++) {
313                                 case LWS_META_CMD_OPEN_SUBCHANNEL:
314
315                                         pss->pend = new_pending(pss);
316                                         if (!pss->pend)
317                                                 return -1;
318
319                                         pss->state = MP_OPEN_SUBCHANNEL_PROTOCOL;
320
321                                         break;
322                                 case LWS_META_CMD_CLOSE_NOTIFY:
323                                 case LWS_META_CMD_CLOSE_RQ:
324                                         pss->which_close = bin[-1];
325                                         pss->state = MP_CLOSE_CHID;
326                                         break;
327                                 case LWS_META_CMD_WRITE:
328                                         pss->state = MP_WRITE_CHID;
329                                         break;
330
331                                 // open result is also illegal to receive
332                                 default:
333                                         lwsl_notice("bad lws_meta cmd 0x%x\n",
334                                                     bin[-1]);
335
336                                         return -1;
337                                 }
338
339                                 break;
340
341                         case MP_OPEN_SUBCHANNEL_PROTOCOL:
342                                 pss->pend->protocol[pss->pos++] = *bin++;
343                                 if (pss->pos == sizeof(pss->pend->protocol) - 1) {
344                                         lwsl_notice("protocol name too long\n");
345                                         return -1;
346                                 }
347
348                                 if (bin[-1] != '\0')
349                                         break;
350
351                                 pss->state = MP_OPEN_SUBCHANNEL_URL;
352                                 pss->pos = 0;
353                                 break;
354
355                         case MP_OPEN_SUBCHANNEL_URL:
356                                 pss->suburl[pss->pos++] = *bin++;
357                                 if (pss->pos == sizeof(pss->suburl) - 1) {
358                                         lwsl_notice("suburl too long\n");
359                                         return -1;
360                                 }
361
362                                 if (bin[-1] != '\0')
363                                         break;
364
365                                 pss->state = MP_OPEN_SUBCHANNEL_COOKIE;
366                                 pss->pos = 0;
367                                 break;
368
369                         case MP_OPEN_SUBCHANNEL_COOKIE:
370                                 pss->pend->cookie[pss->pos++] = *bin++;
371                                 if (pss->pos == sizeof(pss->pend->cookie) - 1) {
372                                         lwsl_notice("cookie too long\n");
373                                         return -1;
374                                 }
375
376                                 if (bin[-1] != '\0')
377                                         break;
378
379                                 lwsl_debug("%s: %s / %s / %s\n", __func__,
380                                             pss->pend->protocol,
381                                             pss->suburl,
382                                             pss->pend->cookie);
383
384                                 pss->pend->ch = lws_find_free_channel(pss);
385                                 if (pss->pend->ch) {
386
387                                         fd.sockfd = 0; // not going to be used
388
389                                         cwsi = lws_adopt_descriptor_vhost(
390                                                         lws_get_vhost(wsi),
391                                                         LWS_ADOPT_WS_PARENTIO,
392                                                         fd, pss->pend->protocol,
393                                                         wsi);
394
395                                         if (!cwsi) {
396                                                 lwsl_notice("open failed\n");
397                                                 pss->pend->ch = 0;
398                                         } else {
399                                                 pss->wsi[pss->pend->ch] = cwsi;
400                                                 lws_set_channel_id(cwsi,
401                                                                 pss->pend->ch);
402                                                 lwsl_debug("cwsi %p on parent %p open OK %s\n",
403                                                         cwsi, wsi, pss->pend->protocol);
404                                         }
405
406                                 } else
407                                         lwsl_notice("no free subchannels\n");
408
409                                 pss->pend->type = PENDING_TYPE_OPEN_RESULT;
410                                 pss->pend->next = pss->first;
411                                 pss->first = pss->pend;
412
413                                 lws_callback_on_writable(wsi);
414
415                                 pss->state = MP_CMD;
416                                 pss->pos = 0;
417                                 break;
418
419                         case MP_CLOSE_CHID:
420                                 pss->ch = (*bin++) - LWS_META_TRANSPORT_OFFSET;
421                                 pss->state = MP_CLOSE_LEN;
422                                 pss->pos = 0;
423                                 break;
424                         case MP_CLOSE_LEN:
425                                 pss->close_len = (*bin++) -
426                                         LWS_META_TRANSPORT_OFFSET;
427                                 lwsl_debug("close len %d\n", pss->close_len);
428                                 pss->state = MP_CLOSE_CODEM;
429                                 pss->pos = 0;
430                                 break;
431                         case MP_CLOSE_CODEM:
432                                 pss->close[pss->pos++] = *bin;
433                                 pss->close_status_16 = (*bin++) * 256;
434                                 pss->state = MP_CLOSE_CODEL;
435                                 break;
436                         case MP_CLOSE_CODEL:
437                                 pss->close[pss->pos++] = *bin;
438                                 pss->close_status_16 |= *bin++;
439                                 pss->state = MP_CLOSE_PAYLOAD;
440                                 break;
441                         case MP_CLOSE_PAYLOAD:
442                                 pss->close[pss->pos++] = *bin++;
443                                 if (pss->pos == sizeof(pss->close) - 1) {
444                                         lwsl_notice("close payload too long\n");
445                                         return -1;
446                                 }
447                                 if (--pss->close_len)
448                                         break;
449
450                                 pss->state = MP_CMD;
451
452                                 cwsi = lws_get_channel_wsi(pss, pss->ch);
453                                 if (!cwsi) {
454                                         lwsl_notice("close (%d) bad ch %d\n",
455                                                 pss->which_close, pss->ch);
456                                         break;
457                                 }
458
459                                 if (pss->which_close == LWS_META_CMD_CLOSE_RQ) {
460                                         if (lws_get_protocol(cwsi)->callback(
461                                             cwsi,
462                                             LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
463                                             lws_wsi_user(cwsi), &pss->close,
464                                             pss->pos))
465                                                 return -1;
466
467                                         /*
468                                          * we need to echo back the close payload
469                                          * when we send the close notification
470                                          */
471                                         lws_close_reason(cwsi,
472                                                          pss->close_status_16,
473                                                          &pss->close[2],
474                                                          pss->pos - 2);
475                                 }
476
477                                 /* so force him closed */
478
479                                 lws_set_timeout(cwsi,
480                                         PENDING_TIMEOUT_KILLED_BY_PARENT,
481                                         LWS_TO_KILL_SYNC);
482                                 break;
483
484                         case MP_WRITE_CHID:
485                                 pss->active_subchannel_rx = (*bin++) -
486                                         LWS_META_TRANSPORT_OFFSET;
487                                 pss->state = MP_IDLE;
488                                 break;
489                         }
490                 }
491
492                 len -= bin - (unsigned char *)in;
493
494                 if (!len)
495                         break;
496
497                 cwsi = lws_get_channel_wsi(pss, pss->active_subchannel_rx);
498                 if (!cwsi) {
499                         lwsl_notice("bad ch %d\n", pss->active_subchannel_rx);
500
501                         return -1;
502                 }
503
504                 lwsl_debug("%s: RX len %d\n", __func__, (int)len);
505
506                 if (lws_get_protocol(cwsi)->callback(cwsi,
507                                         LWS_CALLBACK_RECEIVE,
508                                         lws_wsi_user(cwsi), bin, len))
509                         lws_set_timeout(cwsi,
510                                 PENDING_TIMEOUT_KILLED_BY_PARENT,
511                                 LWS_TO_KILL_SYNC);
512
513                 if (lws_is_final_fragment(wsi)) {
514                         pss->active_subchannel_rx = 0;
515                         pss->state = MP_CMD;
516                 }
517                 break;
518
519         /*
520          * child wrote something via lws_write.... which passed it up to us to
521          * deal with, because we are the parent.  Prepend two bytes for
522          * lws-meta command and channel index, and send it out on parent
523          */
524         case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT:
525                 pas = in;
526                 bin = ((unsigned char *)pas->buf);
527
528                 if ((pas->wp & 7) == 4 /*LWS_WRITE_CLOSE */) {
529                         *p++ = LWS_META_CMD_CLOSE_NOTIFY;
530                         *p++ = LWS_META_TRANSPORT_OFFSET +
531                                         lws_get_channel_id(pas->wsi);
532                         *p++ = pas->len - 2 + LWS_META_TRANSPORT_OFFSET;
533                         *p++ = *bin++;
534                         *p++ = *bin++;
535                         for (n = 0; n < (int)pas->len - 2; n++)
536                                 *p++ = bin[n];
537
538                         if (lws_write(wsi, start, p - start,
539                                       LWS_WRITE_BINARY) < 0)
540                                 return 1;
541
542                         pss->told_closing[lws_get_channel_id(pas->wsi)] = 1;
543                         break;
544                 }
545
546                 if ((pas->wp & 7) == LWS_WRITE_TEXT ||
547                     (pas->wp & 7) == LWS_WRITE_BINARY) {
548
549                         if (pas->wp & LWS_WRITE_NO_FIN)
550                                 pss->active_subchannel_tx =
551                                                 lws_get_channel_id(pas->wsi);
552
553                         /* start of message, prepend the subchannel id */
554
555                         bin -= 2;
556                         bin[0] = LWS_META_CMD_WRITE;
557                         bin[1] = lws_get_channel_id(pas->wsi) +
558                                         LWS_META_TRANSPORT_OFFSET;
559                         if (lws_write(wsi, bin, pas->len + 2, pas->wp) < 0)
560                                 return 1;
561                 } else
562                         if (lws_write(wsi, bin, pas->len, pas->wp) < 0)
563                                 return 1;
564
565                 /* track EOM */
566
567                 if (!(pas->wp & LWS_WRITE_NO_FIN))
568                         pss->active_subchannel_tx = 0;
569                 break;
570
571         default:
572                 break;
573         }
574
575         return 0;
576 }
577
578 #define LWS_PLUGIN_PROTOCOL_LWS_META { \
579                 "lws-meta", \
580                 callback_lws_meta, \
581                 sizeof(struct per_session_data__lws_meta), \
582                 1024, /* rx buf size must be >= permessage-deflate rx size */ \
583         }
584
585 #if !defined (LWS_PLUGIN_STATIC)
586
587 static const struct lws_protocols protocols[] = {
588         LWS_PLUGIN_PROTOCOL_LWS_META
589 };
590
591 LWS_EXTERN LWS_VISIBLE int
592 init_protocol_lws_meta(struct lws_context *context,
593                              struct lws_plugin_capability *c)
594 {
595         if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
596                 lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
597                          c->api_magic);
598                 return 1;
599         }
600
601         c->protocols = protocols;
602         c->count_protocols = ARRAY_SIZE(protocols);
603         c->extensions = NULL;
604         c->count_extensions = 0;
605
606         return 0;
607 }
608
609 LWS_EXTERN LWS_VISIBLE int
610 destroy_protocol_lws_meta(struct lws_context *context)
611 {
612         return 0;
613 }
614 #endif