Skip to content

Instantly share code, notes, and snippets.

@Hakkadaikon
Created December 30, 2024 10:07
Show Gist options
  • Save Hakkadaikon/26f6f7878927cf395417c3cdf9bb340b to your computer and use it in GitHub Desktop.
Save Hakkadaikon/26f6f7878927cf395417c3cdf9bb340b to your computer and use it in GitHub Desktop.
Websocket frame parse (RFC6455)
/**
* @file websocket_frame_parse.c
*
* @brief Parses each parameter of a websocket frame stored in network byte order.
* @see RFC6455 (https://datatracker.ietf.org/doc/html/rfc6455)
*/
#include <alloca.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef struct _WebsocketFrame {
uint8_t fin;
uint8_t rsv1;
uint8_t rsv2;
uint8_t rsv3;
uint8_t opcode;
uint8_t mask;
uint8_t payload_len;
uint64_t ext_payload_len;
uint8_t masking_key[4];
char* payload;
} WebsocketFrame, *PWebsocketFrame;
size_t calcHeaderSize(const size_t ext_payload_len);
bool parseWebsocketFrame(const uint8_t* raw, const size_t frame_size, PWebsocketFrame frame);
void createTestFrame(const size_t ext_payload_len, uint8_t* frame);
/**
* @brief Entry point
*/
int main(void)
{
size_t ext_payload_len = 3 * 1024 * 1024;
size_t header_size = calcHeaderSize(ext_payload_len);
size_t frame_size = header_size + ext_payload_len;
uint8_t frame[frame_size];
memset(frame, 0x00, sizeof(frame));
createTestFrame(ext_payload_len, frame);
WebsocketFrame out;
out.payload = (char*)alloca((size_t)(ext_payload_len + 1));
//out.payload = (char*)malloc((size_t)(ext_payload_len + 1));
if (!parseWebsocketFrame((const uint8_t*)&frame, frame_size, &out)) {
printf("Failed to parse websocket frame.\n");
return 1;
}
printf("fsv : %d\n", out.fin);
printf("rsv1 : %d\n", out.rsv1);
printf("rsv2 : %d\n", out.rsv2);
printf("rsv3 : %d\n", out.rsv3);
printf("opcode : %d\n", out.opcode);
printf("mask : %d\n", out.mask);
printf("payload_len : %d\n", out.payload_len);
printf("ext_payload_len : %ld\n", out.ext_payload_len);
printf("payload : %s\n", out.payload);
return 0;
}
/**
* @brief Calculate the header size of a websocket frame
*
* @param[in] ext_payload_len ext payload length of websocket frame
*
* @return Calculated header size
*/
size_t calcHeaderSize(const size_t ext_payload_len)
{
// fin - payload_len : 2byte
// masking key : 4byte
// ext header : 0(if payload_len <= 125)
// 2(if payload_len = 126)
// 8(if payload_len = 127)
if (ext_payload_len >= 65536) {
return 14;
} else if (ext_payload_len >= 126) {
return 8;
}
return 6;
}
/**
* @brief Create a websocket frame for testing.
*
* @param[in] ext_payload_len ext payload length of websocket frame
* @param[out] frame Output destination of parsed frame
*
* @detail The payload is set to 01234567890...
*/
void createTestFrame(const size_t ext_payload_len, uint8_t* frame)
{
size_t payload_len;
size_t offset = 0;
if (ext_payload_len >= 65536) {
payload_len = 127;
} else if (ext_payload_len >= 126) {
payload_len = 126;
} else {
payload_len = ext_payload_len;
}
frame[offset] |= 0b10000000; // fin(1)
frame[offset] |= 0b00000000; // rsv1(0)
frame[offset] |= 0b00000000; // rsv2(0)
frame[offset] |= 0b00000000; // rsv3(0)
frame[offset] |= 0b00000001; // opcode(1)
offset++;
frame[offset] |= 0b10000000; // mask(1)
frame[offset] |= payload_len; // payload_len(127)
offset++;
if (payload_len == 127) {
frame[offset++] = (ext_payload_len >> 56) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 48) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 40) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 32) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 24) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 16) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 8) & (0x000000FF);
frame[offset++] = (ext_payload_len >> 0) & (0x000000FF);
} else if (payload_len == 126) {
frame[offset++] = (ext_payload_len >> 8);
frame[offset++] = (ext_payload_len & 0x00FF);
}
uint8_t masking_key[] = {0x12, 0x34, 0x56, 0x78};
for (int i = 0; i < 4; i++) {
frame[offset++] = masking_key[i];
}
for (int i = 0; i < ext_payload_len; i++) {
frame[offset++] = (((i % 10) + '0') ^ masking_key[i % 4]);
}
}
/**
* @brief Parse raw data in network byte order into a websocket flame structure
*
* @param[in] raw raw data (network byte order)
* @param[in] frame_size Size of webSocket frame
* @param[out] frame Output destination of parsed frame
*
* @return true: Parse was successful / false: Failed parse
*/
bool parseWebsocketFrame(const uint8_t* raw, const size_t frame_size, PWebsocketFrame frame)
{
if (frame_size < 2) {
return false;
}
if (frame == NULL) {
return false;
}
if (frame->payload == NULL) {
return false;
}
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-------+-+-------------+-------------------------------+
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
// |I|S|S|S| (4) |A| (7) | (16/64) |
// |N|V|V|V| |S| | (if payload len==126/127) |
// | |1|2|3| |K| | |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// | Extended payload length continued, if payload len == 127 |
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |Masking-key, if MASK set to 1 |
// +-------------------------------+-------------------------------+
// | Masking-key (continued) | Payload Data |
// +-------------------------------- - - - - - - - - - - - - - - - +
// : Payload Data continued ... :
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// | Payload Data continued ... |
// +---------------------------------------------------------------+
// 0-7bit
// +-+-+-+-+-------+
// |F|R|R|R| opcode|
// |I|S|S|S| (4) |
// |N|V|V|V| |
// | |1|2|3| |
// +-+-+-+-+-------+
frame->fin = (raw[0] & 0x80) >> 7;
frame->rsv1 = (raw[0] & 0x40) >> 6;
frame->rsv2 = (raw[0] & 0x20) >> 5;
frame->rsv3 = (raw[0] & 0x10) >> 4;
frame->opcode = (raw[0] & 0x0F);
// 8-15bit
// +-+-------------+
// |M| Payload len |
// |A| (7) |
// |S| |
// |K| |
// +-+-------------+
frame->mask = (raw[1] & 0x80) >> 7;
frame->payload_len = (raw[1] & 0x7F);
size_t frame_offset = 2;
// Expanded payload length
// - nothing (if payload_len <= 125)
// - 16-31bit (if payload_len = 126)
// - 16-79bit (if payload_len = 127)
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-------------------------------+
// | Extended payload length |
// | (16/64) |
// | (if payload len==126/127) |
// | |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// | Extended payload length continued, if payload len == 127 |
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |
// +-------------------------------+
if (frame->payload_len == 126) {
if (frame_size < 4) {
return false;
}
frame->ext_payload_len = (raw[2] << 8) | raw[3];
frame_offset += 2;
} else if (frame->payload_len == 127) {
if (frame_size < 10) {
return false;
}
for (int i = 0; i < 8; i++) {
frame->ext_payload_len = (frame->ext_payload_len << 8) | raw[2 + i];
}
frame_offset += 8;
} else {
frame->ext_payload_len = frame->payload_len;
}
// masking key
// - 16-47bit (if payload_len <= 125)
// - 32-63bit (if payload_len = 126)
// - 80-111bit (if payload_len = 127)
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |Masking-key, if MASK set to 1 |
// +-------------------------------+-------------------------------+
// | Masking-key (continued) |
// +--------------------------------
if (frame->mask) {
if (frame_size < frame_offset + 4) {
return false;
}
memcpy(frame->masking_key, &raw[frame_offset], 4);
frame_offset += sizeof(frame->masking_key);
}
const uint8_t* payload_raw = &raw[frame_offset];
for (size_t i = 0; i < frame->ext_payload_len; i++) {
frame->payload[i] =
payload_raw[i] ^ (frame->mask ? frame->masking_key[i % 4] : 0);
}
frame->payload[frame->ext_payload_len] = '\0';
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment