/src/libcoap/src/coap_ws.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * coap_ws.c -- WebSockets functions for libcoap |
3 | | * |
4 | | * Copyright (C) 2023-2024 Olaf Bergmann <bergmann@tzi.org> |
5 | | * Copyright (C) 2023-2024 Jon Shallow <supjps-libcoap@jpshallow.com> |
6 | | * |
7 | | * SPDX-License-Identifier: BSD-2-Clause |
8 | | * |
9 | | * This file is part of the CoAP library libcoap. Please see README for terms |
10 | | * of use. |
11 | | */ |
12 | | |
13 | | /** |
14 | | * @file coap_ws.c |
15 | | * @brief CoAP WebSocket handling functions |
16 | | */ |
17 | | |
18 | | #include "coap3/coap_libcoap_build.h" |
19 | | |
20 | | #if COAP_WS_SUPPORT |
21 | | #include <stdio.h> |
22 | | #include <ctype.h> |
23 | | |
24 | | #ifdef _WIN32 |
25 | | #define strcasecmp _stricmp |
26 | | #define strncasecmp _strnicmp |
27 | | #endif |
28 | | |
29 | | #define COAP_WS_RESPONSE \ |
30 | 0 | "HTTP/1.1 101 Switching Protocols\r\n" \ |
31 | 0 | "Upgrade: websocket\r\n" \ |
32 | 0 | "Connection: Upgrade\r\n" \ |
33 | 0 | "Sec-WebSocket-Accept: %s\r\n" \ |
34 | 0 | "Sec-WebSocket-Protocol: coap\r\n" \ |
35 | 0 | "\r\n" |
36 | | |
37 | | int |
38 | 10 | coap_ws_is_supported(void) { |
39 | 10 | return coap_tcp_is_supported(); |
40 | 10 | } |
41 | | |
42 | | int |
43 | 5 | coap_wss_is_supported(void) { |
44 | 5 | return coap_tls_is_supported(); |
45 | 5 | } |
46 | | |
47 | | static const char |
48 | | basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
49 | | |
50 | | static int |
51 | | coap_base64_encode_buffer(const uint8_t *string, size_t len, char *encoded, |
52 | 0 | const size_t max_encoded_len) { |
53 | 0 | size_t i; |
54 | 0 | char *p; |
55 | |
|
56 | 0 | if ((((len + 2) / 3 * 4) + 1) > max_encoded_len) { |
57 | 0 | assert(0); |
58 | 0 | return 0; |
59 | 0 | } |
60 | | |
61 | 0 | p = encoded; |
62 | 0 | for (i = 0; i < len - 2; i += 3) { |
63 | 0 | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
64 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4) | |
65 | 0 | ((int)(string[i + 1] & 0xF0) >> 4)]; |
66 | 0 | *p++ = basis_64[((string[i + 1] & 0xF) << 2) | |
67 | 0 | ((int)(string[i + 2] & 0xC0) >> 6)]; |
68 | 0 | *p++ = basis_64[string[i + 2] & 0x3F]; |
69 | 0 | } |
70 | 0 | if (i < len) { |
71 | 0 | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
72 | 0 | if (i == (len - 1)) { |
73 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4)]; |
74 | 0 | *p++ = '='; |
75 | 0 | } else { |
76 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4) | |
77 | 0 | ((int)(string[i + 1] & 0xF0) >> 4)]; |
78 | 0 | *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; |
79 | 0 | } |
80 | 0 | *p++ = '='; |
81 | 0 | } |
82 | |
|
83 | 0 | *p++ = '\0'; |
84 | 0 | return 1; |
85 | 0 | } |
86 | | |
87 | | static int |
88 | | coap_base64_decode_buffer(const char *bufcoded, size_t *len, uint8_t *bufplain, |
89 | 0 | const size_t max_decoded_len) { |
90 | 0 | size_t nbytesdecoded; |
91 | 0 | const uint8_t *bufin; |
92 | 0 | uint8_t *bufout; |
93 | 0 | size_t nprbytes; |
94 | 0 | static const uint8_t pr2six[256] = { |
95 | | /* ASCII table */ |
96 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
97 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
98 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, |
99 | 0 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, |
100 | 0 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
101 | 0 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, |
102 | 0 | 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
103 | 0 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, |
104 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
105 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
106 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
107 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
108 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
109 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
110 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
111 | 0 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 |
112 | 0 | }; |
113 | |
|
114 | 0 | bufin = (const uint8_t *)bufcoded; |
115 | 0 | while (pr2six[*(bufin++)] <= 63); |
116 | 0 | nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; |
117 | 0 | nbytesdecoded = ((nprbytes + 3) / 4) * 3; |
118 | 0 | if ((nbytesdecoded - ((4 - nprbytes) & 3)) > max_decoded_len) |
119 | 0 | return 0; |
120 | | |
121 | 0 | bufout = bufplain; |
122 | 0 | bufin = (const uint8_t *)bufcoded; |
123 | |
|
124 | 0 | while (nprbytes > 4) { |
125 | 0 | *(bufout++) = |
126 | 0 | (uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); |
127 | 0 | *(bufout++) = |
128 | 0 | (uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); |
129 | 0 | *(bufout++) = |
130 | 0 | (uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); |
131 | 0 | bufin += 4; |
132 | 0 | nprbytes -= 4; |
133 | 0 | } |
134 | | |
135 | | /* Note: (nprbytes == 1) would be an error, so just ignore that case */ |
136 | 0 | if (nprbytes > 1) { |
137 | 0 | *(bufout++) = (uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); |
138 | 0 | } |
139 | 0 | if (nprbytes > 2) { |
140 | 0 | *(bufout++) = (uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); |
141 | 0 | } |
142 | 0 | if (nprbytes > 3) { |
143 | 0 | *(bufout++) = (uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); |
144 | 0 | } |
145 | |
|
146 | 0 | if (len) |
147 | 0 | *len = nbytesdecoded - ((4 - nprbytes) & 3); |
148 | 0 | return 1; |
149 | 0 | } |
150 | | |
151 | | static void |
152 | 0 | coap_ws_log_header(const coap_session_t *session, const uint8_t *header) { |
153 | | #if COAP_MAX_LOGGING_LEVEL < _COAP_LOG_DEBUG |
154 | | (void)session; |
155 | | (void)header; |
156 | | #else /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */ |
157 | 0 | char buf[3*COAP_MAX_FS + 1]; |
158 | 0 | int i; |
159 | 0 | ssize_t bytes_size; |
160 | 0 | int extra_hdr_len = 2; |
161 | |
|
162 | 0 | bytes_size = header[1] & WS_B1_LEN_MASK; |
163 | 0 | if (bytes_size == 127) { |
164 | 0 | extra_hdr_len += 8; |
165 | 0 | } else if (bytes_size == 126) { |
166 | 0 | extra_hdr_len += 2; |
167 | 0 | } |
168 | 0 | if (header[1] & WS_B1_MASK_BIT) { |
169 | 0 | extra_hdr_len +=4; |
170 | 0 | } |
171 | 0 | for (i = 0; i < extra_hdr_len; i++) { |
172 | 0 | snprintf(&buf[i*3], 4, " %02x", header[i]); |
173 | 0 | } |
174 | 0 | coap_log_debug("* %s: ws: h recv %4d bytes\n", |
175 | 0 | coap_session_str(session), extra_hdr_len); |
176 | 0 | coap_log_debug("* %s: WS header:%s\n", coap_session_str(session), buf); |
177 | 0 | #endif /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */ |
178 | 0 | } |
179 | | |
180 | | static void |
181 | 0 | coap_ws_log_key(const coap_session_t *session) { |
182 | 0 | char buf[3*16 + 1]; |
183 | 0 | size_t i; |
184 | |
|
185 | 0 | for (i = 0; i < sizeof(session->ws->key); i++) { |
186 | 0 | snprintf(&buf[i*3], 4, " %02x", session->ws->key[i]); |
187 | 0 | } |
188 | 0 | coap_log_debug("WS: key:%s\n", buf); |
189 | 0 | } |
190 | | |
191 | | static void |
192 | 0 | coap_ws_mask_data(coap_session_t *session, uint8_t *data, size_t data_len) { |
193 | 0 | coap_ws_state_t *ws = session->ws; |
194 | 0 | size_t i; |
195 | |
|
196 | 0 | for (i = 0; i < data_len; i++) { |
197 | 0 | data[i] ^= ws->mask_key[i%4]; |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | ssize_t |
202 | 0 | coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen) { |
203 | 0 | uint8_t ws_header[COAP_MAX_FS]; |
204 | 0 | ssize_t hdr_len = 2; |
205 | 0 | ssize_t ret; |
206 | 0 | uint8_t *wdata; |
207 | | |
208 | | /* If lower layer not yet up, return error */ |
209 | 0 | if (!session->ws) { |
210 | 0 | session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t)); |
211 | 0 | if (!session->ws) { |
212 | 0 | coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED); |
213 | 0 | return -1; |
214 | 0 | } |
215 | 0 | memset(session->ws, 0, sizeof(coap_ws_state_t)); |
216 | 0 | } |
217 | | |
218 | 0 | if (!session->ws->up) { |
219 | 0 | coap_log_debug("WS: Layer not up\n"); |
220 | 0 | return 0; |
221 | 0 | } |
222 | 0 | if (session->ws->sent_close) |
223 | 0 | return 0; |
224 | | |
225 | 0 | ws_header[0] = WS_B0_FIN_BIT | WS_OP_BINARY; |
226 | 0 | if (datalen <= 125) { |
227 | 0 | ws_header[1] = datalen & WS_B1_LEN_MASK; |
228 | 0 | } else if (datalen <= 0xffff) { |
229 | 0 | ws_header[1] = 126; |
230 | 0 | ws_header[2] = (datalen >> 8) & 0xff; |
231 | 0 | ws_header[3] = datalen & 0xff; |
232 | 0 | hdr_len += 2; |
233 | 0 | } else { |
234 | 0 | ws_header[1] = 127; |
235 | 0 | ws_header[2] = ((uint64_t)datalen >> 56) & 0xff; |
236 | 0 | ws_header[3] = ((uint64_t)datalen >> 48) & 0xff; |
237 | 0 | ws_header[4] = ((uint64_t)datalen >> 40) & 0xff; |
238 | 0 | ws_header[5] = ((uint64_t)datalen >> 32) & 0xff; |
239 | 0 | ws_header[6] = (datalen >> 24) & 0xff; |
240 | 0 | ws_header[7] = (datalen >> 16) & 0xff; |
241 | 0 | ws_header[8] = (datalen >> 8) & 0xff; |
242 | 0 | ws_header[9] = datalen & 0xff; |
243 | 0 | hdr_len += 8; |
244 | 0 | } |
245 | 0 | if (session->ws->state == COAP_SESSION_TYPE_CLIENT) { |
246 | | /* Need to set the Mask bit, and set the masking key */ |
247 | 0 | ws_header[1] |= WS_B1_MASK_BIT; |
248 | | /* TODO Masking Key and mask provided data */ |
249 | 0 | coap_prng_lkd(&ws_header[hdr_len], 4); |
250 | 0 | memcpy(session->ws->mask_key, &ws_header[hdr_len], 4); |
251 | 0 | hdr_len += 4; |
252 | 0 | } |
253 | 0 | coap_ws_log_header(session, ws_header); |
254 | 0 | wdata = coap_malloc_type(COAP_STRING, datalen + hdr_len); |
255 | 0 | if (!wdata) { |
256 | 0 | errno = ENOMEM; |
257 | 0 | return -1; |
258 | 0 | } |
259 | 0 | memcpy(wdata, ws_header, hdr_len); |
260 | 0 | memcpy(&wdata[hdr_len], data, datalen); |
261 | 0 | if (session->ws->state == COAP_SESSION_TYPE_CLIENT) { |
262 | | /* Need to mask the data */ |
263 | 0 | coap_ws_mask_data(session, &wdata[hdr_len], datalen); |
264 | 0 | } |
265 | 0 | ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, wdata, datalen + hdr_len); |
266 | 0 | coap_free_type(COAP_STRING, wdata); |
267 | 0 | if (ret < hdr_len) { |
268 | 0 | return ret; |
269 | 0 | } |
270 | 0 | coap_log_debug("* %s: ws h: sent %4zd bytes\n", |
271 | 0 | coap_session_str(session), hdr_len); |
272 | 0 | if (ret == (ssize_t)(datalen + hdr_len)) |
273 | 0 | coap_log_debug("* %s: ws: sent %4zd bytes\n", |
274 | 0 | coap_session_str(session), ret - hdr_len); |
275 | 0 | else |
276 | 0 | coap_log_debug("* %s: ws: sent %4zd of %4zd bytes\n", |
277 | 0 | coap_session_str(session), ret, datalen - hdr_len); |
278 | 0 | return datalen; |
279 | 0 | } |
280 | | |
281 | | static char * |
282 | 0 | coap_ws_split_rd_header(coap_session_t *session) { |
283 | 0 | char *cp = strchr((char *)session->ws->http_hdr, ' '); |
284 | |
|
285 | 0 | if (!cp) |
286 | 0 | cp = strchr((char *)session->ws->http_hdr, '\t'); |
287 | |
|
288 | 0 | if (!cp) |
289 | 0 | return NULL; |
290 | | |
291 | 0 | *cp = '\000'; |
292 | 0 | cp++; |
293 | 0 | while (isblank(*cp)) |
294 | 0 | cp++; |
295 | 0 | return cp; |
296 | 0 | } |
297 | | |
298 | | static int |
299 | 0 | coap_ws_rd_http_header_server(coap_session_t *session) { |
300 | 0 | coap_ws_state_t *ws = session->ws; |
301 | 0 | char *value; |
302 | |
|
303 | 0 | if (!ws->seen_first) { |
304 | 0 | if (strcasecmp((char *)ws->http_hdr, |
305 | 0 | "GET /.well-known/coap HTTP/1.1") != 0) { |
306 | 0 | coap_log_info("WS: Invalid GET request %s\n", (char *)ws->http_hdr); |
307 | 0 | return 0; |
308 | 0 | } |
309 | 0 | ws->seen_first = 1; |
310 | 0 | return 1; |
311 | 0 | } |
312 | | /* Process the individual header */ |
313 | 0 | value = coap_ws_split_rd_header(session); |
314 | 0 | if (!value) |
315 | 0 | return 0; |
316 | | |
317 | 0 | if (strcasecmp((char *)ws->http_hdr, "Host:") == 0) { |
318 | 0 | if (ws->seen_host) { |
319 | 0 | coap_log_debug("WS: Duplicate Host: header\n"); |
320 | 0 | return 0; |
321 | 0 | } |
322 | 0 | ws->seen_host = 1; |
323 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) { |
324 | 0 | if (ws->seen_upg) { |
325 | 0 | coap_log_debug("WS: Duplicate Upgrade: header\n"); |
326 | 0 | return 0; |
327 | 0 | } |
328 | 0 | if (strcasecmp(value, "websocket") != 0) { |
329 | 0 | coap_log_debug("WS: Invalid Upgrade: header\n"); |
330 | 0 | return 0; |
331 | 0 | } |
332 | 0 | ws->seen_upg = 1; |
333 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) { |
334 | 0 | if (ws->seen_conn) { |
335 | 0 | coap_log_debug("WS: Duplicate Connection: header\n"); |
336 | 0 | return 0; |
337 | 0 | } |
338 | 0 | if (strcasecmp(value, "Upgrade") != 0 && |
339 | 0 | strcasecmp(value, "keep-alive, Upgrade") != 0) { |
340 | 0 | coap_log_debug("WS: Invalid Connection: header\n"); |
341 | 0 | return 0; |
342 | 0 | } |
343 | 0 | ws->seen_conn = 1; |
344 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Key:") == 0) { |
345 | 0 | size_t len; |
346 | |
|
347 | 0 | if (ws->seen_key) { |
348 | 0 | coap_log_debug("WS: Duplicate Sec-WebSocket-Key: header\n"); |
349 | 0 | return 0; |
350 | 0 | } |
351 | 0 | if (!coap_base64_decode_buffer(value, &len, ws->key, |
352 | 0 | sizeof(ws->key)) || |
353 | 0 | len != sizeof(ws->key)) { |
354 | 0 | coap_log_info("WS: Invalid Sec-WebSocket-Key: %s\n", value); |
355 | 0 | return 0; |
356 | 0 | } |
357 | 0 | coap_ws_log_key(session); |
358 | 0 | ws->seen_key = 1; |
359 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) { |
360 | 0 | if (ws->seen_proto) { |
361 | 0 | coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n"); |
362 | 0 | return 0; |
363 | 0 | } |
364 | 0 | if (strcasecmp(value, "coap") != 0) { |
365 | 0 | coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n"); |
366 | 0 | return 0; |
367 | 0 | } |
368 | 0 | ws->seen_proto = 1; |
369 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Version:") == 0) { |
370 | 0 | if (ws->seen_ver) { |
371 | 0 | coap_log_debug("WS: Duplicate Sec-WebSocket-Version: header\n"); |
372 | 0 | return 0; |
373 | 0 | } |
374 | 0 | if (strcasecmp(value, "13") != 0) { |
375 | 0 | coap_log_debug("WS: Invalid Sec-WebSocket-Version: header\n"); |
376 | 0 | return 0; |
377 | 0 | } |
378 | 0 | ws->seen_ver = 1; |
379 | 0 | } |
380 | 0 | return 1; |
381 | 0 | } |
382 | | |
383 | 0 | #define COAP_WS_KEY_EXT "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
384 | | |
385 | | static int |
386 | 0 | coap_ws_build_key_hash(coap_session_t *session, char *hash, size_t max_hash_len) { |
387 | 0 | char buf[28 + sizeof(COAP_WS_KEY_EXT)]; |
388 | 0 | coap_bin_const_t info; |
389 | 0 | coap_bin_const_t *hashed = NULL; |
390 | |
|
391 | 0 | if (max_hash_len < 29) |
392 | 0 | return 0; |
393 | 0 | if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key), |
394 | 0 | buf, sizeof(buf))) |
395 | 0 | return 0; |
396 | 0 | if (strlen(buf) >= 28) |
397 | 0 | return 0; |
398 | 0 | strcat(buf, COAP_WS_KEY_EXT); |
399 | 0 | info.s = (uint8_t *)buf; |
400 | 0 | info.length = strlen(buf); |
401 | 0 | if (!coap_crypto_hash(COSE_ALGORITHM_SHA_1, &info, &hashed)) |
402 | 0 | return 0; |
403 | | |
404 | 0 | if (!coap_base64_encode_buffer(hashed->s, hashed->length, |
405 | 0 | hash, max_hash_len)) { |
406 | 0 | coap_delete_bin_const(hashed); |
407 | 0 | return 0; |
408 | 0 | } |
409 | 0 | coap_delete_bin_const(hashed); |
410 | 0 | return 1; |
411 | 0 | } |
412 | | |
413 | | static int |
414 | 0 | coap_ws_rd_http_header_client(coap_session_t *session) { |
415 | 0 | coap_ws_state_t *ws = session->ws; |
416 | 0 | char *value; |
417 | |
|
418 | 0 | if (!ws->seen_first) { |
419 | 0 | value = coap_ws_split_rd_header(session); |
420 | |
|
421 | 0 | if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 || |
422 | 0 | atoi(value) != 101) { |
423 | 0 | coap_log_info("WS: Invalid GET response %s\n", (char *)ws->http_hdr); |
424 | 0 | return 0; |
425 | 0 | } |
426 | 0 | ws->seen_first = 1; |
427 | 0 | return 1; |
428 | 0 | } |
429 | | /* Process the individual header */ |
430 | 0 | value = coap_ws_split_rd_header(session); |
431 | 0 | if (!value) |
432 | 0 | return 0; |
433 | | |
434 | 0 | if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) { |
435 | 0 | if (ws->seen_upg) { |
436 | 0 | coap_log_debug("WS: Duplicate Upgrade: header\n"); |
437 | 0 | return 0; |
438 | 0 | } |
439 | 0 | if (strcasecmp(value, "websocket") != 0) { |
440 | 0 | coap_log_debug("WS: Invalid Upgrade: header\n"); |
441 | 0 | return 0; |
442 | 0 | } |
443 | 0 | ws->seen_upg = 1; |
444 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) { |
445 | 0 | if (ws->seen_conn) { |
446 | 0 | coap_log_debug("WS: Duplicate Connection: header\n"); |
447 | 0 | return 0; |
448 | 0 | } |
449 | 0 | if (strcasecmp(value, "Upgrade") != 0) { |
450 | 0 | coap_log_debug("WS: Invalid Connection: header\n"); |
451 | 0 | return 0; |
452 | 0 | } |
453 | 0 | ws->seen_conn = 1; |
454 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Accept:") == 0) { |
455 | 0 | char hash[30]; |
456 | |
|
457 | 0 | if (ws->seen_key) { |
458 | 0 | coap_log_debug("WS: Duplicate Sec-WebSocket-Accept: header\n"); |
459 | 0 | return 0; |
460 | 0 | } |
461 | 0 | if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) { |
462 | 0 | return 0; |
463 | 0 | } |
464 | 0 | if (strcmp(hash, value) != 0) { |
465 | 0 | return 0; |
466 | 0 | } |
467 | 0 | ws->seen_key = 1; |
468 | 0 | } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) { |
469 | 0 | if (ws->seen_proto) { |
470 | 0 | coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n"); |
471 | 0 | return 0; |
472 | 0 | } |
473 | 0 | if (strcasecmp(value, "coap") != 0) { |
474 | 0 | coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n"); |
475 | 0 | return 0; |
476 | 0 | } |
477 | 0 | ws->seen_proto = 1; |
478 | 0 | } |
479 | 0 | return 1; |
480 | 0 | } |
481 | | |
482 | | /* |
483 | | * Read in and parse WebSockets setup HTTP headers |
484 | | * |
485 | | * return 0 failure |
486 | | * 1 success |
487 | | */ |
488 | | static int |
489 | 0 | coap_ws_rd_http_header(coap_session_t *session) { |
490 | 0 | coap_ws_state_t *ws = session->ws; |
491 | 0 | ssize_t bytes; |
492 | 0 | ssize_t rem; |
493 | 0 | char *cp; |
494 | |
|
495 | 0 | while (!ws->up) { |
496 | | /* |
497 | | * Can only read in up to COAP_MAX_FS at a time in case there is |
498 | | * some frame info that needs to be subsequently processed |
499 | | */ |
500 | 0 | rem = ws->http_ofs > (sizeof(ws->http_hdr) - 1 - COAP_MAX_FS) ? |
501 | 0 | sizeof(ws->http_hdr) - ws->http_ofs : COAP_MAX_FS; |
502 | 0 | bytes = session->sock.lfunc[COAP_LAYER_WS].l_read(session, |
503 | 0 | &ws->http_hdr[ws->http_ofs], |
504 | 0 | rem); |
505 | 0 | if (bytes < 0) |
506 | 0 | return 0; |
507 | 0 | if (bytes == 0) |
508 | 0 | return 1; |
509 | | |
510 | 0 | ws->http_ofs += (uint32_t)bytes; |
511 | 0 | ws->http_hdr[ws->http_ofs] = '\000'; |
512 | | /* Force at least one check */ |
513 | 0 | cp = (char *)ws->http_hdr; |
514 | 0 | while (cp) { |
515 | 0 | cp = strchr((char *)ws->http_hdr, '\n'); |
516 | 0 | if (cp) { |
517 | | /* Whole header record in */ |
518 | 0 | *cp = '\000'; |
519 | 0 | if (cp != (char *)ws->http_hdr) { |
520 | 0 | if (cp[-1] == '\r') |
521 | 0 | cp[-1] = '\000'; |
522 | 0 | } |
523 | |
|
524 | 0 | coap_log_debug("WS: HTTP: %s\n", ws->http_hdr); |
525 | 0 | if (ws->http_hdr[0] != '\000') { |
526 | 0 | if (ws->state == COAP_SESSION_TYPE_SERVER) { |
527 | 0 | if (!coap_ws_rd_http_header_server(session)) { |
528 | 0 | return 0; |
529 | 0 | } |
530 | 0 | } else { |
531 | 0 | if (!coap_ws_rd_http_header_client(session)) { |
532 | 0 | return 0; |
533 | 0 | } |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | 0 | rem = ws->http_ofs - ((uint8_t *)cp + 1 - ws->http_hdr); |
538 | 0 | if (ws->http_hdr[0] == '\000') { |
539 | | /* Found trailing empty header line */ |
540 | 0 | if (ws->state == COAP_SESSION_TYPE_SERVER) { |
541 | 0 | if (!(ws->seen_first && ws->seen_host && ws->seen_upg && |
542 | 0 | ws->seen_conn && ws->seen_key && ws->seen_proto && |
543 | 0 | ws->seen_ver)) { |
544 | 0 | coap_log_info("WS: Missing protocol header(s)\n"); |
545 | 0 | return 0; |
546 | 0 | } |
547 | 0 | } else { |
548 | 0 | if (!(ws->seen_first && ws->seen_upg && ws->seen_conn && |
549 | 0 | ws->seen_key && ws->seen_proto)) { |
550 | 0 | coap_log_info("WS: Missing protocol header(s)\n"); |
551 | 0 | return 0; |
552 | 0 | } |
553 | 0 | } |
554 | 0 | ws->up = 1; |
555 | 0 | ws->hdr_ofs = (int)rem; |
556 | 0 | if (rem > 0) |
557 | 0 | memcpy(ws->rd_header, cp + 1, rem); |
558 | 0 | return 1; |
559 | 0 | } |
560 | 0 | ws->http_ofs = (uint32_t)rem; |
561 | 0 | memmove(ws->http_hdr, cp + 1, rem); |
562 | 0 | ws->http_hdr[ws->http_ofs] = '\000'; |
563 | 0 | } |
564 | 0 | } |
565 | 0 | } |
566 | 0 | return 1; |
567 | 0 | } |
568 | | |
569 | | /* |
570 | | * return >=0 Number of bytes processed. |
571 | | * -1 Error (error in errno). |
572 | | */ |
573 | | ssize_t |
574 | 0 | coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen) { |
575 | 0 | ssize_t bytes_size = 0; |
576 | 0 | ssize_t extra_hdr_len = 0; |
577 | 0 | ssize_t ret; |
578 | 0 | uint8_t op_code; |
579 | |
|
580 | 0 | if (!session->ws) { |
581 | 0 | session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t)); |
582 | 0 | if (!session->ws) { |
583 | 0 | coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED); |
584 | 0 | return -1; |
585 | 0 | } |
586 | 0 | memset(session->ws, 0, sizeof(coap_ws_state_t)); |
587 | 0 | } |
588 | | |
589 | 0 | if (!session->ws->up) { |
590 | 0 | char buf[250]; |
591 | |
|
592 | 0 | if (!coap_ws_rd_http_header(session)) { |
593 | 0 | snprintf(buf, sizeof(buf), "HTTP/1.1 400 Invalid request\r\n\r\n"); |
594 | 0 | coap_log_debug("WS: Response (Fail)\n%s", buf); |
595 | 0 | if (coap_netif_available(session)) { |
596 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf, |
597 | 0 | strlen(buf)); |
598 | 0 | } |
599 | 0 | coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED); |
600 | 0 | return -1; |
601 | 0 | } |
602 | | |
603 | 0 | if (!session->ws->up) |
604 | 0 | return 0; |
605 | | |
606 | 0 | if (session->ws->state == COAP_SESSION_TYPE_SERVER) { |
607 | 0 | char hash[30]; |
608 | |
|
609 | 0 | if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) { |
610 | 0 | return 0; |
611 | 0 | } |
612 | 0 | snprintf(buf, sizeof(buf), COAP_WS_RESPONSE, hash); |
613 | 0 | coap_log_debug("WS: Response\n%s", buf); |
614 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf, |
615 | 0 | strlen(buf)); |
616 | |
|
617 | 0 | coap_handle_event_lkd(session->context, COAP_EVENT_WS_CONNECTED, session); |
618 | 0 | coap_log_debug("WS: established\n"); |
619 | 0 | } else { |
620 | | /* TODO Process the GET response - error on failure */ |
621 | |
|
622 | 0 | coap_handle_event_lkd(session->context, COAP_EVENT_WS_CONNECTED, session); |
623 | 0 | } |
624 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_establish(session); |
625 | 0 | if (session->ws->hdr_ofs == 0) |
626 | 0 | return 0; |
627 | 0 | } |
628 | | |
629 | | /* Get WebSockets frame if not already completely in */ |
630 | 0 | if (!session->ws->all_hdr_in) { |
631 | 0 | ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session, |
632 | 0 | &session->ws->rd_header[session->ws->hdr_ofs], |
633 | 0 | sizeof(session->ws->rd_header) - session->ws->hdr_ofs); |
634 | 0 | if (ret < 0) |
635 | 0 | return ret; |
636 | 0 | session->ws->hdr_ofs += (int)ret; |
637 | | /* Enough of the header in ? */ |
638 | 0 | if (session->ws->hdr_ofs < 2) |
639 | 0 | return 0; |
640 | | |
641 | 0 | if (session->ws->state == COAP_SESSION_TYPE_SERVER && |
642 | 0 | !(session->ws->rd_header[1] & WS_B1_MASK_BIT)) { |
643 | | /* Client has failed to mask the data */ |
644 | 0 | session->ws->close_reason = 1002; |
645 | 0 | coap_ws_close(session); |
646 | 0 | return 0; |
647 | 0 | } |
648 | | |
649 | 0 | bytes_size = session->ws->rd_header[1] & WS_B1_LEN_MASK; |
650 | 0 | if (bytes_size == 127) { |
651 | 0 | extra_hdr_len += 8; |
652 | 0 | } else if (bytes_size == 126) { |
653 | 0 | extra_hdr_len += 2; |
654 | 0 | } |
655 | 0 | if (session->ws->rd_header[1] & WS_B1_MASK_BIT) { |
656 | 0 | memcpy(session->ws->mask_key, &session->ws->rd_header[2 + extra_hdr_len], 4); |
657 | 0 | extra_hdr_len +=4; |
658 | 0 | } |
659 | 0 | if (session->ws->hdr_ofs < 2 + extra_hdr_len) |
660 | 0 | return 0; |
661 | | |
662 | | /* Header frame is fully in */ |
663 | 0 | coap_ws_log_header(session, session->ws->rd_header); |
664 | |
|
665 | 0 | op_code = session->ws->rd_header[0] & WS_B0_OP_MASK; |
666 | 0 | if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) { |
667 | | /* Remote has failed to use correct opcode */ |
668 | 0 | session->ws->close_reason = 1003; |
669 | 0 | coap_ws_close(session); |
670 | 0 | return 0; |
671 | 0 | } |
672 | 0 | if (op_code == WS_OP_CLOSE) { |
673 | 0 | coap_log_debug("WS: Close received\n"); |
674 | 0 | session->ws->recv_close = 1; |
675 | 0 | coap_ws_close(session); |
676 | 0 | return 0; |
677 | 0 | } |
678 | | |
679 | 0 | session->ws->all_hdr_in = 1; |
680 | | |
681 | | /* Get WebSockets frame size */ |
682 | 0 | if (bytes_size == 127) { |
683 | 0 | bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) + |
684 | 0 | ((uint64_t)session->ws->rd_header[3] << 48) + |
685 | 0 | ((uint64_t)session->ws->rd_header[4] << 40) + |
686 | 0 | ((uint64_t)session->ws->rd_header[5] << 32) + |
687 | 0 | ((uint64_t)session->ws->rd_header[6] << 24) + |
688 | 0 | ((uint64_t)session->ws->rd_header[7] << 16) + |
689 | 0 | ((uint64_t)session->ws->rd_header[8] << 8) + |
690 | 0 | session->ws->rd_header[9]; |
691 | 0 | } else if (bytes_size == 126) { |
692 | 0 | bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) + |
693 | 0 | session->ws->rd_header[3]; |
694 | 0 | } |
695 | 0 | session->ws->data_size = bytes_size; |
696 | 0 | if ((size_t)bytes_size > datalen) { |
697 | 0 | coap_log_err("coap_ws_read: packet size bigger than provided data space" |
698 | 0 | " (%zu > %zu)\n", bytes_size, datalen); |
699 | 0 | coap_handle_event_lkd(session->context, COAP_EVENT_WS_PACKET_SIZE, session); |
700 | 0 | session->ws->close_reason = 1009; |
701 | 0 | coap_ws_close(session); |
702 | 0 | return 0; |
703 | 0 | } |
704 | 0 | coap_log_debug("* %s: Packet size %zu\n", coap_session_str(session), |
705 | 0 | bytes_size); |
706 | | |
707 | | /* Handle any data read in as a part of the header */ |
708 | 0 | ret = session->ws->hdr_ofs - 2 - extra_hdr_len; |
709 | 0 | if (ret > 0) { |
710 | 0 | assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header)); |
711 | | /* data in latter part of header */ |
712 | 0 | if (ret <= bytes_size) { |
713 | | /* copy across all the available data */ |
714 | 0 | memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret); |
715 | 0 | session->ws->data_ofs = ret; |
716 | 0 | if (ret == bytes_size) { |
717 | 0 | if (session->ws->state == COAP_SESSION_TYPE_SERVER) { |
718 | | /* Need to unmask the data */ |
719 | 0 | coap_ws_mask_data(session, data, bytes_size); |
720 | 0 | } |
721 | 0 | session->ws->all_hdr_in = 0; |
722 | 0 | session->ws->hdr_ofs = 0; |
723 | 0 | op_code = session->ws->rd_header[0] & WS_B0_OP_MASK; |
724 | 0 | if (op_code == WS_OP_CLOSE) { |
725 | 0 | session->ws->close_reason = (data[0] << 8) + data[1]; |
726 | 0 | coap_log_debug("* %s: WS: Close received (%u)\n", |
727 | 0 | coap_session_str(session), |
728 | 0 | session->ws->close_reason); |
729 | 0 | session->ws->recv_close = 1; |
730 | 0 | if (!session->ws->sent_close) |
731 | 0 | coap_ws_close(session); |
732 | 0 | return 0; |
733 | 0 | } |
734 | 0 | return bytes_size; |
735 | 0 | } |
736 | 0 | } else { |
737 | | /* more information in header than given data size */ |
738 | 0 | memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size); |
739 | 0 | session->ws->data_ofs = bytes_size; |
740 | 0 | if (session->ws->state == COAP_SESSION_TYPE_SERVER) { |
741 | | /* Need to unmask the data */ |
742 | 0 | coap_ws_mask_data(session, data, bytes_size); |
743 | 0 | } |
744 | | /* set up partial header for the next read */ |
745 | 0 | memmove(session->ws->rd_header, |
746 | 0 | &session->ws->rd_header[2 + extra_hdr_len + bytes_size], |
747 | 0 | ret - bytes_size); |
748 | 0 | session->ws->all_hdr_in = 0; |
749 | 0 | session->ws->hdr_ofs = (int)(ret - bytes_size); |
750 | 0 | return bytes_size; |
751 | 0 | } |
752 | 0 | } else { |
753 | 0 | session->ws->data_ofs = 0; |
754 | 0 | } |
755 | 0 | } |
756 | | |
757 | | /* Get in (remaining) data */ |
758 | 0 | ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session, |
759 | 0 | &data[session->ws->data_ofs], |
760 | 0 | session->ws->data_size - session->ws->data_ofs); |
761 | 0 | if (ret <= 0) |
762 | 0 | return ret; |
763 | 0 | session->ws->data_ofs += ret; |
764 | 0 | if (session->ws->data_ofs == session->ws->data_size) { |
765 | 0 | if (session->ws->state == COAP_SESSION_TYPE_SERVER) { |
766 | | /* Need to unmask the data */ |
767 | 0 | coap_ws_mask_data(session, data, session->ws->data_size); |
768 | 0 | } |
769 | 0 | session->ws->all_hdr_in = 0; |
770 | 0 | session->ws->hdr_ofs = 0; |
771 | 0 | session->ws->data_ofs = 0; |
772 | 0 | coap_log_debug("* %s: ws: recv %4zd bytes\n", |
773 | 0 | coap_session_str(session), session->ws->data_size); |
774 | 0 | return session->ws->data_size; |
775 | 0 | } |
776 | | /* Need to get in all of the data */ |
777 | 0 | coap_log_debug("* %s: Waiting Packet size %zu (got %zu)\n", coap_session_str(session), |
778 | 0 | session->ws->data_size, session->ws->data_ofs); |
779 | 0 | return 0; |
780 | 0 | } |
781 | | |
782 | | #define COAP_WS_REQUEST \ |
783 | 0 | "GET /.well-known/coap HTTP/1.1\r\n" \ |
784 | 0 | "Host: %s\r\n" \ |
785 | 0 | "Upgrade: websocket\r\n" \ |
786 | 0 | "Connection: Upgrade\r\n" \ |
787 | 0 | "Sec-WebSocket-Key: %s\r\n" \ |
788 | 0 | "Sec-WebSocket-Protocol: coap\r\n" \ |
789 | 0 | "Sec-WebSocket-Version: 13\r\n" \ |
790 | 0 | "\r\n" |
791 | | |
792 | | void |
793 | 0 | coap_ws_establish(coap_session_t *session) { |
794 | 0 | if (!session->ws) { |
795 | 0 | session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t)); |
796 | 0 | if (!session->ws) { |
797 | 0 | coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED); |
798 | 0 | return; |
799 | 0 | } |
800 | 0 | memset(session->ws, 0, sizeof(coap_ws_state_t)); |
801 | 0 | } |
802 | 0 | if (session->type == COAP_SESSION_TYPE_CLIENT) { |
803 | 0 | char buf[270]; |
804 | 0 | char base64[28]; |
805 | 0 | char host[80]; |
806 | 0 | int port = 0; |
807 | |
|
808 | 0 | session->ws->state = COAP_SESSION_TYPE_CLIENT; |
809 | 0 | if (!session->ws_host) { |
810 | 0 | coap_log_err("WS Host not defined\n"); |
811 | 0 | coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED); |
812 | 0 | return; |
813 | 0 | } |
814 | 0 | coap_prng_lkd(session->ws->key, sizeof(session->ws->key)); |
815 | 0 | coap_ws_log_key(session); |
816 | 0 | if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key), |
817 | 0 | base64, sizeof(base64))) |
818 | 0 | return; |
819 | 0 | if (session->proto == COAP_PROTO_WS && |
820 | 0 | coap_address_get_port(&session->addr_info.remote) != 80) { |
821 | 0 | port = coap_address_get_port(&session->addr_info.remote); |
822 | 0 | } else if (session->proto == COAP_PROTO_WSS && |
823 | 0 | coap_address_get_port(&session->addr_info.remote) != 443) { |
824 | 0 | port = coap_address_get_port(&session->addr_info.remote); |
825 | 0 | } |
826 | 0 | if (strchr((const char *)session->ws_host->s, ':')) { |
827 | 0 | if (port) { |
828 | 0 | snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port); |
829 | 0 | } else { |
830 | 0 | snprintf(host, sizeof(host), "[%s]", session->ws_host->s); |
831 | 0 | } |
832 | 0 | } else { |
833 | 0 | if (port) { |
834 | 0 | snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port); |
835 | 0 | } else { |
836 | 0 | snprintf(host, sizeof(host), "%s", session->ws_host->s); |
837 | 0 | } |
838 | 0 | } |
839 | 0 | snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64); |
840 | 0 | coap_log_debug("WS Request\n%s", buf); |
841 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf, |
842 | 0 | strlen(buf)); |
843 | 0 | } else { |
844 | 0 | session->ws->state = COAP_SESSION_TYPE_SERVER; |
845 | 0 | } |
846 | 0 | } |
847 | | |
848 | | void |
849 | 0 | coap_ws_close(coap_session_t *session) { |
850 | 0 | if (!coap_netif_available(session) || |
851 | 0 | session->state == COAP_SESSION_STATE_NONE) { |
852 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_close(session); |
853 | 0 | return; |
854 | 0 | } |
855 | 0 | if (session->ws && session->ws->up) { |
856 | 0 | #if !defined(WITH_LWIP) && !defined(WITH_CONTIKI) |
857 | 0 | int count; |
858 | 0 | #endif /* ! WITH_LWIP && ! WITH_CONTIKI */ |
859 | |
|
860 | 0 | if (!session->ws->sent_close) { |
861 | 0 | size_t hdr_len = 2; |
862 | 0 | uint8_t ws_header[COAP_MAX_FS]; |
863 | 0 | size_t ret; |
864 | |
|
865 | 0 | ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE; |
866 | 0 | ws_header[1] = 2; |
867 | 0 | if (session->ws->state == COAP_SESSION_TYPE_CLIENT) { |
868 | | /* Need to set the Mask bit, and set the masking key */ |
869 | 0 | ws_header[1] |= WS_B1_MASK_BIT; |
870 | 0 | coap_prng_lkd(&ws_header[hdr_len], 4); |
871 | 0 | memcpy(session->ws->mask_key, &ws_header[hdr_len], 4); |
872 | 0 | hdr_len += 4; |
873 | 0 | } |
874 | 0 | coap_ws_log_header(session, ws_header); |
875 | 0 | if (session->ws->close_reason == 0) |
876 | 0 | session->ws->close_reason = 1000; |
877 | |
|
878 | 0 | ws_header[hdr_len] = session->ws->close_reason >> 8; |
879 | 0 | ws_header[hdr_len+1] = session->ws->close_reason & 0xff; |
880 | 0 | if (session->ws->state == COAP_SESSION_TYPE_CLIENT) { |
881 | 0 | coap_ws_mask_data(session, &ws_header[hdr_len], 2); |
882 | 0 | } |
883 | 0 | session->ws->sent_close = 1; |
884 | 0 | coap_log_debug("* %s: WS: Close sent (%u)\n", |
885 | 0 | coap_session_str(session), |
886 | 0 | session->ws->close_reason); |
887 | 0 | ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2); |
888 | 0 | if (ret != hdr_len+2) { |
889 | 0 | return; |
890 | 0 | } |
891 | 0 | } |
892 | 0 | #if !defined(WITH_LWIP) && !defined(WITH_CONTIKI) |
893 | 0 | count = 5; |
894 | 0 | while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) { |
895 | 0 | uint8_t buf[100]; |
896 | 0 | fd_set readfds; |
897 | 0 | int result; |
898 | 0 | struct timeval tv; |
899 | |
|
900 | 0 | FD_ZERO(&readfds); |
901 | 0 | FD_SET(session->sock.fd, &readfds); |
902 | 0 | tv.tv_sec = 0; |
903 | 0 | tv.tv_usec = 1000; |
904 | 0 | result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv); |
905 | |
|
906 | 0 | if (result < 0) { |
907 | 0 | break; |
908 | 0 | } else if (result > 0) { |
909 | 0 | coap_ws_read(session, buf, sizeof(buf)); |
910 | 0 | } |
911 | 0 | count --; |
912 | 0 | } |
913 | 0 | #endif /* ! WITH_LWIP && ! WITH_CONTIKI */ |
914 | 0 | coap_handle_event_lkd(session->context, COAP_EVENT_WS_CLOSED, session); |
915 | 0 | } |
916 | 0 | session->sock.lfunc[COAP_LAYER_WS].l_close(session); |
917 | 0 | } |
918 | | |
919 | | int |
920 | 0 | coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) { |
921 | 0 | if (!session | !ws_host) |
922 | 0 | return 0; |
923 | | |
924 | 0 | session->ws_host = coap_new_str_const(ws_host->s, ws_host->length); |
925 | 0 | if (!session->ws_host) |
926 | 0 | return 0; |
927 | 0 | return 1; |
928 | 0 | } |
929 | | |
930 | | #else /* !COAP_WS_SUPPORT */ |
931 | | |
932 | | int |
933 | | coap_ws_is_supported(void) { |
934 | | return 0; |
935 | | } |
936 | | |
937 | | int |
938 | | coap_wss_is_supported(void) { |
939 | | return 0; |
940 | | } |
941 | | |
942 | | int |
943 | | coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) { |
944 | | (void)session; |
945 | | (void)ws_host; |
946 | | return 0; |
947 | | } |
948 | | |
949 | | #endif /* !COAP_WS_SUPPORT */ |