├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── test ├── cases │ ├── 16_bit_frame_length │ ├── continuation_after_fin │ ├── continuation_at_beginning │ ├── fragmented_control_frame │ ├── fragmented_frames │ ├── interleaved_control_frames │ ├── invalid_opcode │ ├── masking │ ├── masking_empty_frames │ ├── non_canonical_16_bit_length │ ├── non_canonical_64_bit_length │ ├── rejects_too_long_control_frames │ ├── reserved_bits │ ├── simple │ └── zero_length_frames ├── driver.rb └── parse.c ├── ws_parser.c └── ws_parser.h /.gitignore: -------------------------------------------------------------------------------- 1 | /test/parse 2 | 3 | *.o 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Hailey Somerville 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -Wall -Wextra -pedantic -Werror -std=c99 2 | 3 | ifdef DEBUG 4 | CFLAGS += -g -DWS_PARSER_DUMP_STATE 5 | else 6 | CFLAGS += -O3 7 | endif 8 | 9 | .PHONY: default test clean 10 | 11 | default: ws_parser.o 12 | 13 | test: test/parse 14 | ruby test/driver.rb 15 | 16 | clean: 17 | rm -f ws_parser.o test/parse test/parse.o 18 | 19 | %.o: %.c ws_parser.h 20 | $(CC) -o $@ $(CFLAGS) -c $< 21 | 22 | test/parse: test/parse.o ws_parser.o 23 | test/parse.o: CFLAGS+=-iquote . 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ws_parser 2 | 3 | ws_parser is a streaming parser for the WebSocket protocol ([RFC 6455](https://tools.ietf.org/html/rfc6455)). 4 | 5 | This library is loosely inspired by [joyent/http_parser](https://github.com/joyent/http-parser) and shares many of the same attributes: it has no dependencies, makes no allocations or syscalls, and only requires 16 bytes of memory to maintain its parse state. 6 | -------------------------------------------------------------------------------- /test/cases/16_bit_frame_length: -------------------------------------------------------------------------------- 1 | "\x81\x7e\x01\x02#{"x" * 258}" 2 | 3 | data_begin: text 4 | data_payload: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' 5 | data_end 6 | -------------------------------------------------------------------------------- /test/cases/continuation_after_fin: -------------------------------------------------------------------------------- 1 | "\x81\x00\x00x05hello" 2 | 3 | data_begin: text 4 | data_end 5 | error: -3 WS_INVALID_CONTINUATION 6 | -------------------------------------------------------------------------------- /test/cases/continuation_at_beginning: -------------------------------------------------------------------------------- 1 | "\x00\x05hello" 2 | 3 | error: -3 WS_INVALID_CONTINUATION 4 | -------------------------------------------------------------------------------- /test/cases/fragmented_control_frame: -------------------------------------------------------------------------------- 1 | "\x08\x04ping" 2 | 3 | error: -6 WS_FRAGMENTED_CONTROL 4 | -------------------------------------------------------------------------------- /test/cases/fragmented_frames: -------------------------------------------------------------------------------- 1 | "\x01\x03foo\x00\x03bar\x80\x03baz" 2 | 3 | data_begin: text 4 | data_payload: 'foo' 5 | data_payload: 'bar' 6 | data_payload: 'baz' 7 | data_end 8 | -------------------------------------------------------------------------------- /test/cases/interleaved_control_frames: -------------------------------------------------------------------------------- 1 | "\x01\x03foo\x89\x04ping\x00\x03bar\x80\x03baz" 2 | 3 | data_begin: text 4 | data_payload: 'foo' 5 | control_begin: ping 6 | control_payload: 'ping' 7 | control_end 8 | data_payload: 'bar' 9 | data_payload: 'baz' 10 | data_end 11 | -------------------------------------------------------------------------------- /test/cases/invalid_opcode: -------------------------------------------------------------------------------- 1 | "\x8f" 2 | 3 | error: -2 WS_INVALID_OPCODE 4 | -------------------------------------------------------------------------------- /test/cases/masking: -------------------------------------------------------------------------------- 1 | "\x81\x85\xde\xad\xbe\xef\xb6\xc8\xd2\x83\xb1\x89\x85\xde\xad\xbe\xef\xb6\xc8\xd2\x83\xb1" 2 | 3 | data_begin: text 4 | data_payload: 'hello' 5 | data_end 6 | control_begin: ping 7 | control_payload: 'hello' 8 | control_end 9 | -------------------------------------------------------------------------------- /test/cases/masking_empty_frames: -------------------------------------------------------------------------------- 1 | "\x81\x80\xde\xad\xbe\xef" 2 | 3 | data_begin: text 4 | data_end 5 | -------------------------------------------------------------------------------- /test/cases/non_canonical_16_bit_length: -------------------------------------------------------------------------------- 1 | "\x81\x7e\x00\x05hello" 2 | 3 | data_begin: text 4 | error: -5 WS_NON_CANONICAL_LENGTH 5 | -------------------------------------------------------------------------------- /test/cases/non_canonical_64_bit_length: -------------------------------------------------------------------------------- 1 | "\x81\x7f\x00\x00\x00\x00\x00\x00\x00\x05hello" 2 | 3 | data_begin: text 4 | error: -5 WS_NON_CANONICAL_LENGTH 5 | -------------------------------------------------------------------------------- /test/cases/rejects_too_long_control_frames: -------------------------------------------------------------------------------- 1 | "\x89\x7e\x01\x02#{"x" * 258}" 2 | 3 | control_begin: ping 4 | error: -4 WS_CONTROL_TOO_LONG 5 | -------------------------------------------------------------------------------- /test/cases/reserved_bits: -------------------------------------------------------------------------------- 1 | "\xff" 2 | 3 | error: -1 WS_RESERVED_BITS_SET 4 | -------------------------------------------------------------------------------- /test/cases/simple: -------------------------------------------------------------------------------- 1 | "\x81\x05hello\x82\x07goodbye\x89\x0ca ping frame\x8a\x0ca pong frame\x88\x0bseeya later" 2 | 3 | data_begin: text 4 | data_payload: 'hello' 5 | data_end 6 | data_begin: binary 7 | data_payload: 'goodbye' 8 | data_end 9 | control_begin: ping 10 | control_payload: 'a ping frame' 11 | control_end 12 | control_begin: pong 13 | control_payload: 'a pong frame' 14 | control_end 15 | control_begin: close 16 | control_payload: 'seeya later' 17 | control_end 18 | -------------------------------------------------------------------------------- /test/cases/zero_length_frames: -------------------------------------------------------------------------------- 1 | "\x81\x00\x82\x00\x88\x00\x89\x00\x8a\x00" 2 | 3 | data_begin: text 4 | data_end 5 | data_begin: binary 6 | data_end 7 | control_begin: close 8 | control_end 9 | control_begin: ping 10 | control_end 11 | control_begin: pong 12 | control_end 13 | -------------------------------------------------------------------------------- /test/driver.rb: -------------------------------------------------------------------------------- 1 | require "tempfile" 2 | 3 | passing = true 4 | 5 | Dir["#{__dir__}/cases/*"].each do |test_case| 6 | test_name = test_case.split("/").last 7 | 8 | input, expected = File.read(test_case).split("\n\n") 9 | 10 | begin 11 | output_file = "/tmp/ws_parser-#{$$}-output" 12 | expected_file = "/tmp/ws_parser-#{$$}-expected" 13 | 14 | io = IO.popen(["#{__dir__}/parse"], "wb", :out => output_file) 15 | 16 | input.lines.each_with_index do |chunk, index| 17 | io.syswrite(eval(chunk, nil, test_case, index + 1).b) 18 | end 19 | 20 | io.close 21 | 22 | if File.read(output_file) == expected 23 | print "." 24 | $stdout.flush 25 | else 26 | File.write(expected_file, expected) 27 | puts 28 | puts "Failed: #{test_name}" 29 | puts IO.popen(["diff", "-u", expected_file, output_file]).readlines.drop(2).join 30 | puts 31 | passing = false 32 | end 33 | ensure 34 | File.delete(output_file) rescue nil 35 | File.delete(expected_file) rescue nil 36 | end 37 | end 38 | 39 | puts 40 | exit passing 41 | -------------------------------------------------------------------------------- /test/parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ws_parser.h" 7 | 8 | int 9 | on_data_begin(void* user_data, ws_frame_type_t frame_type) 10 | { 11 | (void)user_data; 12 | 13 | printf("data_begin: %s\n", 14 | frame_type == WS_FRAME_TEXT ? "text" : 15 | frame_type == WS_FRAME_BINARY ? "binary" : 16 | "?"); 17 | 18 | return WS_OK; 19 | } 20 | 21 | int 22 | on_data_payload(void* user_data, const char* buff, size_t len) 23 | { 24 | (void)user_data; 25 | 26 | printf("data_payload: '%.*s'\n", (int)len, buff); 27 | 28 | return WS_OK; 29 | } 30 | 31 | int 32 | on_data_end() 33 | { 34 | printf("data_end\n"); 35 | 36 | return WS_OK; 37 | } 38 | 39 | int 40 | on_control_begin(void* user_data, ws_frame_type_t frame_type) 41 | { 42 | (void)user_data; 43 | 44 | printf("control_begin: %s\n", 45 | frame_type == WS_FRAME_PING ? "ping" : 46 | frame_type == WS_FRAME_PONG ? "pong" : 47 | frame_type == WS_FRAME_CLOSE ? "close" : 48 | "?"); 49 | 50 | return WS_OK; 51 | } 52 | 53 | int 54 | on_control_payload(void* user_data, const char* buff, size_t len) 55 | { 56 | (void)user_data; 57 | 58 | printf("control_payload: '%.*s'\n", (int)len, buff); 59 | 60 | return WS_OK; 61 | } 62 | 63 | int 64 | on_control_end() 65 | { 66 | printf("control_end\n"); 67 | 68 | return WS_OK; 69 | } 70 | 71 | int 72 | main() 73 | { 74 | ws_parser_callbacks_t callbacks = { 75 | .on_data_begin = on_data_begin, 76 | .on_data_payload = on_data_payload, 77 | .on_data_end = on_data_end, 78 | .on_control_begin = on_control_begin, 79 | .on_control_payload = on_control_payload, 80 | .on_control_end = on_control_end, 81 | }; 82 | 83 | ws_parser_t parser; 84 | ws_parser_init(&parser, &callbacks); 85 | 86 | while(1) { 87 | char buff[4096]; 88 | ssize_t nbytes = read(0, buff, sizeof(buff)); 89 | 90 | if(nbytes < 0) { 91 | if(errno == EINTR) { 92 | continue; 93 | } 94 | 95 | perror("read"); 96 | return 1; 97 | } 98 | 99 | if(nbytes == 0) { 100 | break; 101 | } 102 | 103 | int rc = ws_parser_execute(&parser, buff, nbytes); 104 | 105 | if(rc != WS_OK) { 106 | printf("error: %d %s\n", rc, ws_parser_error(rc)); 107 | return 0; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ws_parser.c: -------------------------------------------------------------------------------- 1 | #ifdef WS_PARSER_DUMP_STATE 2 | #include 3 | #endif 4 | 5 | #include "ws_parser.h" 6 | 7 | enum { 8 | S_OPCODE = 0, 9 | S_LENGTH, 10 | S_LENGTH_16_0, 11 | S_LENGTH_16_1, 12 | S_LENGTH_64_0, 13 | S_LENGTH_64_1, 14 | S_LENGTH_64_2, 15 | S_LENGTH_64_3, 16 | S_LENGTH_64_4, 17 | S_LENGTH_64_5, 18 | S_LENGTH_64_6, 19 | S_LENGTH_64_7, 20 | S_MASK_0, 21 | S_MASK_1, 22 | S_MASK_2, 23 | S_MASK_3, 24 | S_PAYLOAD, 25 | }; 26 | 27 | void 28 | ws_parser_init(ws_parser_t* parser) 29 | { 30 | parser->state = S_OPCODE; 31 | parser->fragment = 0; 32 | } 33 | 34 | #define ADVANCE { buff++; len--; } 35 | #define ADVANCE_AND_BREAK { ADVANCE; break; } 36 | 37 | int 38 | ws_parser_execute( 39 | ws_parser_t* parser, 40 | const ws_parser_callbacks_t* callbacks, 41 | void* data, 42 | char* buff /* mutates! */, 43 | size_t len) 44 | { 45 | while(len) { 46 | uint8_t cur_byte = *buff; 47 | 48 | #ifdef WS_PARSER_DUMP_STATE 49 | printf("cur_byte=%d bytes_remaining=%llu fragment=%d fin=%d " 50 | "control=%d mask_flag=%d mask_pos=%d state=%d len=%zu\n", 51 | (int)cur_byte, 52 | parser->bytes_remaining, 53 | parser->fragment, 54 | parser->fin, 55 | parser->control, 56 | parser->mask_flag, 57 | parser->mask_pos, 58 | parser->state, 59 | len); 60 | #endif 61 | 62 | switch(parser->state) { 63 | case S_OPCODE: { 64 | uint8_t opcode = cur_byte & 0x0f; 65 | 66 | if(cur_byte & 0x70) { 67 | // reserved bits 68 | return WS_RESERVED_BITS_SET; 69 | } 70 | 71 | parser->fin = (cur_byte & 0x80) ? 1 : 0; 72 | 73 | if(opcode == 0) { // continuation 74 | if(!parser->fragment) { 75 | return WS_INVALID_CONTINUATION; 76 | } 77 | 78 | parser->control = 0; 79 | } else if(opcode & 0x8) { // control 80 | if(opcode != WS_FRAME_PING && opcode != WS_FRAME_PONG && opcode != WS_FRAME_CLOSE) { 81 | return WS_INVALID_OPCODE; 82 | } 83 | 84 | if(!parser->fin) { 85 | return WS_FRAGMENTED_CONTROL; 86 | } 87 | 88 | parser->control = 1; 89 | 90 | int rc = callbacks->on_control_begin(data, opcode); 91 | if(rc) { 92 | return rc; 93 | } 94 | } else { // data 95 | if(opcode != WS_FRAME_TEXT && opcode != WS_FRAME_BINARY) { 96 | return WS_INVALID_OPCODE; 97 | } 98 | 99 | parser->control = 0; 100 | parser->fragment = !parser->fin; 101 | 102 | int rc = callbacks->on_data_begin(data, opcode); 103 | if(rc) { 104 | return rc; 105 | } 106 | } 107 | 108 | parser->state = S_LENGTH; 109 | 110 | ADVANCE_AND_BREAK; 111 | } 112 | case S_LENGTH: { 113 | uint8_t length = cur_byte & 0x7f; 114 | 115 | parser->mask_flag = (cur_byte & 0x80) ? 1 : 0; 116 | parser->mask_pos = 0; 117 | 118 | if(parser->control) { 119 | if(length > 125) { 120 | return WS_CONTROL_TOO_LONG; 121 | } 122 | 123 | parser->bytes_remaining = length; 124 | parser->state = parser->mask_flag ? S_MASK_0 : S_PAYLOAD; 125 | } else { 126 | if(length < 126) { 127 | parser->bytes_remaining = length; 128 | parser->state = parser->mask_flag ? S_MASK_0 : S_PAYLOAD; 129 | } else if(length == 126) { 130 | parser->state = S_LENGTH_16_0; 131 | } else { 132 | parser->state = S_LENGTH_64_0; 133 | } 134 | } 135 | 136 | ADVANCE; 137 | 138 | if(parser->state == S_PAYLOAD && parser->bytes_remaining == 0) { 139 | goto end_of_payload; 140 | } 141 | 142 | break; 143 | } 144 | case S_LENGTH_16_0: { 145 | parser->bytes_remaining = (uint64_t)cur_byte << 8; 146 | parser->state = S_LENGTH_16_1; 147 | 148 | ADVANCE_AND_BREAK; 149 | } 150 | case S_LENGTH_16_1: { 151 | parser->bytes_remaining |= (uint64_t)cur_byte << 0; 152 | parser->state = parser->mask_flag ? S_MASK_0 : S_PAYLOAD; 153 | 154 | if(parser->bytes_remaining < 126) { 155 | return WS_NON_CANONICAL_LENGTH; 156 | } 157 | 158 | ADVANCE_AND_BREAK; 159 | } 160 | case S_LENGTH_64_0: { 161 | parser->bytes_remaining = (uint64_t)cur_byte << 56; 162 | parser->state = S_LENGTH_64_1; 163 | 164 | ADVANCE_AND_BREAK; 165 | } 166 | case S_LENGTH_64_1: { 167 | parser->bytes_remaining |= (uint64_t)cur_byte << 48; 168 | parser->state = S_LENGTH_64_2; 169 | 170 | ADVANCE_AND_BREAK; 171 | } 172 | case S_LENGTH_64_2: { 173 | parser->bytes_remaining |= (uint64_t)cur_byte << 40; 174 | parser->state = S_LENGTH_64_3; 175 | 176 | ADVANCE_AND_BREAK; 177 | } 178 | case S_LENGTH_64_3: { 179 | parser->bytes_remaining |= (uint64_t)cur_byte << 32; 180 | parser->state = S_LENGTH_64_4; 181 | 182 | ADVANCE_AND_BREAK; 183 | } 184 | case S_LENGTH_64_4: { 185 | parser->bytes_remaining |= (uint64_t)cur_byte << 24; 186 | parser->state = S_LENGTH_64_5; 187 | 188 | ADVANCE_AND_BREAK; 189 | } 190 | case S_LENGTH_64_5: { 191 | parser->bytes_remaining |= (uint64_t)cur_byte << 16; 192 | parser->state = S_LENGTH_64_6; 193 | 194 | ADVANCE_AND_BREAK; 195 | } 196 | case S_LENGTH_64_6: { 197 | parser->bytes_remaining |= (uint64_t)cur_byte << 8; 198 | parser->state = S_LENGTH_64_7; 199 | 200 | ADVANCE_AND_BREAK; 201 | } 202 | case S_LENGTH_64_7: { 203 | parser->bytes_remaining |= (uint64_t)cur_byte << 0; 204 | parser->state = parser->mask_flag ? S_MASK_0 : S_PAYLOAD; 205 | 206 | if(parser->bytes_remaining < 65536) { 207 | return WS_NON_CANONICAL_LENGTH; 208 | } 209 | 210 | ADVANCE_AND_BREAK; 211 | } 212 | case S_MASK_0: { 213 | parser->mask[0] = cur_byte; 214 | parser->state = S_MASK_1; 215 | 216 | ADVANCE_AND_BREAK; 217 | } 218 | case S_MASK_1: { 219 | parser->mask[1] = cur_byte; 220 | parser->state = S_MASK_2; 221 | 222 | ADVANCE_AND_BREAK; 223 | } 224 | case S_MASK_2: { 225 | parser->mask[2] = cur_byte; 226 | parser->state = S_MASK_3; 227 | 228 | ADVANCE_AND_BREAK; 229 | } 230 | case S_MASK_3: { 231 | parser->mask[3] = cur_byte; 232 | parser->state = S_PAYLOAD; 233 | 234 | ADVANCE; 235 | 236 | if(parser->bytes_remaining == 0) { 237 | goto end_of_payload; 238 | } 239 | 240 | break; 241 | } 242 | case S_PAYLOAD: { 243 | size_t chunk_length = len; 244 | 245 | if(chunk_length > parser->bytes_remaining) { 246 | chunk_length = parser->bytes_remaining; 247 | } 248 | 249 | if(parser->mask_flag) { 250 | for(size_t i = 0; i < chunk_length; i++) { 251 | buff[i] ^= parser->mask[parser->mask_pos++]; 252 | } 253 | } 254 | 255 | int rc; 256 | 257 | if(parser->control) { 258 | rc = callbacks->on_control_payload(data, buff, chunk_length); 259 | } else { 260 | rc = callbacks->on_data_payload(data, buff, chunk_length); 261 | } 262 | 263 | if(rc) { 264 | return rc; 265 | } 266 | 267 | buff += chunk_length; 268 | len -= chunk_length; 269 | parser->bytes_remaining -= chunk_length; 270 | 271 | if(parser->bytes_remaining == 0) { 272 | goto end_of_payload; 273 | } 274 | 275 | break; 276 | } 277 | end_of_payload: { 278 | if(parser->control || parser->fin) { 279 | int rc; 280 | 281 | if(parser->control) { 282 | rc = callbacks->on_control_end(data); 283 | } else { 284 | rc = callbacks->on_data_end(data); 285 | } 286 | 287 | if(rc) { 288 | return rc; 289 | } 290 | } 291 | 292 | parser->state = S_OPCODE; 293 | 294 | break; 295 | } 296 | } 297 | } 298 | 299 | return WS_OK; 300 | } 301 | 302 | const char* 303 | ws_parser_error(int rc) 304 | { 305 | #define XX(name, code) if(rc == code) return #name; 306 | WS_PARSER_ERROR_CODES(XX) 307 | #undef XX 308 | 309 | return NULL; 310 | } 311 | -------------------------------------------------------------------------------- /ws_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef WS_PARSER 2 | #define WS_PARSER 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | WS_FRAME_TEXT = 0x1, 9 | WS_FRAME_BINARY = 0x2, 10 | WS_FRAME_CLOSE = 0x8, 11 | WS_FRAME_PING = 0x9, 12 | WS_FRAME_PONG = 0xA, 13 | } 14 | ws_frame_type_t; 15 | 16 | typedef struct { 17 | int(*on_data_begin) (void*, ws_frame_type_t); 18 | int(*on_data_payload) (void*, const char*, size_t); 19 | int(*on_data_end) (void*); 20 | int(*on_control_begin) (void*, ws_frame_type_t); 21 | int(*on_control_payload)(void*, const char*, size_t); 22 | int(*on_control_end) (void*); 23 | } 24 | ws_parser_callbacks_t; 25 | 26 | typedef struct { 27 | uint64_t bytes_remaining; 28 | uint8_t mask[4]; 29 | uint8_t fragment : 1; 30 | uint8_t fin : 1; 31 | uint8_t control : 1; 32 | uint8_t mask_flag : 1; 33 | uint8_t mask_pos : 2; 34 | uint8_t state : 5; 35 | } 36 | ws_parser_t; 37 | 38 | #define WS_PARSER_ERROR_CODES(XX) \ 39 | XX(WS_OK, 0) \ 40 | XX(WS_RESERVED_BITS_SET, -1) \ 41 | XX(WS_INVALID_OPCODE, -2) \ 42 | XX(WS_INVALID_CONTINUATION, -3) \ 43 | XX(WS_CONTROL_TOO_LONG, -4) \ 44 | XX(WS_NON_CANONICAL_LENGTH, -5) \ 45 | XX(WS_FRAGMENTED_CONTROL, -6) \ 46 | 47 | enum { 48 | #define XX(name, code) name = code, 49 | WS_PARSER_ERROR_CODES(XX) 50 | #undef XX 51 | }; 52 | 53 | void 54 | ws_parser_init(ws_parser_t* parser); 55 | 56 | int 57 | ws_parser_execute( 58 | ws_parser_t* parser, 59 | const ws_parser_callbacks_t* callbacks, 60 | void* data, 61 | char* buff /* mutates! */, 62 | size_t len); 63 | 64 | const char* 65 | ws_parser_error(int rc); 66 | 67 | #endif 68 | --------------------------------------------------------------------------------