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;
 | |
| }
 | 
