-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
extmod/modwebsocket: Add client support. #3406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
#include <stdint.h> | ||
#include <string.h> | ||
|
||
#include "py/objmodule.h" | ||
#include "py/runtime.h" | ||
#include "py/stream.h" | ||
#include "extmod/modwebsocket.h" | ||
|
@@ -38,14 +39,18 @@ enum { FRAME_HEADER, FRAME_OPT, PAYLOAD, CONTROL }; | |
|
||
enum { BLOCKING_WRITE = 0x80 }; | ||
|
||
enum { NO_WRITE_MASKING, NORMAL_WRITE_MASKING, DEBUG_WRITE_MASKING }; | ||
|
||
typedef struct _mp_obj_websocket_t { | ||
mp_obj_base_t base; | ||
mp_obj_t sock; | ||
uint32_t msg_sz; | ||
byte mask[4]; | ||
byte read_mask[4]; | ||
byte do_write_masking; | ||
byte debug_write_mask[4]; | ||
byte state; | ||
byte to_recv; | ||
byte mask_pos; | ||
byte read_mask_pos; | ||
byte buf_pos; | ||
byte buf[6]; | ||
byte opts; | ||
|
@@ -58,16 +63,39 @@ typedef struct _mp_obj_websocket_t { | |
STATIC mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode); | ||
|
||
STATIC mp_obj_t websocket_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { | ||
mp_arg_check_num(n_args, n_kw, 1, 2, false); | ||
static const mp_arg_t allowed_args[] = { | ||
{ MP_QSTR_sock, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, | ||
{ MP_QSTR_use_blocking_writes, MP_ARG_BOOL, {.u_bool = false} }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We always save for the space, so using such long arg names is no-no. |
||
{ MP_QSTR_is_client, MP_ARG_BOOL, {.u_bool = false} }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should use the same terminology as "ssl" module. |
||
{ MP_QSTR_debug_mask, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm always for having debugging/diagnostic facilities in the code, but they should be properly #ifdef'ed and off by default. |
||
}; | ||
|
||
// parse args | ||
struct { | ||
mp_arg_val_t sock, use_blocking_writes, is_client, debug_mask; | ||
} arg_vals; | ||
mp_arg_parse_all_kw_array(n_args, n_kw, args, | ||
MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t*)&arg_vals); | ||
|
||
mp_obj_websocket_t *o = m_new_obj(mp_obj_websocket_t); | ||
o->base.type = type; | ||
o->sock = args[0]; | ||
o->sock = arg_vals.sock.u_obj; | ||
o->do_write_masking = !arg_vals.is_client.u_bool ? NO_WRITE_MASKING : NORMAL_WRITE_MASKING; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least remove "!" and swap args. I'd also use normal "if" here. |
||
if (arg_vals.debug_mask.u_obj != MP_OBJ_NULL) { | ||
mp_buffer_info_t bufinfo; | ||
mp_get_buffer_raise(arg_vals.debug_mask.u_obj, &bufinfo, MP_BUFFER_READ); | ||
if (bufinfo.len != 4) { | ||
mp_raise_ValueError("debug mask must have length of 4"); | ||
} | ||
o->do_write_masking = DEBUG_WRITE_MASKING; | ||
memcpy(o->debug_write_mask, bufinfo.buf, 4); | ||
} | ||
o->state = FRAME_HEADER; | ||
o->to_recv = 2; | ||
o->mask_pos = 0; | ||
o->read_mask_pos = 0; | ||
o->buf_pos = 0; | ||
o->opts = FRAME_TXT; | ||
if (n_args > 1 && args[1] == mp_const_true) { | ||
if (arg_vals.use_blocking_writes.u_bool) { | ||
o->opts |= BLOCKING_WRITE; | ||
} | ||
return MP_OBJ_FROM_PTR(o); | ||
|
@@ -111,7 +139,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t s 8000 ize, int | |
|
||
// Reset mask in case someone will use "simplified" protocol | ||
// without masks. | ||
memset(self->mask, 0, sizeof(self->mask)); | ||
memset(self->read_mask, 0, sizeof(self->read_mask)); | ||
|
||
int to_recv = 0; | ||
size_t sz = self->buf[1] & 0x7f; | ||
|
@@ -149,7 +177,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int | |
} | ||
if (self->buf_pos >= 4) { | ||
// Last 4 bytes is mask | ||
memcpy(self->mask, self->buf + self->buf_pos - 4, 4); | ||
memcpy(self->read_mask, self->buf + self->buf_pos - 4, 4); | ||
} | ||
self->buf_pos = 0; | ||
if ((self->last_flags & FRAME_OPCODE_MASK) >= FRAME_CLOSE) { | ||
|
@@ -176,7 +204,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int | |
|
||
sz = out_sz; | ||
for (byte *p = buf; sz--; p++) { | ||
*p ^= self->mask[self->mask_pos++ & 3]; | ||
*p ^= self->read_mask[self->read_mask_pos++ & 3]; | ||
} | ||
|
||
self->msg_sz -= out_sz; | ||
|
@@ -186,7 +214,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int | |
last_state = self->state; | ||
self->state = FRAME_HEADER; | ||
self->to_recv = 2; | ||
self->mask_pos = 0; | ||
self->read_mask_pos = 0; | ||
self->buf_pos = 0; | ||
|
||
// Handle control frame | ||
|
@@ -218,7 +246,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int | |
STATIC mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { | ||
mp_obj_websocket_t *self = MP_OBJ_TO_PTR(self_in); | ||
assert(size < 0x10000); | ||
byte header[4] = {0x80 | (self->opts & FRAME_OPCODE_MASK)}; | ||
byte header[8] = {0x80 | (self->opts & FRAME_OPCODE_MASK)}; | ||
int hdr_sz; | ||
if (size < 126) { | ||
header[1] = size; | ||
|
@@ -229,6 +257,34 @@ STATIC mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t si | |
header[3] = size & 0xff; | ||
hdr_sz = 4; | ||
} | ||
if (self->do_write_masking != NO_WRITE_MASKING) { | ||
hdr_sz += 4; | ||
header[1] |= 0x80; | ||
if (self->do_write_masking == NORMAL_WRITE_MASKING) { | ||
|
||
// RFC6455 Section 5.3 states that the masking key must be derived | ||
// from a strong source of entropy. The "urandom" module doesn't | ||
// qualify in this regard, but there isn't any cross-platform | ||
// alternative. Fortunately, the purpose of masking is not | ||
// cryptographically motivated. The "urandom" module should be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd remove such a statement, it's subjective. |
||
// seeded though, otherwise upon restart, the same sequence of | ||
// masks will always be used. A seed could be derived from a | ||
// network resource, a network interface's characteristics or | ||
// statistics, or a platform specific resource. Examples of using | ||
// a platform specific resource include reading an ESP8266's | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm all for detailed comments, but specifics of a particular chip in a generic module are superfluous. |
||
// 32-bit Random Number Generator register, or reading consecutive | ||
// values from a floating analog pin. | ||
mp_obj_t dest[3]; | ||
mp_load_method(mp_module_get(MP_QSTR_urandom), MP_QSTR_getrandbits, dest); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code to get a random value should be factored out to a function. |
||
dest[2] = mp_obj_new_int(32); | ||
unsigned int randbits = MP_OBJ_SMALL_INT_VALUE(mp_call_method_n_kw(1, 0, dest)); | ||
for (int i = 0; i < 4; ++i) { | ||
header[hdr_sz - 4 + i] = (randbits >> ((i ^ 3) << 3)) & 0xff; | ||
} | ||
} else if (self->do_write_masking == DEBUG_WRITE_MASKING) { | ||
memcpy(&header[hdr_sz - 4], self->debug_write_mask, 4); | ||
} | ||
} | ||
|
||
mp_obj_t dest[3]; | ||
if (self->opts & BLOCKING_WRITE) { | ||
|
@@ -239,7 +295,15 @@ STATIC mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t si | |
|
||
mp_uint_t out_sz = mp_stream_write_exactly(self->sock, header, hdr_sz, errcode); | ||
if (*errcode == 0) { | ||
out_sz = mp_stream_write_exactly(self->sock, buf, size, errcode); | ||
if (self->do_write_masking == NO_WRITE_MASKING) { | ||
out_sz = mp_stream_write_exactly(self->sock, buf, size, errcode); | ||
} else { | ||
byte masked_buf[size]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really? But size can be 1GB. |
||
for (mp_uint_t i = 0; i < size; ++i) { | ||
masked_buf[i] = ((byte*)buf)[i] ^ header[hdr_sz - 4 + (i & 3)]; | ||
} | ||
out_sz = mp_stream_write_exactly(self->sock, masked_buf, size, errcode); | ||
} | ||
} | ||
|
||
if (self->opts & BLOCKING_WRITE) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be empty qstr.