Skip to content

Commit 7cd4690

Browse files
authored
stream state-machine (#216)
Start of stream state-machine - Only header callbacks hooked up so far. - Connection checks for active stream. - If stream not found, error raised or frame ignored, according to rules. - Stream checks that frame is allowed, given current state. - When END_STREAM is received, stream closes. - When stream closes, connection marks it complete. - Stream errors result stream sending RST_STREAM frame before closing. - Connection errors shut down channel
1 parent 7aa122c commit 7cd4690

File tree

15 files changed

+1011
-63
lines changed

15 files changed

+1011
-63
lines changed

include/aws/http/private/h2_connection.h

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <aws/http/private/h2_frames.h>
2525

2626
struct aws_h2_decoder;
27+
struct aws_h2_stream;
2728

2829
struct aws_h2_connection {
2930
struct aws_http_connection base;
@@ -62,26 +63,41 @@ struct aws_h2_connection {
6263
* When queue is empty, then we send DATA frames from the outgoing_streams_list */
6364
struct aws_linked_list outgoing_frames_queue;
6465

66+
/* Maps stream-id to aws_h2_stream_closed_when.
67+
* Contains data about streams that were recently closed by this end (sent RST_STREAM frame or END_STREAM flag),
68+
* but might still receive frames that remote peer sent before learning that the stream was closed.
69+
* Entries are removed after a period of time. */
70+
struct aws_hash_table closed_streams_where_frames_might_trickle_in;
71+
6572
} thread_data;
6673

67-
/* Any thread may touch this data, but the lock must be held */
74+
/* Any thread may touch this data, but the lock must be held (unless it's an atomic) */
6875
struct {
76+
/* For checking status from outside the event-loop thread. */
77+
struct aws_atomic_var is_open;
78+
79+
/* If non-zero, reason to immediately reject new streams. (ex: closing) */
80+
struct aws_atomic_var new_stream_error_code;
81+
6982
struct aws_mutex lock;
7083

7184
/* New `aws_h2_stream *` that haven't moved to `thread_data` yet */
7285
struct aws_linked_list pending_stream_list;
7386

74-
/* If non-zero, reason to immediately reject new streams. (ex: closing) */
75-
int new_stream_error_code;
76-
7787
bool is_cross_thread_work_task_scheduled;
7888

79-
/* For checking status from outside the event-loop thread. */
80-
bool is_open;
81-
8289
} synced_data;
8390
};
8491

92+
/**
93+
* The action which caused the stream to close.
94+
*/
95+
enum aws_h2_stream_closed_when {
96+
AWS_H2_STREAM_CLOSED_WHEN_BOTH_SIDES_END_STREAM,
97+
AWS_H2_STREAM_CLOSED_WHEN_RST_STREAM_RECEIVED,
98+
AWS_H2_STREAM_CLOSED_WHEN_RST_STREAM_SENT,
99+
};
100+
85101
/* Private functions called from tests... */
86102

87103
AWS_EXTERN_C_BEGIN
@@ -110,4 +126,19 @@ AWS_EXTERN_C_END
110126
*/
111127
void aws_h2_connection_enqueue_outgoing_frame(struct aws_h2_connection *connection, struct aws_h2_frame *frame);
112128

129+
/**
130+
* Invoked immediately after a stream enters the CLOSED state.
131+
* The connection will remove the stream from its "active" datastructures,
132+
* guaranteeing that no further decoder callbacks are invoked on the stream.
133+
*
134+
* This should NOT be invoked in the case of a "Connection Error",
135+
* though a "Stream Error", in which a RST_STREAM is sent and the stream
136+
* is closed early, would invoke this.
137+
*/
138+
int aws_h2_connection_on_stream_closed(
139+
struct aws_h2_connection *connection,
140+
struct aws_h2_stream *stream,
141+
enum aws_h2_stream_closed_when closed_when,
142+
int aws_error_code);
143+
113144
#endif /* AWS_HTTP_H2_CONNECTION_H */

include/aws/http/private/h2_frames.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum aws_h2_frame_type {
3333
AWS_H2_FRAME_T_WINDOW_UPDATE = 0x08,
3434
AWS_H2_FRAME_T_CONTINUATION = 0x09,
3535
AWS_H2_FRAME_T_UNKNOWN,
36+
AWS_H2_FRAME_TYPE_COUNT,
3637
};
3738

3839
/* Represents flags that may be set on a frame (RFC-7540 6) */
@@ -45,7 +46,7 @@ enum aws_h2_frame_flag {
4546
};
4647

4748
/* Error codes that may be present in RST_STREAM and GOAWAY frames (RFC-7540 7). */
48-
enum aws_h2_error_codes {
49+
enum aws_h2_error_code {
4950
AWS_H2_ERR_NO_ERROR = 0x00,
5051
AWS_H2_ERR_PROTOCOL_ERROR = 0x01, /* corresponds to AWS_ERROR_HTTP_PROTOCOL_ERROR */
5152
AWS_H2_ERR_INTERNAL_ERROR = 0x02,
@@ -162,6 +163,25 @@ AWS_EXTERN_C_BEGIN
162163
AWS_HTTP_API
163164
const char *aws_h2_frame_type_to_str(enum aws_h2_frame_type type);
164165

166+
AWS_HTTP_API
167+
const char *aws_h2_error_code_to_str(enum aws_h2_error_code h2_error_code);
168+
169+
/**
170+
* Translate an AWS_ERROR_* into the appropriate HTTP/2 error-code to sending in a RST_STREAM or GOAWAY frame.
171+
*
172+
* Ex: AWS_ERROR_HTTP_PROTOCOL_ERROR -> AWS_H2_ERR_PROTOCOL_ERROR (0x1)
173+
*
174+
* AWS_H2_ERR_INTERNAL_ERROR is returned if nothing else matches.
175+
*
176+
* #TODO - is this really how we want to handle things? It seems weird to tell OUR user
177+
* about the specific rule that the peer violated. Also, if a bunch of AWS_ERROR_*
178+
* magically result in the peer being told that THEY did something wrong,
179+
* we are for sure going to accidentally re-use those AWS_ERROR_* codes for
180+
* errors-on-our-side when we should only be using them for errors-from-their-side.
181+
*/
182+
AWS_HTTP_API
183+
enum aws_h2_error_code aws_error_to_h2_error_code(int aws_error_code);
184+
165185
/* Raises AWS_ERROR_INVALID_ARGUMENT if stream_id is 0 or exceeds AWS_H2_MAX_STREAM_ID */
166186
AWS_HTTP_API
167187
int aws_h2_validate_stream_id(uint32_t stream_id);

include/aws/http/private/h2_stream.h

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,22 @@
3434
#define AWS_H2_STREAM_LOG(level, stream, text) AWS_H2_STREAM_LOGF(level, (stream), "%s", (text))
3535

3636
enum aws_h2_stream_state {
37+
/* Initial state, before anything sent or received. */
3738
AWS_H2_STREAM_STATE_IDLE,
39+
/* (server-only) stream-id was reserved via PUSH_PROMISE on another stream,
40+
* but HEADERS for this stream have not been sent yet */
3841
AWS_H2_STREAM_STATE_RESERVED_LOCAL,
42+
/* (client-only) stream-id was reserved via PUSH_PROMISE on another stream,
43+
* but HEADERS for this stream have not been received yet */
3944
AWS_H2_STREAM_STATE_RESERVED_REMOTE,
45+
/* Neither side is done sending their message. */
4046
AWS_H2_STREAM_STATE_OPEN,
47+
/* This side is done sending message (END_STREAM), but peer is not done. */
4148
AWS_H2_STREAM_STATE_HALF_CLOSED_LOCAL,
49+
/* Peer is done sending message (END_STREAM), but this side is not done */
4250
AWS_H2_STREAM_STATE_HALF_CLOSED_REMOTE,
51+
/* Both sides done sending message (END_STREAM),
52+
* or either side has sent RST_STREAM */
4353
AWS_H2_STREAM_STATE_CLOSED,
4454

4555
AWS_H2_STREAM_STATE_COUNT,
@@ -55,13 +65,8 @@ struct aws_h2_stream {
5565
enum aws_h2_stream_state state;
5666
uint64_t window_size; /* #TODO try to figure out how this actually works, and then implement it */
5767
struct aws_http_message *outgoing_message;
68+
bool received_main_headers;
5869
} thread_data;
59-
60-
/* Any thread may touch this data, but the lock must be held */
61-
struct {
62-
struct aws_mutex lock;
63-
64-
} synced_data;
6570
};
6671

6772
const char *aws_h2_stream_state_to_str(enum aws_h2_stream_state state);
@@ -75,6 +80,21 @@ enum aws_h2_stream_state aws_h2_stream_get_state(const struct aws_h2_stream *str
7580
/* Connection is ready to send frames from stream now */
7681
int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgoing_data);
7782

83+
int aws_h2_stream_on_decoder_headers_begin(struct aws_h2_stream *stream);
84+
85+
int aws_h2_stream_on_decoder_headers_i(
86+
struct aws_h2_stream *stream,
87+
const struct aws_http_header *header,
88+
enum aws_http_header_name name_enum,
89+
enum aws_http_header_block block_type);
90+
91+
int aws_h2_stream_on_decoder_headers_end(
92+
struct aws_h2_stream *stream,
93+
bool malformed,
94+
enum aws_http_header_block block_type);
95+
96+
int aws_h2_stream_on_decoder_end_stream(struct aws_h2_stream *stream);
97+
7898
int aws_h2_stream_activate(struct aws_http_stream *stream);
7999

80100
#endif /* AWS_HTTP_H2_STREAM_H */

include/aws/http/request_response.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ void aws_http_stream_update_window(struct aws_http_stream *stream, size_t increm
687687
* as http/2) for easier tracking purposes. For client streams, this will only be non-zero after a successful call
688688
* to aws_http_stream_activate()
689689
*/
690-
uint32_t aws_http_stream_get_id(struct aws_http_stream *stream);
690+
uint32_t aws_http_stream_get_id(const struct aws_http_stream *stream);
691691

692692
AWS_EXTERN_C_END
693693

0 commit comments

Comments
 (0)