Skip to content

Commit e2deb73

Browse files
authored
WIP Websocket API and channel-handler (#42)
1 parent 8639fb1 commit e2deb73

File tree

9 files changed

+2220
-23
lines changed

9 files changed

+2220
-23
lines changed

include/aws/http/http.h

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ enum aws_http_errors {
2828
AWS_ERROR_HTTP_REACTION_REQUIRED,
2929
AWS_ERROR_HTTP_DATA_NOT_AVAILABLE,
3030
AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT,
31+
AWS_ERROR_HTTP_WEBSOCKET_CLOSE_FRAME_SENT,
3132
AWS_ERROR_HTTP_END_RANGE = 0x0C00,
3233
};
3334

@@ -36,6 +37,7 @@ enum aws_http_log_subject {
3637
AWS_LS_HTTP_CONNECTION,
3738
AWS_LS_HTTP_SERVER,
3839
AWS_LS_HTTP_STREAM,
40+
AWS_LS_HTTP_WEBSOCKET,
3941
};
4042

4143
enum aws_http_version {

include/aws/http/private/websocket_impl.h

+38-15
Original file line numberDiff line numberDiff line change
@@ -16,16 +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_BINARY = 0x2,
25-
AWS_WEBSOCKET_OPCODE_CLOSE = 0x8,
26-
AWS_WEBSOCKET_OPCODE_PING = 0x9,
27-
AWS_WEBSOCKET_OPCODE_PONG = 0xA,
28-
};
19+
#include <aws/http/websocket.h>
2920

3021
/* RFC-6455 Section 5.2 Base Framing Protocol
3122
* Payload length: 7 bits, 7+16 bits, or 7+64 bits
@@ -53,6 +44,9 @@ enum aws_websocket_opcode {
5344
#define AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MIN_VALUE 0x0000000000010000
5445
#define AWS_WEBSOCKET_8BYTE_EXTENDED_LENGTH_MAX_VALUE 0x7FFFFFFFFFFFFFFF
5546

47+
/* Max bytes necessary to send non-payload parts of a frame */
48+
#define AWS_WEBSOCKET_MAX_FRAME_OVERHEAD (2 + 8 + 4) /* base + extended-length + masking-key */
49+
5650
/**
5751
* Full contents of a websocket frame, excluding the payload.
5852
*/
@@ -65,12 +59,41 @@ struct aws_websocket_frame {
6559
uint8_t masking_key[4];
6660
};
6761

62+
struct aws_websocket_handler_options {
63+
struct aws_allocator *allocator;
64+
struct aws_channel_slot *channel_slot;
65+
size_t initial_window_size;
66+
67+
void *user_data;
68+
aws_websocket_on_connection_shutdown_fn *on_connection_shutdown;
69+
aws_websocket_on_incoming_frame_begin_fn *on_incoming_frame_begin;
70+
aws_websocket_on_incoming_frame_payload_fn *on_incoming_frame_payload;
71+
aws_websocket_on_incoming_frame_complete_fn *on_incoming_frame_complete;
72+
73+
bool is_server;
74+
};
75+
76+
AWS_EXTERN_C_BEGIN
77+
78+
/**
79+
* Returns printable name for opcode as c-string.
80+
*/
81+
AWS_HTTP_API
82+
const char *aws_websocket_opcode_str(uint8_t opcode);
83+
84+
/**
85+
* Return total number of bytes needed to encode frame and its payload
86+
*/
87+
AWS_HTTP_API
88+
uint64_t aws_websocket_frame_encoded_size(const struct aws_websocket_frame *frame);
89+
6890
/**
69-
* Return true if opcode is for a data frame, false if opcode if for a control frame.
91+
* Returns channel-handler for websocket.
92+
* handler->impl is the aws_websocket*
93+
* To destroy a handler that was never put into a channel, invoke: `handler->vtable.destroy(handler)`
7094
*/
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-
}
95+
AWS_HTTP_API
96+
struct aws_channel_handler *aws_websocket_handler_new(const struct aws_websocket_handler_options *options);
7597

98+
AWS_EXTERN_C_END
7699
#endif /* AWS_HTTP_WEBSOCKET_IMPL_H */

include/aws/http/websocket.h

