Skip to content

Commit 4f43dba

Browse files
authored
improve websocket error reporting (#416)
- Add new error `AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR` - previously, websocket was using `AWS_ERROR_HTTP_PROTOCOL_ERROR`, which makes it look like an HTTP protocol error - Add logging for errors from websocket encoder & decoder - Remove some TODOs that have been taken care of
1 parent 50201a7 commit 4f43dba

File tree

6 files changed

+55
-34
lines changed

6 files changed

+55
-34
lines changed

include/aws/http/http.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ enum aws_http_errors {
5454
AWS_ERROR_HTTP_STREAM_MANAGER_SHUTTING_DOWN,
5555
AWS_ERROR_HTTP_STREAM_MANAGER_CONNECTION_ACQUIRE_FAILURE,
5656
AWS_ERROR_HTTP_STREAM_MANAGER_UNEXPECTED_HTTP_VERSION,
57+
AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR,
5758

5859
AWS_ERROR_HTTP_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_HTTP_PACKAGE_ID)
5960
};

source/http.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ static struct aws_error_info s_errors[] = {
139139
AWS_DEFINE_ERROR_INFO_HTTP(
140140
AWS_ERROR_HTTP_STREAM_MANAGER_UNEXPECTED_HTTP_VERSION,
141141
"Stream acquisition failed because stream manager got an unexpected version of HTTP connection"),
142+
AWS_DEFINE_ERROR_INFO_HTTP(
143+
AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR,
144+
"Websocket protocol rules violated by peer"),
142145
};
143146
/* clang-format on */
144147

source/websocket.c

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,8 @@
2424

2525
/* TODO: echo payload of peer CLOSE */
2626

27-
/* TODO: Can we be sure socket will always mark aws_io_messages as complete? */
28-
2927
/* TODO: If something goes wrong during normal shutdown, do I change the error_code? */
3028

