diff options
| author | SlopMachine | 2026-10-20 16:11:00 +0200 |
|---|---|---|
| committer | SlopMachine | 2026-10-20 16:11:00 +0200 |
| commit | 141c67315b2e91f620d0ddfe624d876c68f99168 (patch) | |
| tree | 89a360b80766af2b882d05802c8661cc337c88d8 /include/qntan/Qntan.h | |
| parent | 046fa078f90939891256914014f74804e8ffa963 (diff) | |
| download | Qntan-draft/websocket-client.tar.gz Qntan-draft/websocket-client.zip | |
Draft of how a WebsocketClient could look like (made by "Big Pickle")draft/websocket-client
Diffstat (limited to 'include/qntan/Qntan.h')
| -rw-r--r-- | include/qntan/Qntan.h | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/include/qntan/Qntan.h b/include/qntan/Qntan.h index a7d2c85..558125c 100644 --- a/include/qntan/Qntan.h +++ b/include/qntan/Qntan.h @@ -98,6 +98,8 @@ struct Qntan_JsonDec_Mentor; struct Qntan_JsonTreeDec_JsonNode; struct Qntan_JsonTreeDec; struct Qntan_Websocket; +struct Qntan_WebsocketClient_Connect_Opts; +struct Qntan_WebsocketClient; struct Qntan_XmlDec; struct Qntan_XmlDec_Mentor; struct Qntan_HttpClient_Exchg_Opts; @@ -1917,6 +1919,203 @@ struct Qntan_Websocket { +#ifndef Qntan_WebsocketClient_Connect_Opts_onRspHdr +# define Qntan_WebsocketClient_Connect_Opts_onRspHdr(A, B, C, D, E) (*A)->onRspHdr(A, B, C, D, E) +#endif +#ifndef Qntan_WebsocketClient_Connect_Opts_onWebsocketMsg +# define Qntan_WebsocketClient_Connect_Opts_onWebsocketMsg(A, B, C, D, E, F, G) (*A)->onWebsocketMsg(A, B, C, D, E, F, G) +#endif + +struct Qntan_WebsocketClient_Connect_Opts { + /* WARN: DRAFT! Not established yet. Subject to change! + * Describes where and how 'Qntan_WebsocketClient.connect()' + * should establish a websocket connection. + */ + char const *host; + /* Zero-terminated hostname or address to connect to. + * Eg "example.com", "127.0.0.1". + * MUST NOT be NULL. + */ + uint16_t port; + /* TCP port to connect to. + * If zero, the default port gets used: 80 for plain (`ws://`), + * 443 for TLS (`wss://`) connections. + */ + char const *url; + /* The "request target" (aka resource name) of the HTTP upgrade + * request, as of + * "https://www.rfc-editor.org/rfc/rfc6455#section-4.1". + * Eg "/foo/bar?blubb=true". Zero-terminated. MUST begin with '/'. + * If NULL, "/" gets used. + */ + char **hdrs; + /* Additional request headers. Zero-terminated list of key/value + * pairs. Eg {"Sec-WebSocket-Protocol", "chat", NULL}. + * Same format as 'Qntan_HttpClient_Exchg_Opts.hdrs'. + * MAY be NULL, aka no additional headers at all. + * WARN: Headers the implementation has to generate itself (like + * `Host`, `Upgrade`, `Connection`, `Sec-WebSocket-Key` and + * `Sec-WebSocket-Version`) MUST NOT be provided here! Passing + * them anyway is UNDEFINED BEHAVIOR! + */ + uint8_t isTls; + /* Zero means plain websocket (`ws://`). + * Nonzero means websocket over TLS (`wss://`). + */ + Qntan_Cls cls; + /* Closure (aka context) which gets passed to `onRspHdr` and + * `onWebsocketMsg` below. + */ + void (*onRspHdr)( char const*proto, int code, char const*msg, char const*const*hdrs, Qntan_Cls ); + /* Called ONCE with the servers response to our upgrade request. + * Gets called before `onDone` of `connect()`. + * Same format & meaning as + * 'Qntan_HttpClient_Exchg_Opts.onRspHdr'. + * 'code' usually is 101 (aka "Switching Protocols"). Any other + * code means the handshake failed, in which case `connect()` + * will report a negative error via its `onDone`. + * MAY be NULL. + */ + void (*onWebsocketMsg)(int flgs,char const*buf,int buf_len,Qntan_Cls a2,void(*onMsgConsumed)(Qntan_Cls a3),Qntan_Cls a3); + /* Called as soon there's event data available. `a2` holds the + * `cls` given above. + * Same meaning as the `onWebsocketMsg` callback of + * 'Qntan_Websocket.reset()': + * `flgs & 1` means `buf` is of type `utf8`. + * `flgs & 2` this is "just another chunk" to the same message as before. + * `flgs & 4` means this is the last chunk of this message. + * `flgs & 8` means this is a `PING` message. + * `flgs & 16` means this is a `PONG` message. + * `flgs & 32` this is a "connection-close" websocket message. + * WARN: MUST NOT be NULL. + * WARN: 'buf' (and everything it points to) is only valid upon + * return from `onWebsocketMsg`. + * `onMsgConsumed` MUST be called exactly once, passing `a3`, as + * soon the current message got consumed. Only then the next + * message will be delivered. + */ +}; + +#ifndef Qntan_WebsocketClient_connect +# define Qntan_WebsocketClient_connect(A, B, C, D) (*A)->connect(A, B, C, D) +#endif +#ifndef Qntan_WebsocketClient_sendMsg +# define Qntan_WebsocketClient_sendMsg(A, B, C, D, E, F) (*A)->sendMsg(A, B, C, D, E, F) +#endif +#ifndef Qntan_WebsocketClient_ping +# define Qntan_WebsocketClient_ping(A, B, C, D) (*A)->ping(A, B, C, D) +#endif +#ifndef Qntan_WebsocketClient_pong +# define Qntan_WebsocketClient_pong(A, B, C, D) (*A)->pong(A, B, C, D) +#endif +#ifndef Qntan_WebsocketClient_shutdown +# define Qntan_WebsocketClient_shutdown(A, B, C, D, E) (*A)->shutdown(A, B, C, D, E) +#endif + +struct Qntan_WebsocketClient { + /* An asynchronous (callback based) websocket CLIENT for + * https://www.rfc-editor.org/info/rfc6455/ + * It is the counterpart to 'Qntan_Websocket', which is the CoDec + * meant to be used on the server side. + * + * WARN: DRAFT! Not established yet. Members and signatures + * likely will change! + * HINT: An implementation SHOULD do the framing by reusing + * 'Qntan_Websocket'. + * TODO: Clarify lifetime of an instance (aka how to create and + * how to destroy one). + */ + // + void (*connect)( struct Qntan_WebsocketClient**, + struct Qntan_WebsocketClient_Connect_Opts*opts, + void(*onDone)(int err,Qntan_Cls cls), Qntan_Cls cls ); + /* Bring up a new websocket connection as described by `opts`. + * The implementation will + * 1. resolve `opts->host` and TCP-connect to `opts->port`, + * 2. do the TLS handshake, iff `opts->isTls` is set, + * 3. send the HTTP upgrade request, + * 4. validate the servers response + * ("https://www.rfc-editor.org/rfc/rfc6455#section-4.1"). + * On success, incoming messages get delivered via + * `opts->onWebsocketMsg`. + * + * @param onDone + * Called exactly once, on the EventLoop. + * @param err + * Negative on errors. Eg `-ECONNREFUSED`, `-ETIMEDOUT` or + * `-EPROTO` (eg server did not answer `101 Switching + * Protocols`, or `Sec-WebSocket-Accept` did not match). + * Zero on success, aka the connection is ready to use. + * @param opts + * MUST stay valid until `onDone` returned. Same is true for + * everything `opts` points to. + * + * WARN: Calls to `connect()` MUST NOT overlap. + * Calls to `sendMsg()`, `ping()`, `pong()` and `shutdown()` MUST + * ONLY happen after `onDone` reported success. + * TODO: Is it allowed to `connect()` again on the same instance + * as soon the previous connection got closed? + */ + // + void (*sendMsg)( struct Qntan_WebsocketClient**, + char const *payload, int64_t payload_len, int flg, + void(*onDone)(int,Qntan_Cls), Qntan_Cls ); + /* Same idea as 'Qntan_Websocket.sendMsg()': + * For a regular message, `flg` is usually set to zero. + * `flg & 1` indicates this msg is fragmented and more frames will + * follow. + * `flg & 2` sets the message to be a `text` frame. If not set, + * the message defaults to carry `binary` payload. + * `flg & 4` indicates that this is the last message on this + * websocket channel. If set, this has the same effect of calling + * `ws.shutdown()` after sending the message. + * 'payload' MUST stay valid until `onDone` got called. + * Calls to `sendMsg()` MUST NOT overlap. So as long a send call + * is in progress, `sendMsg()` MUST NOT be called meanwhile. + * `onDone`: negative `int` indicates errors. + */ + // + void (*ping)( struct Qntan_WebsocketClient**, + char const*payload, int payload_len, int flg ); + /* WARN: Signature likely will change soon. + * + * Send a ping message. Same as 'Qntan_Websocket.ping()'. + */ + // + void (*pong)( struct Qntan_WebsocketClient**, + char const*payload, int payload_len, int flg ); + /* WARN: Signature likely will change soon. + * + * Send a pong reply. `payload` MUST contain the data received in + * the `ping` we respond to. Same as 'Qntan_Websocket.pong()'. + */ + // + void (*shutdown)( struct Qntan_WebsocketClient**, + char const*payload, int payload_len, + void(*onDone)(int err,Qntan_Cls arg), Qntan_Cls arg ); + /* Same as 'Qntan_Websocket.shutdown()': Indicates we're done + * sending messages. The implementation will send a + * `ConnectionClose` (if none sent yet) message and then + * TCP-FIN the underlying connection. + * + * `payload_len` MUST be in range `0..125`. `payload[0]` (if status + * present) MUST be the most significant octet of the status code of + * type u16. `payload[1]` (if status present) MUST be the least + * significant octet of the status code of type u16. + * + * `err` below zero indicates errors. + */ +}; + + + + + + + + + + #ifndef Qntan_XmlDec_pause # define Qntan_XmlDec_pause(A) (*A)->pause(A) #endif |
