blob: 9c793fa24da1b53988f0fff7aabf2f4c81c20c27 [file] [log] [blame] [raw]
/*
copyright: Boaz Segev, 2017
license: MIT
Feel free to copy, use and enjoy according to the license specified.
*/
#ifndef H_WEBSOCKET_PARSER_H
/**\file
A single file Websocket message parser and Websocket message wrapper, decoupled
from the IO.
Notice that this header file library includes static funnction declerations that
must be implemented by the including file (the callbacks).
*/
#define H_WEBSOCKET_PARSER_H
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/* *****************************************************************************
API - Internal Helpers
***************************************************************************** */
/** used internally to mask and unmask client messages. */
void websocket_xmask(void *msg, size_t len, uint32_t mask);
/* *****************************************************************************
API - Message Wrapping
***************************************************************************** */
/** returns the length of the buffer required to wrap a message `len` long */
static inline size_t websocket_wrapped_len(uint64_t len);
/**
* Wraps a Websocket server message and writes it to the target buffer.
*
* The `first` and `last` flags can be used to support message fragmentation.
*
* * target: the target buffer to write to.
* * msg: the message to be wrapped.
* * len: the message length.
* * text: set to 1 to indicate this is a UTF-8 message or 0 for binary.
* * first: set to 1 if `msg` points the begining of the message.
* * last: set to 1 if `msg + len` ends the message.
* * rsv: accepts a 3 bit value for the rsv websocket message bits.
*
* Returns the number of bytes written. Always `websocket_wrapped_len(len)`
*/
static size_t websocket_server_wrap(void *target, void *msg, size_t len,
char text, char first, char last,
unsigned char rsv);
/**
* Wraps a Websocket client message and writes it to the target buffer.
*
* The `first` and `last` flags can be used to support message fragmentation.
*
* * target: the target buffer to write to.
* * msg: the message to be wrapped.
* * len: the message length.
* * text: set to 1 to indicate this is a UTF-8 message or 0 for binary.
* * first: set to 1 if `msg` points the begining of the message.
* * last: set to 1 if `msg + len` ends the message.
* * rsv: accepts a 3 bit value for the rsv websocket message bits.
*
* Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4`
*/
static size_t websocket_client_wrap(void *target, void *msg, size_t len,
char text, char first, char last,
unsigned char rsv);
/* *****************************************************************************
Callbacks - Required functions that must be inplemented to use this header
***************************************************************************** */
static void websocket_on_unwrapped(void *udata, void *msg, size_t len,
char first, char last, char text,
unsigned char rsv);
static void websocket_on_protocol_ping(void *udata, void *msg, size_t len);
static void websocket_on_protocol_pong(void *udata, void *msg, size_t len);
static void websocket_on_protocol_close(void *udata);
static void websocket_on_protocol_error(void *udata);
/* *****************************************************************************
API - Parsing (unwrapping)
***************************************************************************** */
/**
* Returns the minimal buffer required for the next (upcoming) message.
*
* On protocol error, the value 0 is returned (no buffer required).
*/
inline static size_t websocket_buffer_required(void *buffer, size_t len);
/**
* This argumennt structure sets the callbacks and data used for parsing.
*/
struct websocket_consume_args_s {
uint8_t require_masking;
};
/* *****************************************************************************
Implementation
***************************************************************************** */
/* *****************************************************************************
Message masking
***************************************************************************** */
/** used internally to mask and unmask client messages. */
void websocket_xmask(void *msg, size_t len, uint32_t mask) {
const uint64_t xmask = (((uint64_t)mask) << 32) | mask;
while (len >= 8) {
*((uint64_t *)msg) ^= xmask;
len -= 8;
msg = (void *)((uintptr_t)msg + 8);
}
if (len >= 4) {
*((uint32_t *)msg) ^= mask;
len -= 4;
msg = (void *)((uintptr_t)msg + 4);
}
switch (len) {
case 3:
((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2];
/* fallthrough */
case 2:
((uint8_t *)msg)[1] ^= ((uint8_t *)(&mask))[1];
/* fallthrough */
case 1:
((uint8_t *)msg)[0] ^= ((uint8_t *)(&mask))[0];
/* fallthrough */
}
}
/* *****************************************************************************
Message wrapping
***************************************************************************** */
// clang-format off
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
# if defined(__has_include)
# if __has_include(<endian.h>)
# include <endian.h>
# elif __has_include(<sys/endian.h>)
# include <sys/endian.h>
# endif
# endif
# if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__
# endif
#endif
// clang-format on
#ifdef __BIG_ENDIAN__
/** byte swap 64 bit integer */
#define bswap64(i) (i)
#else
// TODO: check for __builtin_bswap64
/** byte swap 64 bit integer */
#define bswap64(i) \
((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \
(((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \
(((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \
(((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56))
#endif
/** returns the length of the buffer required to wrap a message `len` long */
static inline size_t websocket_wrapped_len(uint64_t len) {
if (len < 126)
return len + 2;
if (len < (1UL << 16))
return len + 4;
return len + 10;
}
/**
* Wraps a Websocket server message and writes it to the target buffer.
*
* The `first` and `last` flags can be used to support message fragmentation.
*
* * target: the target buffer to write to.
* * msg: the message to be wrapped.
* * len: the message length.
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
* * first: set to 1 if `msg` points the begining of the message.
* * last: set to 1 if `msg + len` ends the message.
* * client: set to 1 to use client mode (data masking).
*
* Further opcode values:
* * %x0 denotes a continuation frame
* * %x1 denotes a text frame
* * %x2 denotes a binary frame
* * %x3-7 are reserved for further non-control frames
* * %x8 denotes a connection close
* * %x9 denotes a ping
* * %xA denotes a pong
* * %xB-F are reserved for further control frames
*
* Returns the number of bytes written. Always `websocket_wrapped_len(len)`
*/
static size_t websocket_server_wrap(void *target, void *msg, size_t len,
char opcode, char first, char last,
unsigned char rsv) {
if (len < 126) {
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = len;
memcpy(((uint8_t *)target) + 2, msg, len);
return len + 2;
} else if (len < (1UL << 16)) {
/* head is 4 bytes */
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = 126;
((uint16_t *)target)[1] = htons(len);
memcpy((uint8_t *)target + 4, msg, len);
return len + 4;
}
/* Really Long Message */
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = 127;
((uint64_t *)((uint8_t *)target + 2))[0] = bswap64(len);
memcpy((uint8_t *)target + 10, msg, len);
return len + 10;
}
/**
* Wraps a Websocket client message and writes it to the target buffer.
*
* The `first` and `last` flags can be used to support message fragmentation.
*
* * target: the target buffer to write to.
* * msg: the message to be wrapped.
* * len: the message length.
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
* * first: set to 1 if `msg` points the begining of the message.
* * last: set to 1 if `msg + len` ends the message.
*
* Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4`
*/
static size_t websocket_client_wrap(void *target, void *msg, size_t len,
char opcode, char first, char last,
unsigned char rsv) {
uint32_t mask = rand() + 0x01020408;
if (len < 126) {
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = len;
((uint8_t *)target)[1] |= 128;
((uint32_t *)((uint8_t *)target + 2))[0] = mask;
memcpy(((uint8_t *)target) + 6, msg, len);
websocket_xmask((uint8_t *)target + 6, len, mask);
return len + 6;
} else if (len < (1UL << 16)) {
/* head is 4 bytes */
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = 126 | 128;
((uint16_t *)target)[1] = htons(len);
((uint32_t *)((uint8_t *)target + 4))[0] = mask;
memcpy((uint8_t *)target + 8, msg, len);
websocket_xmask((uint8_t *)target + 8, len, mask);
return len + 8;
}
/* Really Long Message */
((uint8_t *)target)[0] =
/*opcode*/ ((first ? opcode : 0) << 4) |
/*rsv*/ ((rsv & 3) << 1) | /*fin*/ (last & 1);
((uint8_t *)target)[1] = 255;
((uint64_t *)((uint8_t *)target + 2))[0] = bswap64(len);
((uint32_t *)((uint8_t *)target + 10))[0] = mask;
memcpy((uint8_t *)target + 14, msg, len);
websocket_xmask((uint8_t *)target + 14, len, mask);
return len + 14;
}
/* *****************************************************************************
Message unwrapping
***************************************************************************** */
/**
* Returns the minimal buffer required for the next (upcoming) message.
*
* On protocol error, the value 0 is returned (no buffer required).
*/
inline static size_t websocket_buffer_required(void *buffer, size_t len) {
if (len < 2)
return 2;
size_t ret = (((uint8_t *)buffer)[0] & 127);
if (ret < 126)
return ret;
switch (ret) {
case 126:
if (len < 4)
return 4;
return htons(((uint16_t *)buffer)[1]);
case 127:
if (len < 10)
return 10;
return bswap64(((uint64_t *)((uint8_t *)buffer + 2))[0]);
default:
return 0;
}
}
/**
* Consumes the data in the buffer, calling any callbacks required.
*
* Returns the remaining data in the existing buffer (can be 0).
*/
static size_t websocket_consume(void *buffer, size_t len, void *udata,
struct websocket_consume_args_s args) {
size_t border = websocket_buffer_required(buffer, len);
size_t reminder = len;
uint8_t *pos = (uint8_t *)buffer;
while (border <= reminder) {
/* parse head */
uint64_t payload_len;
char text, first, last;
unsigned char rsv;
uint8_t *payload;
payload_len = pos[1] & 127;
switch (payload_len) { /* length */
case 126:
payload = pos + 6;
payload_len = ntohs(*((uint32_t *)(pos + 2)));
break;
case 127:
payload = pos + 10;
payload_len = bswap64(*(uint64_t *)(pos + 2));
break;
default:
break;
}
/* unmask? */
if (pos[1] & 128) {
/* masked */
const uint32_t mask = *((uint32_t *)payload);
payload += 4;
websocket_xmask(payload + 4, payload_len, mask);
} else if (args.require_masking) {
/* error */
websocket_on_protocol_error(udata);
}
/* call callback */
switch (pos[0] & 15) {
case 0:
/* continuation frame */
websocket_on_unwrapped(udata, payload, payload_len, 0, (pos[0] >> 7), 0,
((pos[0] >> 4) & 15));
break;
case 1:
/* text frame */
websocket_on_unwrapped(udata, payload, payload_len, 1, (pos[0] >> 7), 1,
((pos[0] >> 4) & 15));
break;
case 2:
/* data frame */
websocket_on_unwrapped(udata, payload, payload_len, 1, (pos[0] >> 7), 0,
((pos[0] >> 4) & 15));
break;
case 8:
/* close frame */
websocket_on_protocol_close(udata);
break;
case 9:
/* ping frame */
websocket_on_protocol_ping(udata, payload, payload_len);
break;
case 10:
/* pong frame */
websocket_on_protocol_pong(udata, payload, payload_len);
break;
default:
websocket_on_protocol_error(udata);
}
/* step forward */
reminder -= border;
pos += border;
}
/* reset buffer state - support pipelining */
if (!reminder)
return 0;
memmove(buffer, (uint8_t *)buffer + len - reminder, reminder);
return reminder;
}
#endif