Line | Count | Source |
1 | | #include "event2/event-config.h" |
2 | | #include "evconfig-private.h" |
3 | | |
4 | | #include "event2/buffer.h" |
5 | | #include "event2/bufferevent.h" |
6 | | #include "event2/event.h" |
7 | | #include "event2/http.h" |
8 | | #include "event2/ws.h" |
9 | | #include "util-internal.h" |
10 | | #include "mm-internal.h" |
11 | | #include "sha1.h" |
12 | | #include "event2/bufferevent.h" |
13 | | #include "sys/queue.h" |
14 | | #include "http-internal.h" |
15 | | #include "bufferevent-internal.h" |
16 | | |
17 | | #include <assert.h> |
18 | | #include <inttypes.h> |
19 | | #include <string.h> |
20 | | #include <stdbool.h> |
21 | | |
22 | | #ifndef _WIN32 |
23 | | #include <sys/socket.h> |
24 | | #include <sys/stat.h> |
25 | | #else /* _WIN32 */ |
26 | | #include <winsock2.h> |
27 | | #include <ws2tcpip.h> |
28 | | #endif /* _WIN32 */ |
29 | | |
30 | | #ifdef EVENT__HAVE_ARPA_INET_H |
31 | | #include <arpa/inet.h> |
32 | | #endif |
33 | | #ifdef EVENT__HAVE_NETINET_IN_H |
34 | | #include <netinet/in.h> |
35 | | #endif |
36 | | #ifdef EVENT__HAVE_NETINET_IN6_H |
37 | | #include <netinet/in6.h> |
38 | | #endif |
39 | | |
40 | 1.01k | #define WS_UUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
41 | | /* |
42 | | * We limit the size of received WS frames to 10 MiB, |
43 | | * as a DoS prevention measure. |
44 | | */ |
45 | | static const size_t WS_MAX_RECV_FRAME_SZ = 10485760; |
46 | | /* |
47 | | * We also limit the total size of a fragmented message to 10 MiB so that |
48 | | * clients cannot bypass the per-frame cap by streaming unbounded fragments. |
49 | | */ |
50 | | static const size_t WS_MAX_RECV_MSG_SZ = 10485760; |
51 | | |
52 | | struct evws_connection { |
53 | | TAILQ_ENTRY(evws_connection) next; |
54 | | |
55 | | struct bufferevent *bufev; |
56 | | |
57 | | ws_on_msg_cb cb; |
58 | | void *cb_arg; |
59 | | |
60 | | ws_on_close_cb cbclose; |
61 | | void *cbclose_arg; |
62 | | |
63 | | /* for server connections, the http server they are connected with */ |
64 | | struct evhttp *http_server; |
65 | | |
66 | | struct evbuffer *incomplete_frames; |
67 | | bool closed; |
68 | | }; |
69 | | |
70 | | enum WebSocketFrameType { |
71 | | ERROR_FRAME = 0xFF, |
72 | | INCOMPLETE_DATA = 0xFE, |
73 | | |
74 | | CLOSING_FRAME = 0x8, |
75 | | |
76 | | INCOMPLETE_FRAME = 0x81, |
77 | | |
78 | | TEXT_FRAME = 0x1, |
79 | | BINARY_FRAME = 0x2, |
80 | | |
81 | | PING_FRAME = 0x9, |
82 | | PONG_FRAME = 0xA |
83 | | }; |
84 | | |
85 | | |
86 | | static void evws_send(struct evws_connection *evws, |
87 | | enum WebSocketFrameType frame_type, const char *packet_str, size_t str_len); |
88 | | |
89 | | /* |
90 | | * Clean up a WebSockets connection object |
91 | | */ |
92 | | |
93 | | void |
94 | | evws_connection_free(struct evws_connection *evws) |
95 | 1.01k | { |
96 | | /* notify interested parties that this connection is going down */ |
97 | 1.01k | if (evws->cbclose != NULL) |
98 | 0 | (*evws->cbclose)(evws, evws->cbclose_arg); |
99 | | |
100 | 1.01k | if (evws->http_server != NULL) { |
101 | 1.01k | struct evhttp *http = evws->http_server; |
102 | 1.01k | TAILQ_REMOVE(&http->ws_sessions, evws, next); |
103 | 1.01k | http->connection_cnt--; |
104 | 1.01k | } |
105 | | |
106 | 1.01k | if (evws->bufev != NULL) { |
107 | 1.01k | bufferevent_free(evws->bufev); |
108 | 1.01k | } |
109 | 1.01k | if (evws->incomplete_frames != NULL) { |
110 | 515 | evbuffer_free(evws->incomplete_frames); |
111 | 515 | } |
112 | | |
113 | 1.01k | mm_free(evws); |
114 | 1.01k | } |
115 | | |
116 | | static const char basis_64[] = |
117 | | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
118 | | |
119 | | static int |
120 | | Base64encode(char *encoded, const char *string, int len) |
121 | 1.01k | { |
122 | 1.01k | int i; |
123 | 1.01k | char *p; |
124 | | |
125 | 1.01k | p = encoded; |
126 | 7.11k | for (i = 0; i < len - 2; i += 3) { |
127 | 6.10k | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
128 | 6.10k | *p++ = basis_64[((string[i] & 0x3) << 4) | |
129 | 6.10k | ((int)(string[i + 1] & 0xF0) >> 4)]; |
130 | 6.10k | *p++ = basis_64[((string[i + 1] & 0xF) << 2) | |
131 | 6.10k | ((int)(string[i + 2] & 0xC0) >> 6)]; |
132 | 6.10k | *p++ = basis_64[string[i + 2] & 0x3F]; |
133 | 6.10k | } |
134 | 1.01k | if (i < len) { |
135 | 1.01k | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
136 | 1.01k | if (i == (len - 1)) { |
137 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4)]; |
138 | 0 | *p++ = '='; |
139 | 1.01k | } else { |
140 | 1.01k | *p++ = basis_64[((string[i] & 0x3) << 4) | |
141 | 1.01k | ((int)(string[i + 1] & 0xF0) >> 4)]; |
142 | 1.01k | *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; |
143 | 1.01k | } |
144 | 1.01k | *p++ = '='; |
145 | 1.01k | } |
146 | | |
147 | 1.01k | *p++ = '\0'; |
148 | 1.01k | return p - encoded; |
149 | 1.01k | } |
150 | | |
151 | | static char * |
152 | | ws_gen_accept_key(const char *ws_key, char out[32]) |
153 | 1.01k | { |
154 | 1.01k | char buf[1024]; |
155 | 1.01k | char digest[20]; |
156 | | |
157 | 1.01k | snprintf(buf, sizeof(buf), "%s" WS_UUID, ws_key); |
158 | | |
159 | 1.01k | builtin_SHA1(digest, buf, strlen(buf)); |
160 | 1.01k | Base64encode(out, digest, sizeof(digest)); |
161 | 1.01k | return out; |
162 | 1.01k | } |
163 | | |
164 | | static void |
165 | | close_after_write_cb(struct bufferevent *bev, void *ctx) |
166 | 0 | { |
167 | 0 | if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { |
168 | 0 | evws_connection_free(ctx); |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | static void |
173 | | close_event_cb(struct bufferevent *bev, short what, void *ctx) |
174 | 0 | { |
175 | 0 | evws_connection_free(ctx); |
176 | 0 | } |
177 | | |
178 | | void |
179 | | evws_close(struct evws_connection *evws, uint16_t reason) |
180 | 196k | { |
181 | 196k | uint8_t fr[4] = {0x8 | 0x80, 2, 0}; |
182 | 196k | struct evbuffer *output; |
183 | 196k | uint16_t *u16; |
184 | | |
185 | 196k | if (evws->closed) |
186 | 195k | return; |
187 | 582 | evws->closed = true; |
188 | | |
189 | 582 | u16 = (uint16_t *)&fr[2]; |
190 | 582 | *u16 = htons((int16_t)reason); |
191 | 582 | output = bufferevent_get_output(evws->bufev); |
192 | 582 | evbuffer_add(output, fr, 4); |
193 | | |
194 | | /* wait for close frame writing complete and close connection */ |
195 | 582 | bufferevent_setcb( |
196 | 582 | evws->bufev, NULL, close_after_write_cb, close_event_cb, evws); |
197 | 582 | } |
198 | | |
199 | | static void |
200 | | evws_force_disconnect_(struct evws_connection *evws) |
201 | 196k | { |
202 | 196k | evws_close(evws, WS_CR_NONE); |
203 | 196k | } |
204 | | |
205 | | static int |
206 | | ws_message_limit_exceeded_(struct evws_connection *evws, size_t msg_len) |
207 | 3.32M | { |
208 | 3.32M | size_t buffered = 0; |
209 | | |
210 | 3.32M | if (evws->incomplete_frames != NULL) { |
211 | 3.32M | buffered = evbuffer_get_length(evws->incomplete_frames); |
212 | 3.32M | } |
213 | | |
214 | 3.32M | if (msg_len > WS_MAX_RECV_MSG_SZ - buffered) { |
215 | 0 | evws_close(evws, WS_CR_DATA_TOO_BIG); |
216 | 0 | return 1; |
217 | 0 | } |
218 | | |
219 | 3.32M | return 0; |
220 | 3.32M | } |
221 | | |
222 | | /* parse base frame according to |
223 | | * https://www.rfc-editor.org/rfc/rfc6455#section-5.2 |
224 | | */ |
225 | | static enum WebSocketFrameType |
226 | | get_ws_frame(unsigned char *in_buffer, size_t buf_len, |
227 | | unsigned char **payload_ptr, size_t *out_len) |
228 | 3.59M | { |
229 | 3.59M | unsigned char opcode; |
230 | 3.59M | unsigned char fin; |
231 | 3.59M | unsigned char masked; |
232 | 3.59M | size_t payload_len; |
233 | 3.59M | size_t pos; |
234 | 3.59M | int length_field; |
235 | | |
236 | 3.59M | if (buf_len < 2) { |
237 | 227 | return INCOMPLETE_DATA; |
238 | 227 | } |
239 | | |
240 | 3.59M | opcode = in_buffer[0] & 0x0F; |
241 | 3.59M | fin = (in_buffer[0] >> 7) & 0x01; |
242 | 3.59M | masked = (in_buffer[1] >> 7) & 0x01; |
243 | | |
244 | 3.59M | payload_len = 0; |
245 | 3.59M | pos = 2; |
246 | 3.59M | length_field = in_buffer[1] & (~0x80); |
247 | | |
248 | 3.59M | if (length_field <= 125) { |
249 | 3.54M | payload_len = length_field; |
250 | 3.54M | } else if (length_field == 126) { /* msglen is 16bit */ |
251 | 5.47k | uint16_t tmp16; |
252 | 5.47k | if (buf_len < 4) |
253 | 2 | return INCOMPLETE_DATA; |
254 | 5.47k | memcpy(&tmp16, in_buffer + pos, 2); |
255 | 5.47k | payload_len = ntohs(tmp16); |
256 | 5.47k | pos += 2; |
257 | 38.5k | } else if (length_field == 127) { /* msglen is 64bit */ |
258 | 38.5k | int i; |
259 | 38.5k | uint64_t tmp64 = 0; |
260 | 38.5k | if (buf_len < 10) |
261 | 13 | return INCOMPLETE_DATA; |
262 | | /* swap bytes from big endian to host byte order */ |
263 | 347k | for (i = 56; i >= 0; i -= 8) { |
264 | 308k | tmp64 |= (uint64_t)in_buffer[pos++] << i; |
265 | 308k | } |
266 | 38.5k | if (tmp64 > WS_MAX_RECV_FRAME_SZ) { |
267 | | /* Implementation limitation, we support up to 10 MiB |
268 | | * length, as a DoS prevention measure. |
269 | | */ |
270 | 36.5k | event_warn("%s: frame length %" PRIu64 " exceeds %" PRIu64 "\n", |
271 | 36.5k | __func__, tmp64, (uint64_t)WS_MAX_RECV_FRAME_SZ); |
272 | | /* Calling code needs these values; do the best we can here. |
273 | | * Caller will close the connection anyway. |
274 | | */ |
275 | 36.5k | *payload_ptr = in_buffer + pos; |
276 | 36.5k | *out_len = 0; |
277 | 36.5k | return ERROR_FRAME; |
278 | 36.5k | } |
279 | 2.05k | payload_len = (size_t)tmp64; |
280 | 2.05k | } |
281 | 3.55M | if (buf_len < payload_len + pos + (masked ? 4u : 0u)) { |
282 | 275 | return INCOMPLETE_DATA; |
283 | 275 | } |
284 | | |
285 | | /* According to RFC it seems that unmasked data should be prohibited |
286 | | * but we support it for nonconformant clients |
287 | | */ |
288 | 3.55M | if (masked) { |
289 | 62.3k | unsigned char *c, *mask; |
290 | 62.3k | size_t i; |
291 | | |
292 | 62.3k | mask = in_buffer + pos; /* first 4 bytes are mask bytes */ |
293 | 62.3k | pos += 4; |
294 | | |
295 | | /* unmask data */ |
296 | 62.3k | c = in_buffer + pos; |
297 | 24.9M | for (i = 0; i < payload_len; i++) { |
298 | 24.8M | c[i] = c[i] ^ mask[i % 4u]; |
299 | 24.8M | } |
300 | 62.3k | } |
301 | | |
302 | 3.55M | *payload_ptr = in_buffer + pos; |
303 | 3.55M | *out_len = payload_len; |
304 | | |
305 | | /* are reserved for further frames */ |
306 | 3.55M | if ((opcode >= 3 && opcode <= 7) || (opcode >= 0xb)) |
307 | 113k | return ERROR_FRAME; |
308 | | |
309 | 3.44M | if (opcode <= 0x3 && !fin) { |
310 | 3.30M | return INCOMPLETE_FRAME; |
311 | 3.30M | } |
312 | 135k | return opcode; |
313 | 3.44M | } |
314 | | |
315 | | |
316 | | static void |
317 | | ws_evhttp_read_cb(struct bufferevent *bufev, void *arg) |
318 | 1.01k | { |
319 | 1.01k | struct evws_connection *evws = arg; |
320 | 1.01k | unsigned char *payload; |
321 | 1.01k | enum WebSocketFrameType type; |
322 | 1.01k | size_t msg_len, in_len, header_sz; |
323 | 1.01k | struct evbuffer *input = bufferevent_get_input(evws->bufev); |
324 | | |
325 | 1.01k | bufferevent_incref_and_lock_(evws->bufev); |
326 | 3.59M | while ((in_len = evbuffer_get_length(input))) { |
327 | 3.59M | unsigned char *data = evbuffer_pullup(input, in_len); |
328 | 3.59M | if (data == NULL) { |
329 | 0 | goto bailout; |
330 | 0 | } |
331 | | |
332 | 3.59M | type = get_ws_frame(data, in_len, &payload, &msg_len); |
333 | 3.59M | if (type == INCOMPLETE_DATA) { |
334 | | /* incomplete data received, wait for next chunk */ |
335 | 517 | goto bailout; |
336 | 517 | } |
337 | 3.59M | header_sz = payload - data; |
338 | 3.59M | evbuffer_drain(input, header_sz); |
339 | 3.59M | data = evbuffer_pullup(input, -1); |
340 | | |
341 | 3.59M | switch (type) { |
342 | 19.4k | case TEXT_FRAME: |
343 | 25.9k | case BINARY_FRAME: |
344 | 25.9k | if (evws->incomplete_frames != NULL) { |
345 | 19.3k | if (ws_message_limit_exceeded_(evws, msg_len)) { |
346 | 0 | break; |
347 | 0 | } |
348 | | /* we already have incomplete frames in internal buffer |
349 | | * and need to concatenate them with final one */ |
350 | 19.3k | evbuffer_add(evws->incomplete_frames, data, msg_len); |
351 | | |
352 | 19.3k | data = evbuffer_pullup(evws->incomplete_frames, -1); |
353 | | |
354 | 19.3k | evws->cb(evws, type, data, |
355 | 19.3k | evbuffer_get_length(evws->incomplete_frames), evws->cb_arg); |
356 | 19.3k | evbuffer_free(evws->incomplete_frames); |
357 | 19.3k | evws->incomplete_frames = NULL; |
358 | 19.3k | } else { |
359 | 6.62k | evws->cb(evws, type, data, msg_len, evws->cb_arg); |
360 | 6.62k | } |
361 | 25.9k | break; |
362 | 3.30M | case INCOMPLETE_FRAME: |
363 | | /* we received full frame until get fin and need to |
364 | | * postpone callback until all data arrives */ |
365 | 3.30M | if (evws->incomplete_frames == NULL) { |
366 | 19.8k | evws->incomplete_frames = evbuffer_new(); |
367 | 19.8k | } |
368 | 3.30M | if (evws->incomplete_frames == NULL) { |
369 | 0 | evws_force_disconnect_(evws); |
370 | 0 | break; |
371 | 0 | } |
372 | 3.30M | if (ws_message_limit_exceeded_(evws, msg_len)) { |
373 | 0 | break; |
374 | 0 | } |
375 | 3.30M | evbuffer_remove_buffer(input, evws->incomplete_frames, msg_len); |
376 | 3.30M | continue; |
377 | 40.7k | case CLOSING_FRAME: |
378 | 190k | case ERROR_FRAME: |
379 | 190k | evws_force_disconnect_(evws); |
380 | 190k | break; |
381 | 29.5k | case PING_FRAME: |
382 | 63.7k | case PONG_FRAME: |
383 | | /* ping or pong frame */ |
384 | 63.7k | break; |
385 | 5.55k | default: |
386 | 5.55k | event_warn("%s: unexpected frame type %d\n", __func__, type); |
387 | 5.55k | evws_force_disconnect_(evws); |
388 | 3.59M | } |
389 | 285k | evbuffer_drain(input, msg_len); |
390 | 285k | } |
391 | | |
392 | 1.01k | bailout: |
393 | 1.01k | bufferevent_decref_and_unlock_(evws->bufev); |
394 | 1.01k | } |
395 | | |
396 | | static void |
397 | | ws_evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) |
398 | 0 | { |
399 | | /* when client just disappears after connection (wscat closed by Cmd+Q) */ |
400 | 0 | if (what & BEV_EVENT_EOF) { |
401 | 0 | close_after_write_cb(bufev, arg); |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | struct evws_connection * |
406 | | evws_new_session( |
407 | | struct evhttp_request *req, ws_on_msg_cb cb, void *arg, int options) |
408 | 1.01k | { |
409 | 1.01k | struct evws_connection *evws = NULL; |
410 | 1.01k | struct evkeyvalq *in_hdrs; |
411 | 1.01k | const char *upgrade, *connection, *ws_key, *ws_protocol; |
412 | 1.01k | struct evkeyvalq *out_hdrs; |
413 | 1.01k | struct evhttp_connection *evcon; |
414 | 1.01k | struct evhttp *ws_http_server; |
415 | 1.01k | int req_owned = 1; |
416 | | |
417 | 1.01k | in_hdrs = evhttp_request_get_input_headers(req); |
418 | 1.01k | upgrade = evhttp_find_header(in_hdrs, "Upgrade"); |
419 | 1.01k | if (upgrade == NULL || evutil_ascii_strcasecmp(upgrade, "websocket")) |
420 | 0 | goto error; |
421 | | |
422 | 1.01k | connection = evhttp_find_header(in_hdrs, "Connection"); |
423 | 1.01k | if (connection == NULL || evutil_ascii_strcasestr(connection, "Upgrade") == NULL) |
424 | 0 | goto error; |
425 | | |
426 | 1.01k | ws_key = evhttp_find_header(in_hdrs, "Sec-WebSocket-Key"); |
427 | 1.01k | if (ws_key == NULL) |
428 | 0 | goto error; |
429 | | |
430 | 1.01k | out_hdrs = evhttp_request_get_output_headers(req); |
431 | 1.01k | evhttp_add_header(out_hdrs, "Upgrade", "websocket"); |
432 | 1.01k | evhttp_add_header(out_hdrs, "Connection", "Upgrade"); |
433 | | |
434 | 1.01k | evhttp_add_header(out_hdrs, "Sec-WebSocket-Accept", |
435 | 1.01k | ws_gen_accept_key(ws_key, (char[32]){0})); |
436 | | |
437 | 1.01k | ws_protocol = evhttp_find_header(in_hdrs, "Sec-WebSocket-Protocol"); |
438 | 1.01k | if (ws_protocol != NULL) |
439 | 0 | evhttp_add_header(out_hdrs, "Sec-WebSocket-Protocol", ws_protocol); |
440 | | |
441 | 1.01k | if ((evws = mm_calloc(1, sizeof(struct evws_connection))) == NULL) { |
442 | 0 | event_warn("%s: calloc failed", __func__); |
443 | 0 | goto error; |
444 | 0 | } |
445 | | |
446 | 1.01k | evws->cb = cb; |
447 | 1.01k | evws->cb_arg = arg; |
448 | | |
449 | 1.01k | evcon = evhttp_request_get_connection(req); |
450 | 1.01k | ws_http_server = evcon->http_server; |
451 | | |
452 | | /* evhttp_start_ws_ frees both req and evcon on success */ |
453 | 1.01k | evws->bufev = evhttp_start_ws_(req); |
454 | 1.01k | if (evws->bufev == NULL) { |
455 | 0 | goto error; |
456 | 0 | } |
457 | 1.01k | req_owned = 0; |
458 | | |
459 | 1.01k | if (options & BEV_OPT_THREADSAFE) { |
460 | 0 | if (bufferevent_enable_locking_(evws->bufev, NULL) < 0) |
461 | 0 | goto error; |
462 | 0 | } |
463 | | |
464 | 1.01k | bufferevent_setcb( |
465 | 1.01k | evws->bufev, ws_evhttp_read_cb, NULL, ws_evhttp_error_cb, evws); |
466 | | |
467 | 1.01k | evws->http_server = ws_http_server; |
468 | 1.01k | TAILQ_INSERT_TAIL(&evws->http_server->ws_sessions, evws, next); |
469 | 1.01k | evws->http_server->connection_cnt++; |
470 | | |
471 | 1.01k | return evws; |
472 | | |
473 | 0 | error: |
474 | 0 | if (evws) |
475 | 0 | evws_connection_free(evws); |
476 | |
|
477 | 0 | if (req_owned) |
478 | 0 | evhttp_send_reply(req, HTTP_BADREQUEST, NULL, NULL); |
479 | 0 | return NULL; |
480 | 1.01k | } |
481 | | |
482 | | static void |
483 | | make_ws_frame(struct evbuffer *output, enum WebSocketFrameType frame_type, |
484 | | unsigned char *msg, size_t len) |
485 | 0 | { |
486 | 0 | size_t pos = 0; |
487 | 0 | unsigned char header[16] = {0}; |
488 | |
|
489 | 0 | header[pos++] = (unsigned char)frame_type | 0x80; /* fin */ |
490 | 0 | if (len <= 125) { |
491 | 0 | header[pos++] = len; |
492 | 0 | } else if (len <= 65535) { |
493 | 0 | header[pos++] = 126; /* 16 bit length */ |
494 | 0 | header[pos++] = (len >> 8) & 0xFF; /* rightmost first */ |
495 | 0 | header[pos++] = len & 0xFF; |
496 | 0 | } else { /* >2^16-1 */ |
497 | 0 | int i; |
498 | 0 | const uint64_t tmp64 = len; |
499 | 0 | header[pos++] = 127; /* 64 bit length */ |
500 | | /* swap bytes from host byte order to big endian */ |
501 | 0 | for (i = 56; i >= 0; i -= 8) { |
502 | 0 | header[pos++] = tmp64 >> i & 0xFFu; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | evbuffer_add(output, header, pos); |
506 | 0 | evbuffer_add(output, msg, len); |
507 | 0 | } |
508 | | |
509 | | static void |
510 | | evws_send(struct evws_connection *evws, enum WebSocketFrameType frame_type, |
511 | | const char *packet_str, size_t str_len) |
512 | 0 | { |
513 | 0 | struct evbuffer *output; |
514 | |
|
515 | 0 | bufferevent_lock(evws->bufev); |
516 | 0 | output = bufferevent_get_output(evws->bufev); |
517 | 0 | make_ws_frame(output, frame_type, (unsigned char *)packet_str, str_len); |
518 | 0 | bufferevent_unlock(evws->bufev); |
519 | 0 | } |
520 | | |
521 | | void |
522 | | evws_send_text(struct evws_connection *evws, const char *packet_str) |
523 | 0 | { |
524 | 0 | evws_send(evws, TEXT_FRAME, packet_str, strlen(packet_str)); |
525 | 0 | } |
526 | | |
527 | | void |
528 | | evws_send_binary( |
529 | | struct evws_connection *evws, const char *packet_data, size_t packet_len) |
530 | 0 | { |
531 | 0 | evws_send(evws, BINARY_FRAME, packet_data, packet_len); |
532 | 0 | } |
533 | | |
534 | | void |
535 | | evws_connection_set_closecb( |
536 | | struct evws_connection *evws, ws_on_close_cb cb, void *cbarg) |
537 | 0 | { |
538 | 0 | evws->cbclose = cb; |
539 | 0 | evws->cbclose_arg = cbarg; |
540 | 0 | } |
541 | | |
542 | | struct bufferevent * |
543 | | evws_connection_get_bufferevent(struct evws_connection *evws) |
544 | 0 | { |
545 | 0 | return evws->bufev; |
546 | 0 | } |