31-
/* TODO: Delayed payload works by sending 0-size io_msgs down pipe and trying again when they're compele.
32-
* Do something more efficient? */
33-
34-
/* TODO: don't fire send completion until data written to socket .. which also means delaying on_shutdown cb */
35-
36-
/* TODO: stop using the HTTP_PARSE error, give websocket its own error */
37-
3829
struct outgoing_frame {
3930
struct aws_websocket_send_frame_options def;
4031
struct aws_linked_list_node node;
@@ -249,8 +240,6 @@ static void s_unlock_synced_data(struct aws_websocket *websocket) {
249240
}
250241

251242
struct aws_websocket *aws_websocket_handler_new(const struct aws_websocket_handler_options *options) {
252-
/* TODO: validate options */
253-
254243
struct aws_channel_slot *slot = NULL;
255244
struct aws_websocket *websocket = NULL;
256245
int err;
@@ -722,8 +711,7 @@ static void s_try_write_outgoing_frames(struct aws_websocket *websocket) {
722711
return;
723712
}
724713

725-
/* Prepare to send aws_io_message up the channel.
726-
* Note that the write-completion callback may fire before send_message() returns */
714+
/* Prepare to send aws_io_message up the channel. */
727715

728716
/* If CLOSE frame was written, that's the last data we'll write */
729717
if (wrote_close_frame) {
@@ -863,7 +851,6 @@ static int s_handler_process_write_message(
863851

864852
int err = s_send_frame(websocket, &options, false);
865853
if (err) {
866-
/* TODO: mqtt handler needs to clean up messsages that fail to send. */
867854
return AWS_OP_ERR;
868855
}
869856

source/websocket_decoder.c

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#include <aws/http/private/websocket_decoder.h>
77

8-
/* TODO: decoder logging */
8+
#include <inttypes.h>
99

1010
typedef int(state_fn)(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data);
1111

@@ -46,7 +46,12 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
4646
case AWS_WEBSOCKET_OPCODE_PONG:
4747
break;
4848
default:
49-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
49+
AWS_LOGF_ERROR(
50+
AWS_LS_HTTP_WEBSOCKET,
51+
"id=%p: Received frame with unknown opcode 0x%" PRIx8,
52+
(void *)decoder->user_data,
53+
decoder->current_frame.opcode);
54+
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
5055
}
5156

5257
/* RFC-6455 Section 5.2 Fragmentation
@@ -61,15 +66,23 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
6166
bool is_continuation_frame = AWS_WEBSOCKET_OPCODE_CONTINUATION == decoder->current_frame.opcode;
6267

6368
if (decoder->expecting_continuation_data_frame != is_continuation_frame) {
64-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
69+
AWS_LOGF_ERROR(
70+
AWS_LS_HTTP_WEBSOCKET,
71+
"id=%p: Fragmentation error. Received start of new message before end of previous message",
72+
(void *)decoder->user_data);
73+
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
6574
}
6675

6776
decoder->expecting_continuation_data_frame = !decoder->current_frame.fin;
6877

6978
} else {
7079
/* Control frames themselves MUST NOT be fragmented. */
7180
if (!decoder->current_frame.fin) {
72-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
81+
AWS_LOGF_ERROR(
82+
AWS_LS_HTTP_WEBSOCKET,
83+
"id=%p: Received fragmented control frame. This is illegal",
84+
(void *)decoder->user_data);
85+
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
7386
}
7487
}
7588

@@ -150,21 +163,17 @@ static int s_state_extended_length(struct aws_websocket_decoder *decoder, struct
150163
struct aws_byte_cursor cache_cursor = aws_byte_cursor_from_array(decoder->state_cache, total_bytes_extended_length);
151164
if (total_bytes_extended_length == 2) {
152165
uint16_t val;
153-
if (!aws_byte_cursor_read_be16(&cache_cursor, &val)) {
154-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
155-
}
156-
166+
aws_byte_cursor_read_be16(&cache_cursor, &val);
157167
decoder->current_frame.payload_length = val;
158168
} else {
159-
if (!aws_byte_cursor_read_be64(&cache_cursor, &decoder->current_frame.payload_length)) {
160-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
161-
}
169+
aws_byte_cursor_read_be64(&cache_cursor, &decoder->current_frame.payload_length);
162170
}
163171

164172
if (decoder->current_frame.payload_length < min_acceptable_value ||
165173
decoder->current_frame.payload_length > max_acceptable_value) {
166174

167-
return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
175+
AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET, "id=%p: Failed to decode payload length", (void *)decoder->user_data);
176+
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
168177
}
169178

170179
decoder->state = AWS_WEBSOCKET_DECODER_STATE_MASKING_KEY_CHECK;

source/websocket_encoder.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
#include <aws/http/private/websocket_encoder.h>
77

8-
/* TODO: encoder logging */
9-
/* TODO: implement masking function in aws-c-common */
10-
/* TODO: use nospec advance? */
8+
#include <inttypes.h>
119

1210
typedef int(state_fn)(struct aws_websocket_encoder *encoder, struct aws_byte_buf *out_buf);
1311

@@ -219,6 +217,11 @@ static int s_state_payload(struct aws_websocket_encoder *encoder, struct aws_byt
219217
} else {
220218
/* Some more error-checking... */
221219
if (encoder->state_bytes_processed > encoder->frame.payload_length) {
220+
AWS_LOGF_ERROR(
221+
AWS_LS_HTTP_WEBSOCKET,
222+
"id=%p: Outgoing stream has exceeded stated payload length of %" PRIu64,
223+
(void *)encoder->user_data,
224+
encoder->frame.payload_length);
222225
return aws_raise_error(AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT);
223226
}
224227
}
@@ -276,11 +279,20 @@ int aws_websocket_encoder_start_frame(struct aws_websocket_encoder *encoder, con
276279

277280
/* Opcode must fit in 4bits */
278281
if (frame->opcode != (frame->opcode & 0x0F)) {
282+
AWS_LOGF_ERROR(
283+
AWS_LS_HTTP_WEBSOCKET,
284+
"id=%p: Outgoing frame has unknown opcode 0x%" PRIx8,
285+
(void *)encoder->user_data,
286+
frame->opcode);
279287
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
280288
}
281289

282290
/* High bit of 8byte length must be clear */
283291
if (frame->payload_length > AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MAX_VALUE) {
292+
AWS_LOGF_ERROR(
293+
AWS_LS_HTTP_WEBSOCKET,
294+
"id=%p: Outgoing frame's payload length exceeds the max",
295+
(void *)encoder->user_data);
284296
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
285297
}
286298

@@ -294,13 +306,22 @@ int aws_websocket_encoder_start_frame(struct aws_websocket_encoder *encoder, con
294306
bool is_continuation_frame = (AWS_WEBSOCKET_OPCODE_CONTINUATION == frame->opcode);
295307

296308
if (encoder->expecting_continuation_data_frame != is_continuation_frame) {
309+
AWS_LOGF_ERROR(
310+
AWS_LS_HTTP_WEBSOCKET,
311+
"id=%p: Fragmentation error. Outgoing frame starts a new message but previous message has not ended",
312+
(void *)encoder->user_data);
297313
return aws_raise_error(AWS_ERROR_INVALID_STATE);
298314
}
299315

300316
keep_expecting_continuation_data_frame = !frame->fin;
301317
} else {
302318
/* Control frames themselves MUST NOT be fragmented. */
303319
if (!frame->fin) {
320+
AWS_LOGF_ERROR(
321+
AWS_LS_HTTP_WEBSOCKET,
322+
"id=%p: It is illegal to send a fragmented control frame",
323+
(void *)encoder->user_data);
324+
304325
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
305326
}
306327
}

tests/test_websocket_decoder.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ DECODER_TEST_CASE(websocket_decoder_extended_length_2byte) {
377377
aws_raise_error(-1); /* overwrite last-error */
378378

379379
ASSERT_FAILS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
380-
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, aws_last_error());
380+
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR, aws_last_error());
381381
}
382382
}
383383

