# lws-meta protocol lws-meta is a lightweight ws subprotocol that accepts other ws connections to the same server inside it and multiplexes their access to the connection. ``` Client Server conn1: \ / :conn1 conn2: = mux ------ lws-meta ws protocol ----- mux = :conn2 conn3: / \ :conn3 ``` You may have n client ws connections back to the server, but you now only have one tcp connection (and one SSL wrapper if using SSL) instead of n of those. If you currently make multiple ws connections back to the server, so you can have different protocols active in one webpage, this if for you. - The subprotocol code for the connections inside a lws-meta connection need zero changes from being a normal ws connection. It is unaware it is inside an lws-meta parent connection. - The traffic on the lws-meta connection is indistinguishable from standard ws traffic, so intermediaries won't object to it - The multiplexing is done in the protocol, **not by an extension**. So it's compatible with all browsers. - Javascript helper code is provided to very simply use lws-meta protocol instead of direct connections. The lws test server has been converted to use this by default. # Converting your server 1) include the provided lws-meta plugin (plugins/protocl_lws_meta.c) as an active protocol for your server. You can do that using runtime plugins, or include the plugin sources into your server at build-time. The lws test server uses the latter approach. That's all you need to do on the server side. # Converting your browser JS 1) import lws-common.js 2) Instantiate a parent lws-meta connection object ``` var lws_meta = new lws_meta_ws(); ``` 3) Connect the lws-meta object to your server ``` lws_meta.new_parent(get_appropriate_ws_url("?mirror=" + mirror_name)); ``` 4) Convert your actual ws connections to go via the lws_meta object ``` var my_ws = lws_meta.new_ws("", "dumb-increment-protocol"); ``` The first arg is the URL path, the second arg is the ws protocol you want. That's it. my_ws will get `onopen()`, `onmessage()` etc calls as before. # lws-meta wire protocol lws-meta works by adding some bytes at the start of a message indicating which channel the message applies to. Channel messages are atomic on the wire. The reason is if we tried to intersperse other channel fragments between one channels message fragments, an intermediary would observe violations of the ws framing rule about having to start a message with TEXT or BINARY, and use only CONTINUATION for the subsequent fragments. Eg ``` [ ch1 TEXT NOFIN ] [ ch2 BINARY FIN ] [ ch1 CONTINUATION FIN ] ``` is illegal to an observer that doesn't understand lws-meta headers in the packet payloads. So to avoid this situation, only complete messages may be sent from one subchannel in each direction at a time. Consequently, only the first fragment of each message is modified to have the extra two bytes identifying the subchannel it is aimed at, since the rest of the message from the same subchannel is defined to follow. If it makes latencies, modify the protocol sending large messages to send smaller messages, so the transmission of messages from other channels can be sent inbetween the smaller messages. ## lws-meta commands 1) CSTRING indicates a string terminated by 0x00 byte 2) Channel IDs are sent with 0x20 added to them, to guarantee valid UTF-8 ### 0x41: RX: LWS_META_CMD_OPEN_SUBCHANNEL - CSTRING: protocol name - CSTRING: url - CSTRING: cookie (7 bytes max) Client is requesting to open a new channel with the given protocol name, at the given url. The cookie (eg, channel name) is only used in LWS_META_CMD_OPEN_RESULT, when the channel id is assigned, so it is applied to the right channel. ### 0x42: TX: LWS_META_CMD_OPEN_RESULT - CSTRING cookie - BYTE channel id (0 indicates failed) - CSTRING: selected protocol name The server is informing the client of the results of a previous open request. The cookie the client sent to identify the request is returned along with a channel id to be used subsequently. If the channel ID is 0 (after subtracting the transport offset of 0x20) then the open request has failed. ### 0x43: TX: LWS_META_CMD_CLOSE_NOTIFY - BYTE channel id - BYTE: payload length + 0x20 - BYTE: close code MSB - BYTE: close code LSB - PAYLOAD: payload (< 123 bytes) Server notifies the client that a child has closed, for whatever reason. ### 0x44: RX: LWS_META_CMD_CLOSE_RQ - BYTE: channel id - BYTE: payload length + 0x20 - BYTE: close code MSB - BYTE: close code LSB - PAYLOAD: payload (< 123 bytes) The client requests to close a child connection ### 0x45: TX: LWS_META_CMD_WRITE - BYTE: channel id Normal write of payload n from lws-meta perspective is actually LWS_META_CMD_WRITE, channel id, then (n - 2) bytes of payload The command only appears at the start of a message, continuations do not have the command. ## Protocol Notes - Once the subchannel is up, overhead is only +2 bytes per message - Close reasons are supported in both directions - Ping and Pong are only supported at the lws-meta level, using normal ws ping and pong packets. - Only the final close of the tcp lws-meta connection itself goes out as a normal ws close frame. Subchannels close is done in a normal TEXT message using LWS_META_CMD_CLOSE_RQ and then the close packet payload. This is so intermediaries do not mistake subchannel closures for the tcp / ws link going down. Messages that start with LWS_META_CMD_OPEN_SUBCHANNEL only contain those commands but may contain any number of them for the whole duration of the message. The lws-meta js support collects child open requests made before the parent lws-meta connection is open, and dumps them all in a single message when it does open. Messages that start with LWS_META_CMD_OPEN_RESULT or LWS_META_CMD_CLOSE_NOTIFY only contain those two commands, but they may contain any number of them for the whole duration of the message. # Current Implemention Limitations - only server side is supported in lws. The client side JS for a browser is supported. - max number of child connections per parent at the moment is 8 - child connection URL paramter when opening the connection is ignored - there is no ah attached when the child connections are established inside the lws-meta parent. So header access functions will fail.