You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modules-dev@httpd.apache.org by Jacob Champion <ja...@ni.com> on 2015/03/09 19:43:54 UTC
[PATCH 3/5] Pull framing logic into a separate function
mod_websocket_handle_incoming now performs the core logic for
mod_websocket_data_framing. This will let us turn the processing loop
into a combination read/write loop more easily.
The state variables have all been moved into a separate
WebSocketReadState struct.
---
mod_websocket.c | 652 +++++++++++++++++++++++++++++---------------------------
1 file changed, 342 insertions(+), 310 deletions(-)
diff --git a/mod_websocket.c b/mod_websocket.c
index 62be765..1e88ef6 100644
--- a/mod_websocket.c
+++ b/mod_websocket.c
@@ -476,6 +476,327 @@ typedef struct _WebSocketFrameData
unsigned int utf8_state;
} WebSocketFrameData;
+/* Variables that need to persist across calls to mod_websocket_handle_incoming */
+typedef struct
+{
+ int framing_state;
+ unsigned short status_code;
+ /* XXX fin and opcode appear to be duplicated with frame; can they be removed? */
+ unsigned char fin;
+ unsigned char opcode;
+ WebSocketFrameData control_frame;
+ WebSocketFrameData message_frame;
+ WebSocketFrameData *frame;
+ apr_int64_t payload_length;
+ apr_int64_t mask_offset;
+ apr_int64_t extension_bytes_remaining;
+ int payload_length_bytes_remaining;
+ int masking;
+ int mask_index;
+ unsigned char mask[4];
+} WebSocketReadState;
+
+static void mod_websocket_handle_incoming(const WebSocketServer *server,
+ unsigned char *block,
+ apr_int64_t block_size,
+ WebSocketReadState *state,
+ websocket_config_rec *conf,
+ void *plugin_private)
+{
+ apr_int64_t block_offset = 0;
+
+ while (block_offset < block_size) {
+ switch (state->framing_state) {
+ case DATA_FRAMING_START:
+ /*
+ * Since we don't currently support any extensions,
+ * the reserve bits must be 0
+ */
+ if ((FRAME_GET_RSV1(block[block_offset]) != 0) ||
+ (FRAME_GET_RSV2(block[block_offset]) != 0) ||
+ (FRAME_GET_RSV3(block[block_offset]) != 0)) {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ state->fin = FRAME_GET_FIN(block[block_offset]);
+ state->opcode = FRAME_GET_OPCODE(block[block_offset++]);
+
+ state->framing_state = DATA_FRAMING_PAYLOAD_LENGTH;
+
+ if (state->opcode >= 0x8) { /* Control frame */
+ if (state->fin) {
+ state->frame = &state->control_frame;
+ state->frame->opcode = state->opcode;
+ state->frame->utf8_state = UTF8_VALID;
+ }
+ else {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ }
+ else { /* Message frame */
+ state->frame = &state->message_frame;
+ if (state->opcode) {
+ if (state->frame->fin) {
+ state->frame->opcode = state->opcode;
+ state->frame->utf8_state = UTF8_VALID;
+ }
+ else {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ }
+ else if (state->frame->fin ||
+ ((state->opcode = state->frame->opcode) == 0)) {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ state->frame->fin = state->fin;
+ }
+ state->payload_length = 0;
+ state->payload_length_bytes_remaining = 0;
+
+ if (block_offset >= block_size) {
+ break; /* Only break if we need more data */
+ }
+ case DATA_FRAMING_PAYLOAD_LENGTH:
+ state->payload_length = (apr_int64_t)
+ FRAME_GET_PAYLOAD_LEN(block[block_offset]);
+ state->masking = FRAME_GET_MASK(block[block_offset++]);
+
+ if (state->payload_length == 126) {
+ state->payload_length = 0;
+ state->payload_length_bytes_remaining = 2;
+ }
+ else if (state->payload_length == 127) {
+ state->payload_length = 0;
+ state->payload_length_bytes_remaining = 8;
+ }
+ else {
+ state->payload_length_bytes_remaining = 0;
+ }
+ if ((state->masking == 0) || /* Client-side mask is required */
+ ((state->opcode >= 0x8) && /* Control opcodes cannot have a payload larger than 125 bytes */
+ (state->payload_length_bytes_remaining != 0))) {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ else {
+ state->framing_state = DATA_FRAMING_PAYLOAD_LENGTH_EXT;
+ }
+ if (block_offset >= block_size) {
+ break; /* Only break if we need more data */
+ }
+ case DATA_FRAMING_PAYLOAD_LENGTH_EXT:
+ while ((state->payload_length_bytes_remaining > 0) &&
+ (block_offset < block_size)) {
+ state->payload_length *= 256;
+ state->payload_length += block[block_offset++];
+ state->payload_length_bytes_remaining--;
+ }
+ if (state->payload_length_bytes_remaining == 0) {
+ if ((state->payload_length < 0) ||
+ (state->payload_length > conf->payload_limit)) {
+ /* Invalid payload length */
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = (server->state->protocol_version >= 13) ?
+ STATUS_CODE_MESSAGE_TOO_LARGE :
+ STATUS_CODE_RESERVED;
+ break;
+ }
+ else if (state->masking != 0) {
+ state->framing_state = DATA_FRAMING_MASK;
+ }
+ else {
+ state->framing_state = DATA_FRAMING_EXTENSION_DATA;
+ break;
+ }
+ }
+ if (block_offset >= block_size) {
+ break; /* Only break if we need more data */
+ }
+ case DATA_FRAMING_MASK:
+ while ((state->mask_index < 4) && (block_offset < block_size)) {
+ state->mask[state->mask_index++] = block[block_offset++];
+ }
+ if (state->mask_index == 4) {
+ state->framing_state = DATA_FRAMING_EXTENSION_DATA;
+ state->mask_offset = 0;
+ state->mask_index = 0;
+ if ((state->mask[0] == 0) && (state->mask[1] == 0) &&
+ (state->mask[2] == 0) && (state->mask[3] == 0)) {
+ state->masking = 0;
+ }
+ }
+ else {
+ break;
+ }
+ /* Fall through */
+ case DATA_FRAMING_EXTENSION_DATA:
+ /* Deal with extension data when we support them -- FIXME */
+ if (state->extension_bytes_remaining == 0) {
+ if (state->payload_length > 0) {
+ state->frame->application_data = (unsigned char *)
+ realloc(state->frame->application_data,
+ state->frame->application_data_offset +
+ state->payload_length);
+ if (state->frame->application_data == NULL) {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = (server->state->protocol_version >= 13) ?
+ STATUS_CODE_INTERNAL_ERROR :
+ STATUS_CODE_GOING_AWAY;
+ break;
+ }
+ }
+ state->framing_state = DATA_FRAMING_APPLICATION_DATA;
+ }
+ /* Fall through */
+ case DATA_FRAMING_APPLICATION_DATA:
+ {
+ apr_int64_t block_data_length;
+ apr_int64_t block_length = 0;
+ apr_uint64_t application_data_offset =
+ state->frame->application_data_offset;
+ unsigned char *application_data =
+ state->frame->application_data;
+
+ block_length = block_size - block_offset;
+ block_data_length =
+ (state->payload_length >
+ block_length) ? block_length : state->payload_length;
+
+ if (state->masking) {
+ apr_int64_t i;
+
+ if (state->opcode == OPCODE_TEXT) {
+ unsigned int utf8_state = state->frame->utf8_state;
+ unsigned char c;
+
+ for (i = 0; i < block_data_length; i++) {
+ c = block[block_offset++] ^
+ state->mask[state->mask_offset++ & 3];
+ utf8_state =
+ validate_utf8[utf8_state + c];
+ if (utf8_state == UTF8_INVALID) {
+ state->payload_length = block_data_length;
+ break;
+ }
+ application_data
+ [application_data_offset++] = c;
+ }
+ state->frame->utf8_state = utf8_state;
+ }
+ else {
+ /* Need to optimize the unmasking -- FIXME */
+ for (i = 0; i < block_data_length; i++) {
+ application_data
+ [application_data_offset++] =
+ block[block_offset++] ^
+ state->mask[state->mask_offset++ & 3];
+ }
+ }
+ }
+ else if (block_data_length > 0) {
+ memcpy(&application_data[application_data_offset],
+ &block[block_offset], block_data_length);
+ if (state->opcode == OPCODE_TEXT) {
+ apr_int64_t i, application_data_end =
+ application_data_offset +
+ block_data_length;
+ unsigned int utf8_state = state->frame->utf8_state;
+
+ for (i = application_data_offset;
+ i < application_data_end; i++) {
+ utf8_state =
+ validate_utf8[utf8_state +
+ application_data[i]];
+ if (utf8_state == UTF8_INVALID) {
+ state->payload_length = block_data_length;
+ break;
+ }
+ }
+ state->frame->utf8_state = utf8_state;
+ }
+ application_data_offset += block_data_length;
+ block_offset += block_data_length;
+ }
+ state->payload_length -= block_data_length;
+
+ if (state->payload_length == 0) {
+ int message_type = MESSAGE_TYPE_INVALID;
+
+ switch (state->opcode) {
+ case OPCODE_TEXT:
+ if ((state->fin &&
+ (state->frame->utf8_state != UTF8_VALID)) ||
+ (state->frame->utf8_state == UTF8_INVALID)) {
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_INVALID_UTF8;
+ }
+ else {
+ message_type = MESSAGE_TYPE_TEXT;
+ }
+ break;
+ case OPCODE_BINARY:
+ message_type = MESSAGE_TYPE_BINARY;
+ break;
+ case OPCODE_CLOSE:
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_OK;
+ break;
+ case OPCODE_PING:
+ apr_thread_mutex_lock(server->state->mutex);
+ mod_websocket_send_internal(server->state,
+ MESSAGE_TYPE_PONG,
+ application_data,
+ application_data_offset);
+ apr_thread_mutex_unlock(server->state->mutex);
+ break;
+ case OPCODE_PONG:
+ break;
+ default:
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ if (state->fin && (message_type != MESSAGE_TYPE_INVALID)) {
+ conf->plugin->on_message(plugin_private,
+ server, message_type,
+ application_data,
+ application_data_offset);
+ }
+ if (state->framing_state != DATA_FRAMING_CLOSE) {
+ state->framing_state = DATA_FRAMING_START;
+
+ if (state->fin) {
+ if (state->frame->application_data != NULL) {
+ free(state->frame->application_data);
+ state->frame->application_data = NULL;
+ }
+ application_data_offset = 0;
+ }
+ }
+ }
+ state->frame->application_data_offset =
+ application_data_offset;
+ }
+ break;
+ case DATA_FRAMING_CLOSE:
+ block_offset = block_size;
+ break;
+ default:
+ state->framing_state = DATA_FRAMING_CLOSE;
+ state->status_code = STATUS_CODE_PROTOCOL_ERROR;
+ break;
+ }
+ }
+}
+
/*
* The data framing handler requires that the server state mutex is locked by
* the caller upon entering this function. It will be locked when leaving too.
@@ -509,19 +830,19 @@ static void mod_websocket_data_framing(const WebSocketServer *server,
(apr_pollset_create(&pollset, 1, pool, 0) == APR_SUCCESS)) {
unsigned char block[BLOCK_DATA_SIZE];
apr_int64_t block_size;
- apr_int64_t extension_bytes_remaining = 0;
- apr_int64_t payload_length = 0;
- apr_int64_t mask_offset = 0;
- int framing_state = DATA_FRAMING_START;
- int payload_length_bytes_remaining = 0;
- int mask_index = 0, masking = 0;
- unsigned char mask[4] = { 0, 0, 0, 0 };
- unsigned char fin = 0, opcode = 0xFF;
- WebSocketFrameData control_frame = { 0, NULL, 1, 8, UTF8_VALID };
- WebSocketFrameData message_frame = { 0, NULL, 1, 0, UTF8_VALID };
- WebSocketFrameData *frame = &control_frame;
- unsigned short status_code = STATUS_CODE_OK;
unsigned char status_code_buffer[2];
+ WebSocketReadState read_state = { 0 };
+
+ read_state.framing_state = DATA_FRAMING_START;
+ read_state.status_code = STATUS_CODE_OK;
+ read_state.control_frame.fin = 1;
+ read_state.control_frame.opcode = 8;
+ read_state.control_frame.utf8_state = UTF8_VALID;
+ read_state.message_frame.fin = 1;
+ read_state.message_frame.opcode = 0;
+ read_state.message_frame.utf8_state = UTF8_VALID;
+ read_state.frame = &read_state.control_frame;
+ read_state.opcode = 0xFF;
/* Initialize the pollset */
pollfd.p = pool;
@@ -534,8 +855,7 @@ static void mod_websocket_data_framing(const WebSocketServer *server,
state->obb = obb;
apr_thread_mutex_unlock(state->mutex);
- while ((framing_state != DATA_FRAMING_CLOSE)) {
- apr_int64_t block_offset = 0;
+ while ((read_state.framing_state != DATA_FRAMING_CLOSE)) {
apr_status_t rv;
do {
@@ -548,307 +868,19 @@ static void mod_websocket_data_framing(const WebSocketServer *server,
break;
}
- while (block_offset < block_size) {
- switch (framing_state) {
- case DATA_FRAMING_START:
- /*
- * Since we don't currently support any extensions,
- * the reserve bits must be 0
- */
- if ((FRAME_GET_RSV1(block[block_offset]) != 0) ||
- (FRAME_GET_RSV2(block[block_offset]) != 0) ||
- (FRAME_GET_RSV3(block[block_offset]) != 0)) {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- fin = FRAME_GET_FIN(block[block_offset]);
- opcode = FRAME_GET_OPCODE(block[block_offset++]);
-
- framing_state = DATA_FRAMING_PAYLOAD_LENGTH;
-
- if (opcode >= 0x8) { /* Control frame */
- if (fin) {
- frame = &control_frame;
- frame->opcode = opcode;
- frame->utf8_state = UTF8_VALID;
- }
- else {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- }
- else { /* Message frame */
- frame = &message_frame;
- if (opcode) {
- if (frame->fin) {
- frame->opcode = opcode;
- frame->utf8_state = UTF8_VALID;
- }
- else {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- }
- else if (frame->fin ||
- ((opcode = frame->opcode) == 0)) {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- frame->fin = fin;
- }
- payload_length = 0;
- payload_length_bytes_remaining = 0;
-
- if (block_offset >= block_size) {
- break; /* Only break if we need more data */
- }
- case DATA_FRAMING_PAYLOAD_LENGTH:
- payload_length = (apr_int64_t)
- FRAME_GET_PAYLOAD_LEN(block[block_offset]);
- masking = FRAME_GET_MASK(block[block_offset++]);
-
- if (payload_length == 126) {
- payload_length = 0;
- payload_length_bytes_remaining = 2;
- }
- else if (payload_length == 127) {
- payload_length = 0;
- payload_length_bytes_remaining = 8;
- }
- else {
- payload_length_bytes_remaining = 0;
- }
- if ((masking == 0) || /* Client-side mask is required */
- ((opcode >= 0x8) && /* Control opcodes cannot have a payload larger than 125 bytes */
- (payload_length_bytes_remaining != 0))) {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- else {
- framing_state = DATA_FRAMING_PAYLOAD_LENGTH_EXT;
- }
- if (block_offset >= block_size) {
- break; /* Only break if we need more data */
- }
- case DATA_FRAMING_PAYLOAD_LENGTH_EXT:
- while ((payload_length_bytes_remaining > 0) &&
- (block_offset < block_size)) {
- payload_length *= 256;
- payload_length += block[block_offset++];
- payload_length_bytes_remaining--;
- }
- if (payload_length_bytes_remaining == 0) {
- if ((payload_length < 0) ||
- (payload_length > conf->payload_limit)) {
- /* Invalid payload length */
- framing_state = DATA_FRAMING_CLOSE;
- status_code = (state->protocol_version >= 13) ?
- STATUS_CODE_MESSAGE_TOO_LARGE :
- STATUS_CODE_RESERVED;
- break;
- }
- else if (masking != 0) {
- framing_state = DATA_FRAMING_MASK;
- }
- else {
- framing_state = DATA_FRAMING_EXTENSION_DATA;
- break;
- }
- }
- if (block_offset >= block_size) {
- break; /* Only break if we need more data */
- }
- case DATA_FRAMING_MASK:
- while ((mask_index < 4) && (block_offset < block_size)) {
- mask[mask_index++] = block[block_offset++];
- }
- if (mask_index == 4) {
- framing_state = DATA_FRAMING_EXTENSION_DATA;
- mask_offset = 0;
- mask_index = 0;
- if ((mask[0] == 0) && (mask[1] == 0) &&
- (mask[2] == 0) && (mask[3] == 0)) {
- masking = 0;
- }
- }
- else {
- break;
- }
- /* Fall through */
- case DATA_FRAMING_EXTENSION_DATA:
- /* Deal with extension data when we support them -- FIXME */
- if (extension_bytes_remaining == 0) {
- if (payload_length > 0) {
- frame->application_data = (unsigned char *)
- realloc(frame->application_data,
- frame->application_data_offset +
- payload_length);
- if (frame->application_data == NULL) {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = (state->protocol_version >= 13) ?
- STATUS_CODE_INTERNAL_ERROR :
- STATUS_CODE_GOING_AWAY;
- break;
- }
- }
- framing_state = DATA_FRAMING_APPLICATION_DATA;
- }
- /* Fall through */
- case DATA_FRAMING_APPLICATION_DATA:
- {
- apr_int64_t block_data_length;
- apr_int64_t block_length = 0;
- apr_uint64_t application_data_offset =
- frame->application_data_offset;
- unsigned char *application_data =
- frame->application_data;
-
- block_length = block_size - block_offset;
- block_data_length =
- (payload_length >
- block_length) ? block_length : payload_length;
-
- if (masking) {
- apr_int64_t i;
-
- if (opcode == OPCODE_TEXT) {
- unsigned int utf8_state = frame->utf8_state;
- unsigned char c;
-
- for (i = 0; i < block_data_length; i++) {
- c = block[block_offset++] ^
- mask[mask_offset++ & 3];
- utf8_state =
- validate_utf8[utf8_state + c];
- if (utf8_state == UTF8_INVALID) {
- payload_length = block_data_length;
- break;
- }
- application_data
- [application_data_offset++] = c;
- }
- frame->utf8_state = utf8_state;
- }
- else {
- /* Need to optimize the unmasking -- FIXME */
- for (i = 0; i < block_data_length; i++) {
- application_data
- [application_data_offset++] =
- block[block_offset++] ^
- mask[mask_offset++ & 3];
- }
- }
- }
- else if (block_data_length > 0) {
- memcpy(&application_data[application_data_offset],
- &block[block_offset], block_data_length);
- if (opcode == OPCODE_TEXT) {
- apr_int64_t i, application_data_end =
- application_data_offset +
- block_data_length;
- unsigned int utf8_state = frame->utf8_state;
-
- for (i = application_data_offset;
- i < application_data_end; i++) {
- utf8_state =
- validate_utf8[utf8_state +
- application_data[i]];
- if (utf8_state == UTF8_INVALID) {
- payload_length = block_data_length;
- break;
- }
- }
- frame->utf8_state = utf8_state;
- }
- application_data_offset += block_data_length;
- block_offset += block_data_length;
- }
- payload_length -= block_data_length;
-
- if (payload_length == 0) {
- int message_type = MESSAGE_TYPE_INVALID;
-
- switch (opcode) {
- case OPCODE_TEXT:
- if ((fin &&
- (frame->utf8_state != UTF8_VALID)) ||
- (frame->utf8_state == UTF8_INVALID)) {
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_INVALID_UTF8;
- }
- else {
- message_type = MESSAGE_TYPE_TEXT;
- }
- break;
- case OPCODE_BINARY:
- message_type = MESSAGE_TYPE_BINARY;
- break;
- case OPCODE_CLOSE:
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_OK;
- break;
- case OPCODE_PING:
- apr_thread_mutex_lock(state->mutex);
- mod_websocket_send_internal(state,
- MESSAGE_TYPE_PONG,
- application_data,
- application_data_offset);
- apr_thread_mutex_unlock(state->mutex);
- break;
- case OPCODE_PONG:
- break;
- default:
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- if (fin && (message_type != MESSAGE_TYPE_INVALID)) {
- conf->plugin->on_message(plugin_private,
- server, message_type,
- application_data,
- application_data_offset);
- }
- if (framing_state != DATA_FRAMING_CLOSE) {
- framing_state = DATA_FRAMING_START;
-
- if (fin) {
- if (frame->application_data != NULL) {
- free(frame->application_data);
- frame->application_data = NULL;
- }
- application_data_offset = 0;
- }
- }
- }
- frame->application_data_offset =
- application_data_offset;
- }
- break;
- case DATA_FRAMING_CLOSE:
- block_offset = block_size;
- break;
- default:
- framing_state = DATA_FRAMING_CLOSE;
- status_code = STATUS_CODE_PROTOCOL_ERROR;
- break;
- }
- }
+ mod_websocket_handle_incoming(server, block, block_size,
+ &read_state, conf, plugin_private);
}
- if (message_frame.application_data != NULL) {
- free(message_frame.application_data);
+ if (read_state.message_frame.application_data != NULL) {
+ free(read_state.message_frame.application_data);
}
- if (control_frame.application_data != NULL) {
- free(control_frame.application_data);
+ if (read_state.control_frame.application_data != NULL) {
+ free(read_state.control_frame.application_data);
}
/* Send server-side closing handshake */
- status_code_buffer[0] = (status_code >> 8) & 0xFF;
- status_code_buffer[1] = status_code & 0xFF;
+ status_code_buffer[0] = (read_state.status_code >> 8) & 0xFF;
+ status_code_buffer[1] = read_state.status_code & 0xFF;
apr_thread_mutex_lock(state->mutex);
mod_websocket_send_internal(state, MESSAGE_TYPE_CLOSE,
--
2.1.1