@@ -448,7 +448,7 @@ DECODER_TEST_CASE(websocket_decoder_extended_length_8byte) {
448448
aws_raise_error(-1); /* overwrite last-error */
449449

450450
ASSERT_FAILS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
451-
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, aws_last_error());
451+
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR, aws_last_error());
452452
}
453453
}
454454

@@ -530,7 +530,7 @@ DECODER_TEST_CASE(websocket_decoder_fail_on_unknown_opcode) {
530530
bool frame_complete;
531531
struct aws_byte_cursor input_cursor = aws_byte_cursor_from_array(input, sizeof(input));
532532
ASSERT_FAILS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
533-
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, aws_last_error());
533+
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR, aws_last_error());
534534

535535
ASSERT_SUCCESS(s_decoder_tester_clean_up(&tester));
536536
return AWS_OP_SUCCESS;
@@ -626,7 +626,7 @@ DECODER_TEST_CASE(websocket_decoder_fail_on_bad_fragmentation) {
626626
struct aws_byte_cursor input_cursor = aws_byte_cursor_from_array(input, sizeof(input));
627627
ASSERT_SUCCESS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
628628
ASSERT_FAILS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
629-
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, aws_last_error());
629+
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR, aws_last_error());
630630

631631
ASSERT_SUCCESS(s_decoder_tester_clean_up(&tester));
632632
return AWS_OP_SUCCESS;
@@ -646,7 +646,7 @@ DECODER_TEST_CASE(websocket_decoder_control_frame_cannot_be_fragmented) {
646646
bool frame_complete;
647647
struct aws_byte_cursor input_cursor = aws_byte_cursor_from_array(input, sizeof(input));
648648
ASSERT_FAILS(aws_websocket_decoder_process(&tester.decoder, &input_cursor, &frame_complete));
649-
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, aws_last_error());
649+
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR, aws_last_error());
650650

651651
ASSERT_SUCCESS(s_decoder_tester_clean_up(&tester));
652652
return AWS_OP_SUCCESS;

0 commit comments

Comments
 (0)