1
+#include "aclk_lws_wss_client.h"
2
+
3
+#include "libnetdata/libnetdata.h"
4
+
5
+static int aclk_lws_wss_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
6
+
7
+struct aclk_lws_wss_perconnect_data {
8
+ int todo;
9
+};
10
+
11
+struct lws_wss_packet_buffer {
12
+ unsigned char* data;
13
+ size_t data_size;
14
+ struct lws_wss_packet_buffer *next;
15
+};
16
+
17
+static inline struct lws_wss_packet_buffer *lws_wss_packet_buffer_new(void* data, size_t size)
18
+{
19
+ struct lws_wss_packet_buffer *new = callocz(1, sizeof(struct lws_wss_packet_buffer));
20
+ if(data) {
21
+ new->data = mallocz(LWS_PRE+size);
22
+ memcpy(new->data+LWS_PRE, data, size);
23
+ new->data_size = size;
24
+ }
25
+ return new;
26
+}
27
+
28
+static inline void lws_wss_packet_buffer_append(struct lws_wss_packet_buffer **list, struct lws_wss_packet_buffer *item)
29
+{
30
+ struct lws_wss_packet_buffer *tail = *list;
31
+ if(!*list) {
32
+ *list = item;
33
+ return;
34
+ }
35
+ while(tail->next) {
36
+ tail = tail->next;
37
+ }
38
+ tail->next = item;
39
+}
40
+
41
+static inline struct lws_wss_packet_buffer *lws_wss_packet_buffer_pop(struct lws_wss_packet_buffer **list)
42
+{
43
+ struct lws_wss_packet_buffer *ret = *list;
44
+ if(ret != NULL)
45
+ *list = ret->next;
46
+
47
+ return ret;
48
+}
49
+
50
+static inline void lws_wss_packet_buffer_free(struct lws_wss_packet_buffer *item)
51
+{
52
+ freez(item->data);
53
+ freez(item);
54
+}
55
+
56
+static inline void _aclk_lws_wss_read_buffer_clear(struct lws_ring *ringbuffer)
57
+{
58
+ size_t elems = lws_ring_get_count_waiting_elements(ringbuffer, NULL);
59
+ if(elems > 0)
60
+ lws_ring_consume(ringbuffer, NULL, NULL, elems);
61
+}
62
+
63
+static inline void _aclk_lws_wss_write_buffer_clear(struct lws_wss_packet_buffer **list)
64
+{
65
+ struct lws_wss_packet_buffer *i;
66
+ while((i = lws_wss_packet_buffer_pop(list)) != NULL) {
67
+ lws_wss_packet_buffer_free(i);
68
+ }
69
+ *list = NULL;
70
+}
71
+
72
+static inline void aclk_lws_wss_clear_io_buffers(struct aclk_lws_wss_engine_instance *inst)
73
+{
74
+ aclk_lws_mutex_lock(&inst->read_buf_mutex);
75
+ _aclk_lws_wss_read_buffer_clear(inst->read_ringbuffer);
76
+ aclk_lws_mutex_unlock(&inst->read_buf_mutex);
77
+ aclk_lws_mutex_lock(&inst->write_buf_mutex);
78
+ _aclk_lws_wss_write_buffer_clear(&inst->write_buffer_head);
79
+ aclk_lws_mutex_unlock(&inst->write_buf_mutex);
80
+}
81
+
82
+static const struct lws_protocols protocols[] = {
83
+ {
84
+ "aclk-wss",
85
+ aclk_lws_wss_callback,
86
+ sizeof(struct aclk_lws_wss_perconnect_data),
87
+ 0, 0, 0, 0
88
+ },
89
+ { NULL, NULL, 0, 0, 0, 0, 0 }
90
+};
91
+
92
+static void aclk_lws_wss_log_divert(int level, const char *line) {
93
+ switch(level){
94
+ case LLL_ERR:
95
+ error("Libwebsockets Error: %s", line);
96
+ break;
97
+ case LLL_WARN:
98
+ debug(D_ACLK, "Libwebsockets Warn: %s", line);
99
+ break;
100
+ default:
101
+ error("Libwebsockets try to log with unknown log level (%d), msg: %s", level, line);
102
+ }
103
+}
104
+
105
+struct aclk_lws_wss_engine_instance* aclk_lws_wss_client_init (const struct aclk_lws_wss_engine_callbacks *callbacks, const char *target_hostname, int target_port) {
106
+ static int lws_logging_initialized = 0;
107
+ struct lws_context_creation_info info;
108
+ struct aclk_lws_wss_engine_instance *inst;
109
+
110
+ if(unlikely(!lws_logging_initialized)) {
111
+ lws_set_log_level(LLL_ERR | LLL_WARN, aclk_lws_wss_log_divert);
112
+ lws_logging_initialized = 1;
113
+ }
114
+
115
+ if(!callbacks || !target_hostname)
116
+ return NULL;
117
+
118
+ inst = callocz(1, sizeof(struct aclk_lws_wss_engine_instance));
119
+
120
+ inst->host = target_hostname;
121
+ inst->port = target_port;
122
+
123
+ memset(&info, 0, sizeof(struct lws_context_creation_info));
124
+ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
125
+ info.port = CONTEXT_PORT_NO_LISTEN;
126
+ info.protocols = protocols;
127
+ info.user = inst;
128
+
129
+ inst->lws_context = lws_create_context(&info);
130
+ if(!inst->lws_context)
131
+ goto failure_cleanup_2;
132
+
133
+ inst->callbacks = *callbacks;
134
+
135
+ aclk_lws_mutex_init(&inst->write_buf_mutex);
136
+ aclk_lws_mutex_init(&inst->read_buf_mutex);
137
+
138
+ inst->read_ringbuffer = lws_ring_create(1, ACLK_LWS_WSS_RECV_BUFF_SIZE_BYTES, NULL);
139
+ if(!inst->read_ringbuffer)
140
+ goto failure_cleanup;
141
+
142
+ return inst;
143
+
144
+failure_cleanup:
145
+ lws_context_destroy(inst->lws_context);
146
+failure_cleanup_2:
147
+ freez(inst);
148
+ return NULL;
149
+}
150
+
151
+void aclk_lws_wss_client_destroy(struct aclk_lws_wss_engine_instance* inst) {
152
+ lws_context_destroy(inst->lws_context);
153
+ inst->lws_context = NULL;
154
+ inst->lws_wsi = NULL;
155
+
156
+ aclk_lws_wss_clear_io_buffers(inst);
157
+
158
+#ifdef ACLK_LWS_MOSQUITTO_IO_CALLS_MULTITHREADED
159
+ pthread_mutex_destroy(&inst->write_buf_mutex);
160
+ pthread_mutex_destroy(&inst->read_buf_mutex);
161
+#endif
162
+}
163
+
164
+void _aclk_wss_connect(struct aclk_lws_wss_engine_instance *inst){
165
+ struct lws_client_connect_info i;
166
+
167
+ memset(&i, 0, sizeof(i));
168
+ i.context = inst->lws_context;
169
+ i.port = inst->port;
170
+ i.address = inst->host;
171
+ i.path = "/mqtt";
172
+ i.host = inst->host;
173
+ i.protocol = "mqtt";
174
+#ifdef ACLK_SSL_ALLOW_SELF_SIGNED
175
+ i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
176
+#else
177
+ i.ssl_connection = LCCSCF_USE_SSL;
178
+#endif
179
+ lws_client_connect_via_info(&i);
180
+}
181
+
182
+static inline int received_data_to_ringbuff(struct lws_ring *buffer, void* data, size_t len) {
183
+ if( lws_ring_insert(buffer, data, len) != len ) {
184
+ error("ACLK_LWS_WSS_CLIENT: receive buffer full. Closing connection to prevent flooding.");
185
+ return 0;
186
+ }
187
+ return 1;
188
+}
189
+
190
+static int
191
+aclk_lws_wss_callback(struct lws *wsi, enum lws_callback_reasons reason,
192
+ void *user, void *in, size_t len)
193
+{
194
+ UNUSED(user);
195
+ struct aclk_lws_wss_engine_instance *inst = lws_context_user(lws_get_context(wsi));
196
+ struct lws_wss_packet_buffer *data;
197
+ int retval = 0;
198
+
199
+ if( !inst ) {
200
+ error("Callback received without any aclk_lws_wss_engine_instance!");
201
+ return -1;
202
+ }
203
+
204
+ if( inst->upstream_reconnect_request ) {
205
+ error("Closing lws connectino due to libmosquitto error.");
206
+ char *upstream_connection_error = "MQTT protocol error. Closing underlying wss connection.";
207
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (unsigned char*)upstream_connection_error, strlen(upstream_connection_error));
208
+ retval = -1;
209
+ inst->upstream_reconnect_request = 0;
210
+ }
211
+
212
+ switch (reason) {
213
+ case LWS_CALLBACK_CLIENT_WRITEABLE:
214
+ aclk_lws_mutex_lock(&inst->write_buf_mutex);
215
+ data = lws_wss_packet_buffer_pop(&inst->write_buffer_head);
216
+ if(likely(data)) {
217
+ lws_write(wsi, data->data + LWS_PRE, data->data_size, LWS_WRITE_BINARY);
218
+ lws_wss_packet_buffer_free(data);
219
+ if(inst->write_buffer_head)
220
+ lws_callback_on_writable(inst->lws_wsi);
221
+ }
222
+ aclk_lws_mutex_unlock(&inst->write_buf_mutex);
223
+ break;
224
+ case LWS_CALLBACK_CLIENT_RECEIVE:
225
+ aclk_lws_mutex_lock(&inst->read_buf_mutex);
226
+ if(!received_data_to_ringbuff(inst->read_ringbuffer, in, len))
227
+ retval = 1;
228
+ aclk_lws_mutex_unlock(&inst->read_buf_mutex);
229
+
230
+ if(likely(inst->callbacks.data_rcvd_callback))
231
+ // to future myself -> do not call this while read lock is active as it will eventually
232
+ // want to acquire same lock later in aclk_lws_wss_client_read() function
233
+ inst->callbacks.data_rcvd_callback();
234
+ else
235
+ inst->data_to_read = 1; //to inform logic above there is reason to call mosquitto_loop_read
236
+ break;
237
+ case LWS_CALLBACK_PROTOCOL_INIT:
238
+ //initial connection here
239
+ //later we will reconnect with delay od ACLK_LWS_WSS_RECONNECT_TIMEOUT
240
+ //in case this connection fails or drops
241
+ _aclk_wss_connect(inst);
242
+ break;
243
+ case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
244
+ //TODO if already active make some error noise
245
+ //currently we expect only one connection per netdata
246
+ inst->lws_wsi = wsi;
247
+ break;
248
+#ifdef AUTO_RECONNECT_ON_LWS_LAYER
249
+ case LWS_CALLBACK_USER:
250
+ inst->reconnect_timeout_running = 0;
251
+ _aclk_wss_connect(inst);
252
+ break;
253
+#endif
254
+ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
255
+ error("Could not connect MQTT over WSS server \"%s:%d\". LwsReason:\"%s\"", inst->host, inst->port, (in ? (char*)in : "not given"));
256
+ /* FALLTHRU */
257
+ case LWS_CALLBACK_CLIENT_CLOSED:
258
+ case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
259
+#ifdef AUTO_RECONNECT_ON_LWS_LAYER
260
+ if(!inst->reconnect_timeout_running) {
261
+ lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
262
+ lws_get_protocol(wsi),
263
+ LWS_CALLBACK_USER, ACLK_LWS_WSS_RECONNECT_TIMEOUT);
264
+ inst->reconnect_timeout_running = 1;
265
+ }
266
+ /* FALLTHRU */
267
+#endif
268
+ //no break here on purpose we want to continue with LWS_CALLBACK_WSI_DESTROY
269
+ case LWS_CALLBACK_WSI_DESTROY:
270
+ aclk_lws_wss_clear_io_buffers(inst);
271
+ inst->lws_wsi = NULL;
272
+ inst->websocket_connection_up = 0;
273
+ break;
274
+ case LWS_CALLBACK_CLIENT_ESTABLISHED:
275
+ inst->websocket_connection_up = 1;
276
+ if(inst->callbacks.connection_established_callback)
277
+ inst->callbacks.connection_established_callback();
278
+ break;
279
+ default:
280
+ break;
281
+ }
282
+ return retval; //0-OK, other connection should be closed!
283
+}
284
+
285
+int aclk_lws_wss_client_write(struct aclk_lws_wss_engine_instance *inst, void *buf, size_t count)
286
+{
287
+ if(inst && inst->lws_wsi && inst->websocket_connection_up)
288
+ {
289
+ aclk_lws_mutex_lock(&inst->write_buf_mutex);
290
+ lws_wss_packet_buffer_append(&inst->write_buffer_head, lws_wss_packet_buffer_new(buf, count));
291
+ aclk_lws_mutex_unlock(&inst->write_buf_mutex);
292
+
293
+ lws_callback_on_writable(inst->lws_wsi);
294
+ return count;
295
+ }
296
+ return 0;
297
+}
298
+
299
+int aclk_lws_wss_client_read(struct aclk_lws_wss_engine_instance *inst, void *buf, size_t count)
300
+{
301
+ size_t data_to_be_read = count;
302
+
303
+ aclk_lws_mutex_lock(&inst->read_buf_mutex);
304
+ size_t readable_byte_count = lws_ring_get_count_waiting_elements(inst->read_ringbuffer, NULL);
305
+ if(unlikely(readable_byte_count == 0)) {
306
+ errno = EAGAIN;
307
+ data_to_be_read = -1;
308
+ goto abort;
309
+ }
310
+
311
+ if( readable_byte_count < data_to_be_read )
312
+ data_to_be_read = readable_byte_count;
313
+
314
+ data_to_be_read = lws_ring_consume(inst->read_ringbuffer, NULL, buf, data_to_be_read);
315
+ if(data_to_be_read == readable_byte_count)
316
+ inst->data_to_read = 0;
317
+
318
+abort:
319
+ aclk_lws_mutex_unlock(&inst->read_buf_mutex);
320
+ return data_to_be_read;
321
+}
322
+
323
+int aclk_lws_wss_service_loop(struct aclk_lws_wss_engine_instance *inst)
324
+{
325
+ return lws_service(inst->lws_context, 0);
326
+}
327
+
328
+// in case the MQTT connection disconnect while lws transport is still operational
329
+// we should drop connection and reconnect
330
+// this function should be called when that happens to notify lws of that situation
331
+void aclk_lws_wss_mqtt_layer_disconect_notif(struct aclk_lws_wss_engine_instance *inst)
332
+{
333
+ if(inst->lws_wsi && inst->websocket_connection_up) {
334
+ inst->upstream_reconnect_request = 1;
335
+ lws_callback_on_writable(inst->lws_wsi); //here we just do it to ensure we get callback called from lws, we don't need any actual data to be written.
336
+ }
337
+}
\ No newline at end of file