Created
December 30, 2024 10:07
-
-
Save Hakkadaikon/26f6f7878927cf395417c3cdf9bb340b to your computer and use it in GitHub Desktop.
Websocket frame parse (RFC6455)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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