Skip to content

Commit 095acca

Browse files
authored
Websocket frame encoder (#36)
1 parent 2324325 commit 095acca

12 files changed

+1275
-87
lines changed

include/aws/http/http.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
enum aws_http_errors {
2424
AWS_ERROR_HTTP_UNKNOWN = 0x0800,
2525
AWS_ERROR_HTTP_PARSE,
26-
AWS_ERROR_HTTP_INVALID_PARSE_STATE,
2726
AWS_ERROR_HTTP_CONNECTION_CLOSED,
2827
AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL,
2928
AWS_ERROR_HTTP_REACTION_REQUIRED,
3029
AWS_ERROR_HTTP_DATA_NOT_AVAILABLE,
30+
AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT,
3131
AWS_ERROR_HTTP_END_RANGE = 0x0C00,
3232
};
3333

include/aws/http/private/websocket_decoder.h

+2-31
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,7 @@
1616
* permissions and limitations under the License.
1717
*/
1818

19-
#include <aws/http/http.h>
20-
21-
enum aws_websocket_opcode {
22-
AWS_WEBSOCKET_OPCODE_CONTINUATION = 0x0,
23-
AWS_WEBSOCKET_OPCODE_TEXT = 0x1,
24-
AWS_WEBSOCKET_OPCODE_DATA = 0x2,
25-
AWS_WEBSOCKET_OPCODE_CLOSE = 0x8,
26-
AWS_WEBSOCKET_OPCODE_PING = 0x9,
27-
AWS_WEBSOCKET_OPCODE_PONG = 0xA,
28-
};
29-
30-
/**
31-
* Full contents of a websocket frame, excluding the payload.
32-
*/
33-
struct aws_websocket_frame {
34-
bool fin;
35-
bool rsv[3];
36-
bool masked;
37-
uint8_t opcode;
38-
uint64_t payload_length;
39-
uint8_t masking_key[4];
40-
};
19+
#include <aws/http/private/websocket_impl.h>
4120

4221
/* Called when the non-payload portion of a frame has been decoded. */
4322
typedef int(aws_websocket_decoder_frame_fn)(const struct aws_websocket_frame *frame, void *user_data);
@@ -67,21 +46,13 @@ struct aws_websocket_decoder {
6746

6847
struct aws_websocket_frame current_frame; /* Data about current frame being decoded */
6948

70-
bool expecting_continuation_data_frames; /* True when the next data frame must be CONTINUATION frame */
49+
bool expecting_continuation_data_frame; /* True when the next data frame must be CONTINUATION frame */
7150

7251
void *user_data;
7352
aws_websocket_decoder_frame_fn *on_frame;
7453
aws_websocket_decoder_payload_fn *on_payload;
7554
};
7655

77-
/**
78-
* Return true if opcode is for a data frame, false if opcode if for a control frame.
79-
*/
80-
AWS_STATIC_IMPL
81-
bool aws_websocket_is_data_frame(uint8_t opcode) {
82-
return !(opcode & 0x08); /* RFC-6455 Section 5.6: Most significant bit of (4 bit) data frame opcode is 0 */
83-
}
84-
8556
AWS_EXTERN_C_BEGIN
8657

8758
AWS_HTTP_API
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#ifndef AWS_HTTP_WEBSOCKET_ENCODER_H
2+
#define AWS_HTTP_WEBSOCKET_ENCODER_H
3+
4+
/*
5+
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License").
8+
* You may not use this file except in compliance with the License.
9+
* A copy of the License is located at
10+
*
11+
* http://aws.amazon.com/apache2.0
12+
*
13+
* or in the "license" file accompanying this file. This file is distributed
14+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
15+
* express or implied. See the License for the specific language governing
16+
* permissions and limitations under the License.
17+
*/
18+
19+
#include <aws/http/private/websocket_impl.h>
20+
21+
typedef int(aws_websocket_encoder_payload_fn)(struct aws_byte_buf *out_buf, bool *out_done, void *user_data);
22+
23+
enum aws_websocket_encoder_state {
24+
AWS_WEBSOCKET_ENCODER_STATE_INIT,
25+
AWS_WEBSOCKET_ENCODER_STATE_OPCODE_BYTE,
26+
AWS_WEBSOCKET_ENCODER_STATE_LENGTH_BYTE,
27+
AWS_WEBSOCKET_ENCODER_STATE_EXTENDED_LENGTH,
28+
AWS_WEBSOCKET_ENCODER_STATE_MASKING_KEY_CHECK,
29+
AWS_WEBSOCKET_ENCODER_STATE_MASKING_KEY,
30+
AWS_WEBSOCKET_ENCODER_STATE_PAYLOAD_CHECK,
31+
AWS_WEBSOCKET_ENCODER_STATE_PAYLOAD,
32+
AWS_WEBSOCKET_ENCODER_STATE_DONE,
33+
};
34+
35+
struct aws_websocket_encoder {
36+
enum aws_websocket_encoder_state state;
37+
uint64_t state_bytes_processed;
38+
struct aws_websocket_frame frame;
39+
bool is_frame_in_progress;
40+
41+
/* True when the next data frame must be a CONTINUATION frame */
42+
bool expecting_continuation_data_frame;
43+
44+
void *user_data;
45+
aws_websocket_encoder_payload_fn *stream_outgoing_payload;
46+
};
47+
48+
AWS_EXTERN_C_BEGIN
49+
50+
AWS_HTTP_API
51+
void aws_websocket_encoder_init(
52+
struct aws_websocket_encoder *encoder,
53+
aws_websocket_encoder_payload_fn *stream_outgoing_payload,
54+
void *user_data);
55+
56+
AWS_HTTP_API
57+
int aws_websocket_encoder_start_frame(struct aws_websocket_encoder *encoder, const struct aws_websocket_frame *frame);
58+
59+
AWS_HTTP_API
60+
bool aws_websocket_encoder_is_frame_in_progress(const struct aws_websocket_encoder *encoder);
61+
62+
AWS_HTTP_API
63+
int aws_websocket_encoder_process(struct aws_websocket_encoder *encoder, struct aws_byte_buf *out_buf);
64+
65+
AWS_EXTERN_C_END
66+
67+
#endif /* AWS_HTTP_WEBSOCKET_ENCODER_H */
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#ifndef AWS_HTTP_WEBSOCKET_IMPL_H
2+
#define AWS_HTTP_WEBSOCKET_IMPL_H
3+
4+
/*
5+
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License").
8+
* You may not use this file except in compliance with the License.
9+
* A copy of the License is located at
10+
*
11+
* http://aws.amazon.com/apache2.0
12+
*
13+
* or in the "license" file accompanying this file. This file is distributed
14+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
15+
* express or implied. See the License for the specific language governing
16+
* permissions and limitations under the License.
17+
*/
18+
19+
#include <aws/http/http.h>
20+
21+
enum aws_websocket_opcode {
22+
AWS_WEBSOCKET_OPCODE_CONTINUATION = 0x0,
23+
AWS_WEBSOCKET_OPCODE_TEXT = 0x1,
24+
AWS_WEBSOCKET_OPCODE_BINARY = 0x2,
25+
AWS_WEBSOCKET_OPCODE_CLOSE = 0x8,
26+
AWS_WEBSOCKET_OPCODE_PING = 0x9,
27+
AWS_WEBSOCKET_OPCODE_PONG = 0xA,
28+
};
29+
30+
/* RFC-6455 Section 5.2 Base Framing Protocol
31+
* Payload length: 7 bits, 7+16 bits, or 7+64 bits
32+
*
33+
* The length of the "Payload data", in bytes: if 0-125, that is the
34+
* payload length. If 126, the following 2 bytes interpreted as a
35+
* 16-bit unsigned integer are the payload length. If 127, the
36+
* following 8 bytes interpreted as a 64-bit unsigned integer (the
37+
* most significant bit MUST be 0) are the payload length. Multibyte
38+
* length quantities are expressed in network byte order. Note that
39+
* in all cases, the minimal number of bytes MUST be used to encode
40+
* the length, for example, the length of a 124-byte-long string
41+
* can't be encoded as the sequence 126, 0, 124. The payload length
42+
* is the length of the "Extension data" + the length of the
43+
* "Application data". The length of the "Extension data" may be
44+
* zero, in which case the payload length is the length of the
45+
* "Application data".
46+
*/
47+
#define AWS_WEBSOCKET_7BIT_VALUE_FOR_2BYTE_EXTENDED_LENGTH 126
48+
#define AWS_WEBSOCKET_7BIT_VALUE_FOR_8BYTE_EXTENDED_LENGTH 127
49+
50+
#define AWS_WEBSOCKET_2BYTE_EXTENDED_LENGTH_MIN_VALUE AWS_WEBSOCKET_7BIT_VALUE_FOR_2BYTE_EXTENDED_LENGTH
51+
#define AWS_WEBSOCKET_2BYTE_EXTENDED_LENGTH_MAX_VALUE 0x000000000000FFFF
52+
53+
#define AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MIN_VALUE 0x0000000000010000
54+
#define AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MAX_VALUE 0x7FFFFFFFFFFFFFFF
55+
56+
/**
57+
* Full contents of a websocket frame, excluding the payload.
58+
*/
59+
struct aws_websocket_frame {
60+
bool fin;
61+
bool rsv[3];
62+
bool masked;
63+
uint8_t opcode;
64+
uint64_t payload_length;
65+
uint8_t masking_key[4];
66+
};
67+
68+
/**
69+
* Return true if opcode is for a data frame, false if opcode if for a control frame.
70+
*/
71+
AWS_STATIC_IMPL
72+
bool aws_websocket_is_data_frame(uint8_t opcode) {
73+
return !(opcode & 0x08); /* RFC-6455 Section 5.6: Most significant bit of (4 bit) data frame opcode is 0 */
74+
}
75+
76+
#endif /* AWS_HTTP_WEBSOCKET_IMPL_H */

source/connection_h1.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ static int s_handler_process_read_message(
12971297
"connection.",
12981298
(void *)&connection->base);
12991299

1300-
aws_raise_error(AWS_ERROR_HTTP_INVALID_PARSE_STATE);
1300+
aws_raise_error(AWS_ERROR_INVALID_STATE);
13011301
goto error;
13021302
}
13031303

