2 * lws meta protocol handler
4 * Copyright (C) 2017 Andy Green <andy@warmcat.com>
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.
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.
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,
23 #if !defined (LWS_PLUGIN_STATIC)
26 #include "../lib/libwebsockets.h"
32 #define MAX_SUBCHANNELS 8
34 enum lws_meta_parser_state {
35 MP_IDLE, /* in body of message */
37 MP_CMD, /* await cmd */
39 MP_OPEN_SUBCHANNEL_PROTOCOL,
40 MP_OPEN_SUBCHANNEL_URL,
41 MP_OPEN_SUBCHANNEL_COOKIE,
53 PENDING_TYPE_OPEN_RESULT = 0,
54 PENDING_TYPE_CHILD_CLOSE
58 * while we haven't reported the result yet, we keep a linked-list of
59 * connection opens and their result.
62 struct pending_conn *next;
72 * the parent, lws-meta connection
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;
80 unsigned char close[126];
81 int active_subchannel_tx, active_subchannel_rx;
82 enum lws_meta_parser_state state;
93 lws_find_free_channel(struct per_session_data__lws_meta *pss)
97 for (n = 1; n <= MAX_SUBCHANNELS; n++)
98 if (pss->wsi[n] == NULL)
101 return 0; /* none free */
105 lws_get_channel_wsi(struct per_session_data__lws_meta *pss, int ch)
113 lws_get_channel_id(struct lws *wsi)
115 return (lws_intptr_t)lws_get_opaque_parent_data(wsi);
119 lws_set_channel_id(struct lws *wsi, int id)
121 lws_set_opaque_parent_data(wsi, (void *)(lws_intptr_t)id);
124 static struct pending_conn *
125 new_pending(struct per_session_data__lws_meta *pss)
127 struct pending_conn *pend;
129 if (pss->count_pending >= MAX_SUBCHANNELS * 2) {
130 lwsl_notice("too many pending open subchannel\n");
135 pss->count_pending++;
137 pend = malloc(sizeof(*pend));
139 lwsl_notice("OOM\n");
144 memset(pend, 0, sizeof(*pend));
150 callback_lws_meta(struct lws *wsi, enum lws_callback_reasons reason,
151 void *user, void *in, size_t len)
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;
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;
165 case LWS_CALLBACK_ESTABLISHED:
166 lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
171 case LWS_CALLBACK_CLOSED:
174 case LWS_CALLBACK_CHILD_CLOSING:
175 cwsi = (struct lws *)in;
177 /* remove it from our tracking */
178 pss->wsi[lws_get_channel_id(cwsi)] = NULL;
180 if (pss->told_closing[lws_get_channel_id(cwsi)]) {
181 pss->told_closing[lws_get_channel_id(cwsi)] = 0;
185 pend = new_pending(pss);
189 /* note which channel id */
190 pend->ch = lws_get_channel_id(cwsi);
192 if (lws_get_close_length(cwsi)) {
193 pend->len = lws_get_close_length(cwsi);
194 memcpy(pend->protocol, lws_get_close_payload(cwsi),
198 pend->type = PENDING_TYPE_CHILD_CLOSE;
199 pend->next = pss->first;
203 * nothing else will complete from this wsi, so abandon
204 * tracking in-process messages from this wsi.
207 if (pss->active_subchannel_tx == pend->ch)
208 pss->active_subchannel_tx = 0;
210 if (pss->active_subchannel_rx == pend->ch)
211 pss->active_subchannel_rx = 0;
214 case LWS_CALLBACK_SERVER_WRITEABLE:
216 if (!pss->active_subchannel_tx) {
218 /* not in the middle of a message...
220 * PRIORITY 1: pending open and close notifications
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 +
235 memcpy(p, pend->protocol,
236 strlen(pend->protocol) + 1);
237 p += strlen(pend->protocol) + 1;
239 case PENDING_TYPE_CHILD_CLOSE:
240 *p++ = LWS_META_CMD_CLOSE_NOTIFY;
241 *p++ = LWS_META_TRANSPORT_OFFSET +
243 for (n = 0; n < pend->len; n++)
244 *p++ = pend->protocol[n];
248 pss->count_pending--;
256 if (lws_write(wsi, start, p - start,
257 LWS_WRITE_BINARY) < 0)
259 if (pend) /* still more */
260 lws_callback_on_writable(wsi);
264 /* PRIORITY 2: pick a child for the writable callback */
267 for (n = 0; n < MAX_SUBCHANNELS; n++) {
268 m = ((pss->round_robin + n) % MAX_SUBCHANNELS) + 1;
270 lws_get_child_pending_on_writable(pss->wsi[m])) {
271 pss->round_robin = m;
277 /* one child is in middle of message, stay with it */
278 cwsi = pss->wsi[pss->active_subchannel_tx];
283 lws_clear_child_pending_on_writable(cwsi);
284 if (lws_handle_POLLOUT_event(cwsi, NULL))
288 case LWS_CALLBACK_RECEIVE:
289 bin = (unsigned char *)in;
292 * at the start of a message, we may have one or more
293 * lws_meta command blocks.
295 while (pss->state != MP_IDLE &&
296 (unsigned int)(bin - (unsigned char *)in) < len) {
298 switch (pss->state) {
299 case MP_IDLE: /* in body of message */
301 if (!lws_is_first_fragment(wsi))
308 case MP_CMD: /* await cmd */
313 case LWS_META_CMD_OPEN_SUBCHANNEL:
315 pss->pend = new_pending(pss);
319 pss->state = MP_OPEN_SUBCHANNEL_PROTOCOL;
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;
327 case LWS_META_CMD_WRITE:
328 pss->state = MP_WRITE_CHID;
331 // open result is also illegal to receive
333 lwsl_notice("bad lws_meta cmd 0x%x\n",
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");
351 pss->state = MP_OPEN_SUBCHANNEL_URL;
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");
365 pss->state = MP_OPEN_SUBCHANNEL_COOKIE;
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");
379 lwsl_debug("%s: %s / %s / %s\n", __func__,
384 pss->pend->ch = lws_find_free_channel(pss);
387 fd.sockfd = 0; // not going to be used
389 cwsi = lws_adopt_descriptor_vhost(
391 LWS_ADOPT_WS_PARENTIO,
392 fd, pss->pend->protocol,
396 lwsl_notice("open failed\n");
399 pss->wsi[pss->pend->ch] = cwsi;
400 lws_set_channel_id(cwsi,
402 lwsl_debug("cwsi %p on parent %p open OK %s\n",
403 cwsi, wsi, pss->pend->protocol);
407 lwsl_notice("no free subchannels\n");
409 pss->pend->type = PENDING_TYPE_OPEN_RESULT;
410 pss->pend->next = pss->first;
411 pss->first = pss->pend;
413 lws_callback_on_writable(wsi);
420 pss->ch = (*bin++) - LWS_META_TRANSPORT_OFFSET;
421 pss->state = 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;
432 pss->close[pss->pos++] = *bin;
433 pss->close_status_16 = (*bin++) * 256;
434 pss->state = MP_CLOSE_CODEL;
437 pss->close[pss->pos++] = *bin;
438 pss->close_status_16 |= *bin++;
439 pss->state = MP_CLOSE_PAYLOAD;
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");
447 if (--pss->close_len)
452 cwsi = lws_get_channel_wsi(pss, pss->ch);
454 lwsl_notice("close (%d) bad ch %d\n",
455 pss->which_close, pss->ch);
459 if (pss->which_close == LWS_META_CMD_CLOSE_RQ) {
460 if (lws_get_protocol(cwsi)->callback(
462 LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
463 lws_wsi_user(cwsi), &pss->close,
468 * we need to echo back the close payload
469 * when we send the close notification
471 lws_close_reason(cwsi,
472 pss->close_status_16,
477 /* so force him closed */
479 lws_set_timeout(cwsi,
480 PENDING_TIMEOUT_KILLED_BY_PARENT,
485 pss->active_subchannel_rx = (*bin++) -
486 LWS_META_TRANSPORT_OFFSET;
487 pss->state = MP_IDLE;
492 len -= bin - (unsigned char *)in;
497 cwsi = lws_get_channel_wsi(pss, pss->active_subchannel_rx);
499 lwsl_notice("bad ch %d\n", pss->active_subchannel_rx);
504 lwsl_debug("%s: RX len %d\n", __func__, (int)len);
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,
513 if (lws_is_final_fragment(wsi)) {
514 pss->active_subchannel_rx = 0;
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
524 case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT:
526 bin = ((unsigned char *)pas->buf);
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++ = (unsigned char)pas->len +
533 LWS_META_TRANSPORT_OFFSET - 2;
536 for (n = 0; n < (int)pas->len - 2; n++)
539 if (lws_write(wsi, start, p - start,
540 LWS_WRITE_BINARY) < 0)
543 pss->told_closing[lws_get_channel_id(pas->wsi)] = 1;
547 if ((pas->wp & 7) == LWS_WRITE_TEXT ||
548 (pas->wp & 7) == LWS_WRITE_BINARY) {
550 if (pas->wp & LWS_WRITE_NO_FIN)
551 pss->active_subchannel_tx =
552 lws_get_channel_id(pas->wsi);
554 /* start of message, prepend the subchannel id */
557 bin[0] = LWS_META_CMD_WRITE;
558 bin[1] = lws_get_channel_id(pas->wsi) +
559 LWS_META_TRANSPORT_OFFSET;
560 if (lws_write(wsi, bin, pas->len + 2, pas->wp) < 0)
563 if (lws_write(wsi, bin, pas->len, pas->wp) < 0)
568 if (!(pas->wp & LWS_WRITE_NO_FIN))
569 pss->active_subchannel_tx = 0;
579 #define LWS_PLUGIN_PROTOCOL_LWS_META { \
582 sizeof(struct per_session_data__lws_meta), \
583 1024, /* rx buf size must be >= permessage-deflate rx size */ \
586 #if !defined (LWS_PLUGIN_STATIC)
588 static const struct lws_protocols protocols[] = {
589 LWS_PLUGIN_PROTOCOL_LWS_META
592 LWS_EXTERN LWS_VISIBLE int
593 init_protocol_lws_meta(struct lws_context *context,
594 struct lws_plugin_capability *c)
596 if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
597 lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
602 c->protocols = protocols;
603 c->count_protocols = ARRAY_SIZE(protocols);
604 c->extensions = NULL;
605 c->count_extensions = 0;
610 LWS_EXTERN LWS_VISIBLE int
611 destroy_protocol_lws_meta(struct lws_context *context)