+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
#ifndef AWS_HTTP_WEBSOCKET_H
2+
#define AWS_HTTP_WEBSOCKET_H
3+
/*
4+
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License").
7+
* You may not use this file except in compliance with the License.
8+
* A copy of the License is located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed
13+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
#include <aws/http/http.h>
19+
20+
struct aws_channel_handler;
21+
struct aws_http_header;
22+
23+
/**
24+
* A websocket connection.
25+
*/
26+
struct aws_websocket;
27+
28+
/**
29+
* Opcode describing the type of a websocket frame.
30+
* RFC-6455 Section 5.2
31+
*/
32+
enum aws_websocket_opcode {
33+
AWS_WEBSOCKET_OPCODE_CONTINUATION = 0x0,
34+
AWS_WEBSOCKET_OPCODE_TEXT = 0x1,
35+
AWS_WEBSOCKET_OPCODE_BINARY = 0x2,
36+
AWS_WEBSOCKET_OPCODE_CLOSE = 0x8,
37+
AWS_WEBSOCKET_OPCODE_PING = 0x9,
38+
AWS_WEBSOCKET_OPCODE_PONG = 0xA,
39+
};
40+
41+
#define AWS_WEBSOCKET_MAX_PAYLOAD_LENGTH 0x7FFFFFFFFFFFFFFF
42+
43+
/**
44+
* Called when websocket setup is complete.
45+
* If setup succeeds, error_code is zero and the websocket pointer is valid.
46+
* If setup failed, error_code will be non-zero and the pointer will be NULL.
47+
* Called exactly once on the websocket's event-loop thread.
48+
*/
49+
typedef void(aws_websocket_on_connection_setup_fn)(
50+
struct aws_websocket *websocket,
51+
int error_code,
52+
/* TODO: how to pass back misc response data like headers */
53+
void *user_data);
54+
55+
/**
56+
* Called when the websocket has finished shutting down.
57+
* Called once on the websocket's event-loop thread if setup succeeded.
58+
* If setup failed, this is never called.
59+
*/
60+
typedef void(aws_websocket_on_connection_shutdown_fn)(struct aws_websocket *websocket, int error_code, void *user_data);
61+
62+
/**
63+
* Data about an incoming frame.
64+
* See RFC-6455 Section 5.2.
65+
*/
66+
struct aws_websocket_incoming_frame {
67+
uint64_t payload_length;
68+
uint8_t opcode;
69+
bool fin;
70+
bool rsv[3];
71+
};
72+
73+
/**
74+
* Called when a new frame arrives.
75+
* Invoked once per frame on the websocket's event-loop thread.
76+
* Each incoming-frame-begin call will eventually be followed by an incoming-frame-complete call,
77+
* before the next frame begins and before the websocket shuts down.
78+
*/
79+
typedef void(aws_websocket_on_incoming_frame_begin_fn)(
80+
struct aws_websocket *websocket,
81+
const struct aws_websocket_incoming_frame *frame,
82+
void *user_data);
83+
84+
/**
85+
* Called repeatedly as payload data arrives.
86+
* Invoked 0 or more times on the websocket's event-loop thread.
87+
* Payload data will not be valid after this call, so copy if necessary.
88+
* The payload data is always unmasked at this point.
89+
*
90+
* `out_window_update_size` is how much to increment the flow-control window once this data is processed.
91+
* By default, it is the size of the data which has just come in.
92+
* Leaving this value untouched will increment the window back to its original size.
93+
* Setting this value to 0 will prevent the update and let the window shrink.
94+
* The window can be manually updated via aws_websocket_update_window()
95+
*/
96+
typedef void(aws_websocket_on_incoming_frame_payload_fn)(
97+
struct aws_websocket *websocket,
98+
const struct aws_websocket_incoming_frame *frame,
99+
struct aws_byte_cursor data,
100+
size_t *out_window_update_size,
101+
void *user_data);
102+
103+
/**
104+
* Called when done processing an incoming frame.
105+
* If error_code is non-zero, an error occurred and the payload may not have been completely received.
106+
* Invoked once per frame on the websocket's event-loop thread.
107+
*/
108+
typedef void(aws_websocket_on_incoming_frame_complete_fn)(
109+
struct aws_websocket *websocket,
110+
const struct aws_websocket_incoming_frame *frame,
111+
int error_code,
112+
void *user_data);
113+
114+
/* WIP */
115+
struct aws_websocket_client_connection_options {
116+
struct aws_allocator *allocator;
117+
118+
struct aws_client_bootstrap *bootstrap;
119+
struct aws_byte_cursor host_name;
120+
uint16_t port;
121+
struct aws_socket_options *socket_options;
122+
struct aws_tls_connection_options *tls_options;
123+
size_t initial_window_size;
124+
125+
/* TODO: How to take handshake request params. related, could take URI instead of host_name:port. */
126+
127+
void *user_data;
128+
aws_websocket_on_connection_setup_fn *on_connection_setup;
129+
aws_websocket_on_connection_shutdown_fn *on_connection_shutdown;
130+
aws_websocket_on_incoming_frame_begin_fn *on_incoming_frame_begin;
131+
aws_websocket_on_incoming_frame_payload_fn *on_incoming_frame_payload;
132+
aws_websocket_on_incoming_frame_complete_fn *on_incoming_frame_complete;
133+
};
134+
135+
enum aws_websocket_outgoing_payload_state {
136+
AWS_WEBSOCKET_OUTGOING_PAYLOAD_IN_PROGRESS,
137+
AWS_WEBSOCKET_OUTGOING_PAYLOAD_DONE,
138+
};
139+
140+
/**
141+
* Called repeatedly as the websocket's payload is streamed out.
142+
* The user should write payload data to out_buf and return an enum to indicate their progress.
143+
* If the data is not yet available, your may return return IN_PROGRESS without writing out any data.
144+
* The websocket will mask this data for you, if necessary.
145+
* Invoked repeatedly on the websocket's event-loop thread.
146+
*/
147+
typedef enum aws_websocket_outgoing_payload_state(aws_websocket_stream_outgoing_payload_fn)(
148+
struct aws_websocket *websocket,
149+
struct aws_byte_buf *out_buf,
150+
void *user_data);
151+
152+
/**
153+
* Called when a aws_websocket_send_frame() operation completes.
154+
* error_code will be non-zero if the operation was successful.
155+
* "Success" does not guarantee that the peer actually received or processed the frame.
156+
* Invoked exactly once per sent frame on the websocket's event-loop thread.
157+
*/
158+
typedef void(
159+
aws_websocket_outgoing_frame_complete_fn)(struct aws_websocket *websocket, int error_code, void *user_data);
160+
161+
/**
162+
* Options for sending a websocket frame.
163+
* This structure is copied immediately by aws_websocket_send().
164+
* For descriptions of sopcode, fin, rsv, and payload_length see in RFC-6455 Section 5.2.
165+
*/
166+
struct aws_websocket_send_frame_options {
167+
/**
168+
* Size of payload to be sent via `stream_outgoing_payload` callback.
169+
*/
170+
uint64_t payload_length;
171+
172+
/**
173+
* User data passed to callbacks.
174+
*/
175+
void *user_data;
176+
177+
/**
178+
* Callback for sending payload data.
179+
* See `aws_websocket_stream_outgoing_payload_fn`.
180+
* Required if `payload_length` is non-zero.
181+
*/
182+
aws_websocket_stream_outgoing_payload_fn *stream_outgoing_payload;
183+
184+
/**
185+
* Callback for completion of send operation.
186+
* See `aws_websocket_outgoing_frame_complete_fn`.
187+
* Optional.
188+
*/
189+
aws_websocket_outgoing_frame_complete_fn *on_complete;
190+
191+
/**
192+
* Frame type.
193+
* `aws_websocket_opcode` enum provides standard values.
194+
*/
195+
uint8_t opcode;
196+
197+
/**
198+
* Indicates that this is the final fragment in a message. The first fragment MAY also be the final fragment.
199+
*/
200+
bool fin;
201+
202+
/**
203+
* If true, frame will be sent before those with normal priority.
204+
* Useful for opcodes like PING and PONG where low latency is important.
205+
* This feature may only be used with "control" opcodes, not "data" opcodes like BINARY and TEXT.
206+
*/
207+
bool high_priority;
208+
209+
/**
210+
* MUST be 0 unless an extension is negotiated that defines meanings for non-zero values.
211+
*/
212+
bool rsv[3];
213+
};
214+
215+
AWS_EXTERN_C_BEGIN
216+
217+
/**
218+
* Return true if opcode is for a data frame, false if opcode if for a control frame.
219+
*/
220+
AWS_HTTP_API
221+
bool aws_websocket_is_data_frame(uint8_t opcode);
222+
223+
/* WIP */
224+
AWS_HTTP_API
225+
int aws_websocket_client_connect(const struct aws_websocket_client_connection_options *options);
226+
227+
/* TODO: Require all users to manually grab a hold? Http doesn't work like that... */
228+
/* TODO: should the last release trigger a shutdown automatically? http does that, channel doesn't. */
229+
230+
/**
231+
* Ensure that the websocket cannot be destroyed until aws_websocket_release_hold() is called.
232+
* The websocket might still shutdown/close, but the public API will not crash when this websocket pointer is used.
233+
* If acquire_hold() is never called, the websocket is destroyed when its channel its channel is destroyed.
234+
*/
235+
AWS_HTTP_API
236+
void aws_websocket_acquire_hold(struct aws_websocket *websocket);
237+
238+
/**
239+
* See aws_websocket_acquire_hold().
240+
* The websocket will shut itself down when the last hold is released.
241+
*/
242+
AWS_HTTP_API
243+
void aws_websocket_release_hold(struct aws_websocket *websocket);
244+
245+
/**
246+
* Close the websocket connection.
247+
* It is safe to call this, even if the connection is already closed or closing.
248+
* The websocket will attempt to send a CLOSE frame during normal shutdown.
249+
*/
250+
AWS_HTTP_API
251+
void aws_websocket_close(struct aws_websocket *websocket, int error_code);
252+
253+
/**
254+
* Send a websocket frame.
255+
* The `options` struct is copied.
256+
* A callback will be invoked when the operation completes.
257+
*/
258+
AWS_HTTP_API
259+
int aws_websocket_send_frame(struct aws_websocket *websocket, const struct aws_websocket_send_frame_options *options);
260+
261+
/* WIP */
262+
AWS_HTTP_API
263+
int aws_websocket_install_channel_handler_to_right(
264+
struct aws_websocket *websocket,
265+
struct aws_channel_handler *right_handler);
266+
267+
AWS_EXTERN_C_END
268+
269+
#endif /* AWS_HTTP_WEBSOCKET_H */

0 commit comments

Comments
 (0)