source/http.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ static struct aws_error_info s_errors[] = {
3737
AWS_DEFINE_ERROR_INFO_HTTP(
3838
AWS_ERROR_HTTP_PARSE,
3939
"Encountered an unexpected form when parsing an http message."),
40-
AWS_DEFINE_ERROR_INFO_HTTP(
41-
AWS_ERROR_HTTP_INVALID_PARSE_STATE,
42-
"Decoding/parsing was ran while the decoder object was in a poor state -- make sure to properly check for error codes before running the decoder."),
4340
AWS_DEFINE_ERROR_INFO_HTTP(
4441
AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL,
4542
"An unsupported protocol was encountered."),
@@ -55,6 +52,9 @@ static struct aws_error_info s_errors[] = {
5552
AWS_DEFINE_ERROR_INFO_HTTP(
5653
AWS_ERROR_HTTP_CONNECTION_CLOSED,
5754
"Message not sent, as the connection has closed."),
55+
AWS_DEFINE_ERROR_INFO_HTTP(
56+
AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT,
57+
"Amount of data streamed out does not match the previously declared length."),
5858
};
5959
/* clang-format on */
6060

source/websocket_decoder.c

+26-20
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515

1616
#include <aws/http/private/websocket_decoder.h>
1717

18-
/* If 7bit payload length has these values, then the next few bytes contain the real payload length */
19-
#define VALUE_FOR_2BYTE_EXTENDED_LENGTH 126
20-
#define VALUE_FOR_8BYTE_EXTENDED_LENGTH 127
18+
/* TODO: decoder logging */
2119

2220
typedef int(state_fn)(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data);
2321

@@ -47,6 +45,20 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
4745
/* next 4 bits are opcode */
4846
decoder->current_frame.opcode = byte & 0x0F;
4947

48+
/* RFC-6455 Section 5.2 - Opcode
49+
* If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_. */
50+
switch (decoder->current_frame.opcode) {
51+
case AWS_WEBSOCKET_OPCODE_CONTINUATION:
52+
case AWS_WEBSOCKET_OPCODE_TEXT:
53+
case AWS_WEBSOCKET_OPCODE_BINARY:
54+
case AWS_WEBSOCKET_OPCODE_CLOSE:
55+
case AWS_WEBSOCKET_OPCODE_PING:
56+
case AWS_WEBSOCKET_OPCODE_PONG:
57+
break;
58+
default:
59+
return aws_raise_error(AWS_ERROR_HTTP_PARSE);
60+
}
61+
5062
/* RFC-6455 Section 5.2 Fragmentation
5163
*
5264
* Data frames with the FIN bit clear are considered fragmented and must be followed by
@@ -58,11 +70,11 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
5870
if (aws_websocket_is_data_frame(decoder->current_frame.opcode)) {
5971
bool is_continuation_frame = AWS_WEBSOCKET_OPCODE_CONTINUATION == decoder->current_frame.opcode;
6072

61-
if (decoder->expecting_continuation_data_frames != is_continuation_frame) {
73+
if (decoder->expecting_continuation_data_frame != is_continuation_frame) {
6274
return aws_raise_error(AWS_ERROR_HTTP_PARSE);
6375
}
6476

65-
decoder->expecting_continuation_data_frames = !decoder->current_frame.fin;
77+
decoder->expecting_continuation_data_frame = !decoder->current_frame.fin;
6678

6779
} else {
6880
/* Control frames themselves MUST NOT be fragmented. */
@@ -90,7 +102,7 @@ static int s_state_length_byte(struct aws_websocket_decoder *decoder, struct aws
90102
/* remaining 7 bits are payload length */
91103
decoder->current_frame.payload_length = byte & 0x7F;
92104

93-
if (decoder->current_frame.payload_length >= VALUE_FOR_2BYTE_EXTENDED_LENGTH) {
105+
if (decoder->current_frame.payload_length >= AWS_WEBSOCKET_7BIT_VALUE_FOR_2BYTE_EXTENDED_LENGTH) {
94106
/* If 7bit payload length has a high value, then the next few bytes contain the real payload length */
95107
decoder->state_bytes_processed = 0;
96108
decoder->state = AWS_WEBSOCKET_DECODER_STATE_EXTENDED_LENGTH;
@@ -108,27 +120,21 @@ static int s_state_extended_length(struct aws_websocket_decoder *decoder, struct
108120
return AWS_OP_SUCCESS;
109121
}
110122

111-
/* RFC-6455 Section 5.2 Base Framing Protocol - Payload length
112-
* 1) It is a crime to use more bytes than necessary to encode length.
113-
* > in all cases, the minimal number of bytes MUST be used to encode
114-
* > the length, for example, the length of a 124-byte-long string
115-
* > can't be encoded as the sequence 126, 0, 124
116-
* 2) It is a crime to use most-significant bit on 8 byte payloads.
117-
* > If 127, the following 8 bytes interpreted as a 64-bit unsigned integer
118-
* > (the most significant bit MUST be 0) are the payload length */
123+
/* The 7bit payload value loaded during the previous state indicated that
124+
* actual payload length is encoded across the next 2 or 8 bytes. */
119125
uint8_t total_bytes_extended_length;
120126
uint64_t min_acceptable_value;
121127
uint64_t max_acceptable_value;
122-
if (decoder->current_frame.payload_length == VALUE_FOR_2BYTE_EXTENDED_LENGTH) {
128+
if (decoder->current_frame.payload_length == AWS_WEBSOCKET_7BIT_VALUE_FOR_2BYTE_EXTENDED_LENGTH) {
123129
total_bytes_extended_length = 2;
124-
min_acceptable_value = VALUE_FOR_2BYTE_EXTENDED_LENGTH;
125-
max_acceptable_value = UINT16_MAX;
130+
min_acceptable_value = AWS_WEBSOCKET_2BYTE_EXTENDED_LENGTH_MIN_VALUE;
131+
max_acceptable_value = AWS_WEBSOCKET_2BYTE_EXTENDED_LENGTH_MAX_VALUE;
126132
} else {
127-
assert(decoder->current_frame.payload_length == VALUE_FOR_8BYTE_EXTENDED_LENGTH);
133+
assert(decoder->current_frame.payload_length == AWS_WEBSOCKET_7BIT_VALUE_FOR_8BYTE_EXTENDED_LENGTH);
128134

129135
total_bytes_extended_length = 8;
130-
min_acceptable_value = UINT16_MAX + 1;
131-
max_acceptable_value = 0x7FFFFFFFFFFFFFFFULL;
136+
min_acceptable_value = AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MIN_VALUE;
137+
max_acceptable_value = AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MAX_VALUE;
132138
}
133139

134140
/* Copy bytes of extended-length to state_cache, we'll process them later.*/

0 commit comments

Comments
 (0)