• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## Information for new role implementers
2
3### Introduction
4
5In lws the "role" is the job the wsi is doing in the system, eg,
6being an http1 or h2, or ws connection, or being a listen socket, etc.
7
8This is different than, eg, a new ws protocol or a different callback
9for an existing role.  A new role is needed when you want to add support for
10something completely new, like a completely new wire protocol that
11doesn't use http or ws.
12
13So... what's the point of implementing the protocol inside the lws role framework?
14
15You inherit all the well-maintained lws core functionality around:
16
17 - connection lifecycle sequencing in a valgrind-clean way
18
19 - client connection proxy support, for HTTP and Socks5
20
21 - tls support working equally on mbedTLS and OpenSSL and derivatives without any code in the role
22
23 - apis for cert lifecycle management and parsing
24
25 - event loop support working on all the lws event loops (poll, libuv , ev, and event)
26
27 - clean connection tracking and closing even on advanced event loops
28
29 - user code follows the same simple callbacks on wsi
30
31 - multi-vhost support
32
33 - core multithreaded service support with usually no locking requirement on the role code
34
35 - direct compatibility with all other lws roles + protocols in the same event loop
36
37 - compatibility with higher-level stuff like lwsws as the server application
38
39### Code placement
40
41The code specific to that role should live in `./lib/roles/**role name**`
42
43If a role is asymmetic between a client and server side, like http is, it
44should generally be implemented as a single role.
45
46### Allowing control over enabling roles
47
48All roles should add a cmake define `LWS_ROLE_**role name**` and make its build
49dependent on it in CMakeLists.txt.  Export the cmakedefine in `./cmake/lws_config.h.in`
50as well so user builds can understand if the role is available in the lws build it is
51trying to bind to.
52
53If the role is disabled in cmake, nothing in its directory is built.
54
55### Role ops struct
56
57The role is defined by `struct lws_role_ops` in `lib/roles/private-lib-roles.h`,
58each role instantiates one of these and fills in the appropriate ops
59callbacks to perform its job.  By convention that lives in
60`./lib/roles/**role name**/ops-**role_name**.c`.
61
62### Private role declarations
63
64Truly private declarations for the role can go in the role directory as you like.
65However when the declarations must be accessible to other things in lws build, eg,
66the role adds members to `struct lws` when enabled, they should be in the role
67directory in a file `private-lib-roles-myrole.h`.
68
69Search for "bring in role private declarations" in `./lib/roles/private-lib-roles.h
70and add your private role file there following the style used for the other roles,
71eg,
72
73```
74#if defined(LWS_ROLE_WS)
75 #include "roles/ws/private-lib-roles-ws.h"
76#else
77 #define lwsi_role_ws(wsi) (0)
78#endif
79```
80
81If the role is disabled at cmake, nothing from its private.h should be used anywhere.
82
83### Integrating role assets to lws
84
85If your role needs special storage in lws objects, that's no problem.  But to keep
86things sane, there are some rules.
87
88 - declare a "container struct" in your private.h for everything, eg, the ws role wants
89   to add storage in lws_vhost for enabled extensions, it declares in its private.h
90
91```
92struct lws_vhost_role_ws {
93#if !defined(LWS_WITHOUT_EXTENSIONS)
94	const struct lws_extension *extensions;
95#endif
96};
97```
98
99 - add your role content in one place in the lws struct, protected by `#if defined(LWS_ROLE_**role name**)`,
100   eg, again for LWS_ROLE_WS
101
102```
103	struct lws_vhost {
104
105...
106
107#if defined(LWS_ROLE_WS)
108	struct lws_vhost_role_ws ws;
109#endif
110
111...
112```
113
114### Adding to lws available roles list
115
116Edit the NULL-terminated array `available_roles` at the top of `./lib/core/context.c` to include
117a pointer to your new role's ops struct, following the style already there.
118
119```
120const struct lws_role_ops * available_roles[] = {
121#if defined(LWS_ROLE_H2)
122	&role_ops_h2,
123#endif
124...
125```
126
127This makes lws aware that your role exists, and it can auto-generate some things like
128ALPN lists, and call your role ops callbacks for things like hooking vhost creation.
129
130### Enabling role adoption
131
132The primary way wsi get bound to a specific role is via the lws adoption api
133`lws_adopt_descriptor_vhost()`.  Add flags as necessary in `./include/libwebsockets/lws-adopt.h`
134`enum lws_adoption_type` and follow the existing code in `lws_adopt_descriptor_vhost()`
135to bind a wsi with suitable flags to your role ops.
136
137### Implementation of the role
138
139After that plumbing-in is completed, the role ops you declare are "live" on a wsi
140bound to them via the adoption api.
141
142The core support for wsis in lws has some generic concepts
143
144 - the wsi holds a pointer member `role_ops` that indicates which role ops the
145   wsi is bound to
146
147 - the wsi holds a generic uint32 `wsistate` that contains role flags and wsi state
148
149 - role flags are provided (LWSIFR_CLIENT, LWSIFR_SERVER) to differentiate between
150   client and server connections inside a wsi, along with helpers `lwsi_role_client(wsi)`
151   and `lwsi_role_server(wsi)`.
152
153 - lws provides around 30 generic states for the wsi starting from 'unconnected' through
154   various proxy or tunnel states, to 'established', and then various states shutting
155   down until 'dead socket'.  The states have testable flags and helpers to discover if
156   the wsi state is before establishment `lwsi_state_est(wsi)` and if in the state it is
157   in, it can handle pollout `lwsi_state_can_handle_POLLOUT(wsi)`.
158
159 - You set the initial binding, role flags and state using `lws_role_transition()`.  Afterwards
160   you can adjust the state using `lwsi_set_state()`.
161
162### Role ops compression
163
164Since the role ops struct is typically only sparsely filled, rather than have 20 function
165pointers most of which may be NULL, there is a separate array of a union of function
166pointers that is just long enough for functions that exist in the role, and a nybble index
167table with a nybble for each possible op, either 0 indicating that the operation is not
168provided in this role, or 1 - 15 indicating the position of the function pointer in the
169array.
170
171