250 lines
8.3 KiB
C
250 lines
8.3 KiB
C
|
#include "websocket_parser.h"
|
||
|
#include <assert.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#ifdef assert
|
||
|
# define assertFalse(msg) assert(0 && msg)
|
||
|
#else
|
||
|
# define assertFalse(msg)
|
||
|
#endif
|
||
|
|
||
|
#define SET_STATE(V) parser->state = V
|
||
|
#define HAS_DATA() (p < end )
|
||
|
#define CC (*p)
|
||
|
#define GET_NPARSED() ( (p == end) ? len : (p - data) )
|
||
|
|
||
|
#define NOTIFY_CB(FOR) \
|
||
|
do { \
|
||
|
if (settings->on_##FOR) { \
|
||
|
if (settings->on_##FOR(parser) != 0) { \
|
||
|
return GET_NPARSED(); \
|
||
|
} \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
#define EMIT_DATA_CB(FOR, ptr, len) \
|
||
|
do { \
|
||
|
if (settings->on_##FOR) { \
|
||
|
if (settings->on_##FOR(parser, ptr, len) != 0) { \
|
||
|
return GET_NPARSED(); \
|
||
|
} \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
enum state {
|
||
|
s_start,
|
||
|
s_head,
|
||
|
s_length,
|
||
|
s_mask,
|
||
|
s_body,
|
||
|
};
|
||
|
|
||
|
void websocket_parser_init(websocket_parser * parser) {
|
||
|
void *data = parser->data; /* preserve application data */
|
||
|
memset(parser, 0, sizeof(*parser));
|
||
|
parser->data = data;
|
||
|
parser->state = s_start;
|
||
|
}
|
||
|
|
||
|
void websocket_parser_settings_init(websocket_parser_settings *settings) {
|
||
|
memset(settings, 0, sizeof(*settings));
|
||
|
}
|
||
|
|
||
|
size_t websocket_parser_execute(websocket_parser *parser, const websocket_parser_settings *settings, const char *data, size_t len) {
|
||
|
const char * p;
|
||
|
const char * end = data + len;
|
||
|
size_t frame_offset = 0;
|
||
|
|
||
|
for(p = data; p != end; p++) {
|
||
|
switch(parser->state) {
|
||
|
case s_start:
|
||
|
parser->offset = 0;
|
||
|
parser->length = 0;
|
||
|
parser->mask_offset = 0;
|
||
|
parser->flags = (websocket_flags) (CC & WS_OP_MASK);
|
||
|
if(CC & (1<<7)) {
|
||
|
parser->flags |= WS_FIN;
|
||
|
}
|
||
|
SET_STATE(s_head);
|
||
|
|
||
|
frame_offset++;
|
||
|
break;
|
||
|
case s_head:
|
||
|
parser->length = (size_t)CC & 0x7F;
|
||
|
if(CC & 0x80) {
|
||
|
parser->flags |= WS_HAS_MASK;
|
||
|
}
|
||
|
if(parser->length >= 126) {
|
||
|
if(parser->length == 127) {
|
||
|
parser->require = 8;
|
||
|
} else {
|
||
|
parser->require = 2;
|
||
|
}
|
||
|
parser->length = 0;
|
||
|
SET_STATE(s_length);
|
||
|
} else if (parser->flags & WS_HAS_MASK) {
|
||
|
SET_STATE(s_mask);
|
||
|
parser->require = 4;
|
||
|
} else if (parser->length) {
|
||
|
SET_STATE(s_body);
|
||
|
parser->require = parser->length;
|
||
|
NOTIFY_CB(frame_header);
|
||
|
} else {
|
||
|
SET_STATE(s_start);
|
||
|
NOTIFY_CB(frame_header);
|
||
|
NOTIFY_CB(frame_end);
|
||
|
}
|
||
|
|
||
|
frame_offset++;
|
||
|
break;
|
||
|
case s_length:
|
||
|
while(HAS_DATA() && parser->require) {
|
||
|
parser->length <<= 8;
|
||
|
parser->length |= (unsigned char)CC;
|
||
|
parser->require--;
|
||
|
frame_offset++;
|
||
|
p++;
|
||
|
}
|
||
|
p--;
|
||
|
if(!parser->require) {
|
||
|
if (parser->flags & WS_HAS_MASK) {
|
||
|
SET_STATE(s_mask);
|
||
|
parser->require = 4;
|
||
|
} else if (parser->length) {
|
||
|
SET_STATE(s_body);
|
||
|
parser->require = parser->length;
|
||
|
NOTIFY_CB(frame_header);
|
||
|
} else {
|
||
|
SET_STATE(s_start);
|
||
|
NOTIFY_CB(frame_header);
|
||
|
NOTIFY_CB(frame_end);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case s_mask:
|
||
|
while(HAS_DATA() && parser->require) {
|
||
|
parser->mask[4 - parser->require--] = CC;
|
||
|
frame_offset++;
|
||
|
p++;
|
||
|
}
|
||
|
p--;
|
||
|
if(!parser->require) {
|
||
|
if(parser->length) {
|
||
|
SET_STATE(s_body);
|
||
|
parser->require = parser->length;
|
||
|
NOTIFY_CB(frame_header);
|
||
|
} else {
|
||
|
SET_STATE(s_start);
|
||
|
NOTIFY_CB(frame_header);
|
||
|
NOTIFY_CB(frame_end);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case s_body:
|
||
|
if(parser->require) {
|
||
|
if(p + parser->require <= end) {
|
||
|
EMIT_DATA_CB(frame_body, p, parser->require);
|
||
|
p += parser->require;
|
||
|
parser->require = 0;
|
||
|
frame_offset = p - data;
|
||
|
} else {
|
||
|
EMIT_DATA_CB(frame_body, p, end - p);
|
||
|
parser->require -= end - p;
|
||
|
p = end;
|
||
|
parser->offset += p - data - frame_offset;
|
||
|
frame_offset = 0;
|
||
|
}
|
||
|
|
||
|
p--;
|
||
|
}
|
||
|
if(!parser->require) {
|
||
|
SET_STATE(s_start);
|
||
|
NOTIFY_CB(frame_end);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
assertFalse("Unreachable case");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return GET_NPARSED();
|
||
|
}
|
||
|
|
||
|
void websocket_parser_decode(char * dst, const char * src, size_t len, websocket_parser * parser) {
|
||
|
size_t i = 0;
|
||
|
for(; i < len; i++) {
|
||
|
dst[i] = src[i] ^ parser->mask[(i + parser->mask_offset) % 4];
|
||
|
}
|
||
|
|
||
|
parser->mask_offset = (uint8_t) ((i + parser->mask_offset) % 4);
|
||
|
}
|
||
|
|
||
|
uint8_t websocket_decode(char * dst, const char * src, size_t len, const char mask[4], uint8_t mask_offset) {
|
||
|
size_t i = 0;
|
||
|
for(; i < len; i++) {
|
||
|
dst[i] = src[i] ^ mask[(i + mask_offset) % 4];
|
||
|
}
|
||
|
|
||
|
return (uint8_t) ((i + mask_offset) % 4);
|
||
|
}
|
||
|
|
||
|
size_t websocket_calc_frame_size(websocket_flags flags, size_t data_len) {
|
||
|
size_t size = data_len + 2; // body + 2 bytes of head
|
||
|
if(data_len >= 126) {
|
||
|
if(data_len > 0xFFFF) {
|
||
|
size += 8;
|
||
|
} else {
|
||
|
size += 2;
|
||
|
}
|
||
|
}
|
||
|
if(flags & WS_HAS_MASK) {
|
||
|
size += 4;
|
||
|
}
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t websocket_build_frame(char * frame, websocket_flags flags, const char mask[4], const char * data, size_t data_len) {
|
||
|
size_t body_offset = 0;
|
||
|
frame[0] = 0;
|
||
|
frame[1] = 0;
|
||
|
if(flags & WS_FIN) {
|
||
|
frame[0] = (char) (1 << 7);
|
||
|
}
|
||
|
frame[0] |= flags & WS_OP_MASK;
|
||
|
if(flags & WS_HAS_MASK) {
|
||
|
frame[1] = (char) (1 << 7);
|
||
|
}
|
||
|
if(data_len < 126) {
|
||
|
frame[1] |= data_len;
|
||
|
body_offset = 2;
|
||
|
} else if(data_len <= 0xFFFF) {
|
||
|
frame[1] |= 126;
|
||
|
frame[2] = (char) (data_len >> 8);
|
||
|
frame[3] = (char) (data_len & 0xFF);
|
||
|
body_offset = 4;
|
||
|
} else {
|
||
|
frame[1] |= 127;
|
||
|
frame[2] = (char) ((data_len >> 56) & 0xFF);
|
||
|
frame[3] = (char) ((data_len >> 48) & 0xFF);
|
||
|
frame[4] = (char) ((data_len >> 40) & 0xFF);
|
||
|
frame[5] = (char) ((data_len >> 32) & 0xFF);
|
||
|
frame[6] = (char) ((data_len >> 24) & 0xFF);
|
||
|
frame[7] = (char) ((data_len >> 16) & 0xFF);
|
||
|
frame[8] = (char) ((data_len >> 8) & 0xFF);
|
||
|
frame[9] = (char) ((data_len) & 0xFF);
|
||
|
body_offset = 10;
|
||
|
}
|
||
|
if(flags & WS_HAS_MASK) {
|
||
|
if(mask != NULL) {
|
||
|
memcpy(&frame[body_offset], mask, 4);
|
||
|
}
|
||
|
websocket_decode(&frame[body_offset + 4], data, data_len, &frame[body_offset], 0);
|
||
|
body_offset += 4;
|
||
|
} else {
|
||
|
memcpy(&frame[body_offset], data, data_len);
|
||
|
}
|
||
|
|
||
|
return body_offset + data_len;
|
||
|
}
|