clii/src/wsock.c
reemo 1b775f59cc i finally get to go home
not touching a computer for a while weow
2023-12-28 18:08:23 +00:00

525 lines
11 KiB
C

#include "wsock.h"
/** UTILITIES **/
/*****************/
#define WS_REORDER_BUFLEN (sizeof(long double))
static int _sys_is_net_order() {
const uint16_t test = 0xB00B;
return ((char*)&test)[0] == 0xB0;
}
void swap_order(char* in, int length) {
if(_sys_is_net_order() || length > WS_REORDER_BUFLEN)
return;
char buf[WS_REORDER_BUFLEN];
for(int i = 0; i < length; ++i)
buf[i] = in[length - i - 1];
memcpy(in, buf, length);
}
void swap_order_copy(char* in, char* out, int length) {
memcpy(out, in, length);
swap_order(out, length);
}
/** DATA BUFFER **/
/*******************/
buffer_t* buffer_create() {
buffer_t* buf = malloc(sizeof(buffer_t));
buf->data = NULL;
buf->next = NULL;
buf->length = 0;
return buf;
}
int buffer_is_empty(buffer_t* buf) {
return buf->data == NULL;
}
int buffer_length(buffer_t* buf) {
if(buf == NULL)
return 0;
return buf->length + buffer_length(buf->next);
}
void buffer_append
(buffer_t* buf, const char* data, int length)
{
if(buf->data != NULL) {
if(buf->next == NULL)
buf->next = buffer_create();
buffer_append(buf->next, data, length);
} else {
buf->data = malloc(length);
strncpy(buf->data, data, length);
buf->length = length;
}
}
void buffer_append_str(buffer_t* buf, const char* data) {
buffer_append(buf, data, strlen(data));
}
static int _buffer_read(buffer_t* buf, char* out, int n) {
if(buffer_length(buf) == 0)
return 0;
int i = 0;
while(buf != NULL && (!n || (n && i < n))) {
if(!n || buf->length <= n - i) {
strncpy(out + i, buf->data, buf->length);
i += buf->length;
buf = buf->next;
} else if(n && buf->length > n - i) {
strncpy(out + i, buf->data, n - i);
i += n - i;
}
}
return i;
}
int buffer_peek(buffer_t* buf, char* out, int length) {
return _buffer_read(buf, out, length);
}
int buffer_peek_str(buffer_t* buf, char* out, int length) {
int read = _buffer_read(buf, out, length);
if(read)
out[read] = '\0';
return read;
}
int buffer_read(buffer_t* buf, char** out) {
int length = buffer_length(buf);
if(length)
*out = malloc(length);
else
*out = NULL;
_buffer_read(buf, *out, 0);
return length;
}
int buffer_read_str(buffer_t* buf, char** out) {
int length = buffer_length(buf);
if(length) {
*out = malloc(length + 1);
(*out)[length] = '\0';
} else
*out = NULL;
_buffer_read(buf, *out, 0);
return length;
}
int buffer_flush(buffer_t* buf, char** out) {
int length = buffer_read(buf, out);
buffer_clear(buf);
return length;
}
int buffer_flush_str(buffer_t* buf, char** out) {
int length = buffer_read_str(buf, out);
buffer_clear(buf);
return length;
}
static void _buffer_clear(buffer_t* buf, int root) {
if(buf == NULL)
return;
_buffer_clear(buf->next, 0);
free(buf->data);
if(!root)
free(buf);
else {
buf->data = NULL;
buf->next = NULL;
buf->length = 0;
}
}
void buffer_clear(buffer_t* buf) {
_buffer_clear(buf, 1);
}
void buffer_free(buffer_t* buf) {
buffer_clear(buf);
free(buf);
}
/** WEB SOCKET **/
/******************/
static int _wsock_header_length(char* head) {
return 2
+ ((head[1] & 0x80) == 0 ? 0 : 4)
+ ((head[1] & 0x7F) < 0x7E ? 0
: ((head[1] & 0x7F) == 0x7E ? 2 : 8));
}
static int _wsock_read_header
(char* head, _ws_head_t* out)
{
out->head_length = _wsock_header_length(head);
out->fin = 0x80 & head[0];
out->opcode = 0x0F & head[0];
out->masked = 0x80 & head[1];
// garbage in check
if((head[0] & 0x70) != 0
|| ((head[0] & 0x0F) > 0x2 && (head[0] & 0x0F) < 0x8)
|| ((head[0] & 0x0F) > 0xA && (head[0] & 0x0F) <= 0xF))
return 0;
// invalid parameters check
if((out->opcode == WS_CONT &&
out->first_opcode == WS_OP_UNDEF)
|| (out->opcode != WS_CONT &&
!WS_OP_IS_CTRL(out->opcode) &&
out->first_opcode != WS_OP_UNDEF)
|| (WS_OP_IS_CTRL(out->opcode) && !out->fin))
return 0;
// cache first opcode of fragmented data set
if(out->opcode != WS_CONT && !WS_OP_IS_CTRL(out->opcode))
out->first_opcode = out->opcode;
int mask_start;
if((head[1] & 0x7F) < 0x7E) {
out->body_length = head[1] & 0x7F;
mask_start = 2;
} else if((head[1] & 0x7F) == 0x7E) {
swap_order_copy(head + 2, (char*)&out->body_length, 2);
mask_start = 4;
} else {
swap_order_copy(head + 2, (char*)&out->body_length, 8);
mask_start = 10;
}
if(out->masked)
memcpy(out->mask, head + mask_start, 4);
return 1;
}
static void _wsock_mask(char* mask, char* msg, int length) {
for(int i = 0; i < length; ++i)
msg[i] = msg[i] ^ mask[i % 4];
}
static void _wsock_gen_mask(char* mask) {
for(int i = 0; i < 4; ++i)
mask[i] = rand() % 0xFF;
}
wsock_t* wsock_open
(const char* host, const char* path, uint16_t port)
{
char s_port[6];
sprintf(s_port, "%i", port);
int sock;
struct addrinfo hints, *result, *rp;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = 0;
if(getaddrinfo(host, s_port, &hints, &result) != 0)
return NULL;
for(rp = result; rp != NULL; rp = rp->ai_next) {
if((sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
continue;
if(connect(sock, rp->ai_addr, rp->ai_addrlen) != -1)
break;
}
freeaddrinfo(result);
if(rp == NULL)
return NULL;
// todo: randomize wsock key
char s_shake[4096];
sprintf(s_shake,
"GET %s HTTP/1.1\r\n"
"Host: %s:%i\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEC==\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n",
path,
host, port
);
if(send(sock, s_shake, strlen(s_shake), MSG_NOSIGNAL) <= 0) {
shutdown(sock, SHUT_RDWR);
return NULL;
}
s_shake[0] = '\0';
struct pollfd pfd;
pfd.fd = sock;
pfd.events = POLLIN;
int total = 0;
char* end;
while((end = strstr(s_shake, "\r\n\r\n")) == NULL) {
if(total + 1 >= sizeof(s_shake)
|| poll(&pfd, 1, 5000) <= 0)
{
shutdown(sock,SHUT_RDWR);
return NULL;
}
total = recv(sock, s_shake, sizeof(s_shake) - 1, MSG_PEEK);
s_shake[total] = '\0';
}
total = (end - s_shake) + 4;
recv(sock, s_shake, total, 0);
s_shake[total + 1] = '\0';
const char resp_check[] = "HTTP/1.1 101 Switching Protocols";
if(strncmp(s_shake, resp_check, sizeof(resp_check) - 1) != 0)
{
shutdown(sock, SHUT_RDWR);
return NULL;
}
wsock_t* wsock = malloc(sizeof(wsock_t));
wsock->sock = sock;
wsock->mode = WS_BLOCK | WS_FULL_RECV;
wsock->head_buf.first_opcode = WS_OP_UNDEF;
wsock->head_buf.opcode = WS_OP_UNDEF;
wsock->recv_buf = buffer_create();
wsock->frag_buf = buffer_create();
return wsock;
}
void wsock_mode_set(wsock_t* ws, int mode) {
ws->mode = mode | WS_FULL_SEND;
}
int wsock_mode_get(wsock_t* ws) {
return ws->mode;
}
static int _srecv
(wsock_t* ws, char* out, int len, int flags)
{
int got = recv(ws->sock, out, len, flags);
if(got == EAGAIN || got == EWOULDBLOCK)
return 0;
else if(got <= 0) {
wsock_close(ws);
return -1;
} else
return got;
}
int wsock_recv(wsock_t* ws, char** out) {
if(!wsock_is_open(ws))
return -1;
char tmp[WS_INTERNAL_BUFLEN];
int got, flags;
do {
if(ws->head_buf.opcode == WS_OP_UNDEF) {
flags = MSG_PEEK |
((ws->mode & WS_NOBLOCK) ? MSG_DONTWAIT : 0);
got = _srecv(ws, tmp, 2, flags);
if(got < 2 && got >= 0) {
got = 0;
continue;
} else if(got < 0)
return got;
int head_length = _wsock_header_length(tmp);
got = _srecv(ws, tmp, head_length, flags);
if(got < head_length && got >= 0) {
got = 0;
continue;
} else if(got < 0)
return got;
flags &= ~MSG_PEEK;
got = _srecv(ws, tmp, head_length, flags);
if(got < 0)
return got;
if(!_wsock_read_header(tmp, &ws->head_buf)
|| ws->head_buf.body_length > WS_MAX_LENGTH) {
wsock_close(ws);
return -1;
}
}
if(ws->head_buf.body_length > 0) {
// todo: complex fetching algorithm that queries
// internal recv buffer if more info is available
// and continues to buffer data in the same call
// if WS_FULL_RECV is not enabled, however i have
// no current use case for this and this will still
// work provided wsock_recv is called enough times
// that the wsock buffer eventually received all
// information off of the recv buffer
int buflen = buffer_length(ws->recv_buf);
flags = (ws->mode & WS_NOBLOCK) ? MSG_DONTWAIT : 0;
got = _srecv(ws, tmp, __MIN(sizeof(tmp), ws->head_buf.body_length - buflen), flags);
if(got < 0)
return got;
else if(got > 0)
buffer_append(ws->recv_buf, tmp, got);
if(got + buflen < ws->head_buf.body_length) {
got = 0;
continue;
}
}
char* body;
int body_length = buffer_flush(ws->recv_buf, &body);
if(body != NULL) {
if(ws->head_buf.masked)
_wsock_mask((char*)ws->head_buf.mask,
body, body_length);
if(!WS_OP_IS_CTRL(ws->head_buf.opcode)) {
buffer_append(ws->frag_buf, body, body_length);
free(body);
}
}
switch(ws->head_buf.opcode) {
case WS_CONT:
case WS_TEXT:
case WS_BIN:
if(ws->head_buf.fin) {
if(ws->head_buf.first_opcode == WS_TEXT
&& !(ws->mode & WS_RECV_ALL_BIN))
got = buffer_flush_str(ws->frag_buf, out);
else
got = buffer_flush(ws->frag_buf, out);
ws->head_buf.first_opcode = WS_OP_UNDEF;
}
break;
case WS_CLOSE:
wsock_close(ws);
return -1;
case WS_PING:
wsock_send_raw(ws, WS_PONG, body, body_length);
break;
case WS_PONG:
break;
}
if(WS_OP_IS_CTRL(ws->head_buf.opcode)) {
got = 0;
free(body);
}
ws->head_buf.opcode = WS_OP_UNDEF;
if(!ws->head_buf.fin) {
got = 0;
continue;
} else
break;
} while(ws->mode & WS_FULL_RECV);
return got;
}
// todo: support sending fragments at some point
int wsock_send_raw
(wsock_t* ws, int opcode, char* body, uint64_t length)
{
if(!wsock_is_open(ws))
return -1;
char header[14], *mask;
int header_bsize_len =
(length < 0x7E) ? 0 :
((length <= 0xFFFF) ? 2 : 8);
int header_length =
2 + (length > 0 ? 4 : 0) + header_bsize_len;
header[0] = 0x80 | opcode;
header[1] = 0x80 | ((length < 0x7E) ? length
: (length <= 0xFFFF ? 0x7E : 0x7F));
uint16_t length_u16 = (uint16_t)length;
if(header_bsize_len > 0)
swap_order_copy(
(header_bsize_len == 2
? (char*)&length_u16 : (char*)&length),
header + 2, header_bsize_len);
if(length > 0) {
mask = header + 2 + header_bsize_len;
_wsock_gen_mask(mask);
}
if(send(ws->sock, header,
header_length, MSG_NOSIGNAL) <= 0)
{
wsock_close(ws);
return -1;
}
if(length > 0) {
_wsock_mask(mask, body, length);
if(send(ws->sock, body, length, MSG_NOSIGNAL) <= 0) {
wsock_close(ws);
return -1;
}
_wsock_mask(mask, body, length);
}
return header_length + length;
}
int wsock_send(wsock_t* ws, char* body, int length) {
return wsock_send_raw(ws, WS_BIN, body, length);
}
int wsock_send_str(wsock_t* ws, char* body) {
if(ws->mode & WS_SEND_ALL_BIN)
return wsock_send(ws, body, strlen(body));
else
return wsock_send_raw(ws, WS_TEXT, body, strlen(body));
}
int wsock_ping(wsock_t* ws, char* msg) {
if(msg == NULL)
return wsock_send_raw(ws, WS_PING, NULL, 0);
else
return wsock_send_raw(ws, WS_PING, msg, strlen(msg));
}
int wsock_is_open(wsock_t* ws) {
return ws->sock >= 0;
}
void wsock_close(wsock_t* ws) {
if(!wsock_is_open(ws))
return;
const char close_frame[2] = {0x88, 0x00};
send(ws->sock, close_frame,
sizeof(close_frame), MSG_NOSIGNAL);
shutdown(ws->sock, SHUT_RDWR);
ws->sock = -1;
}
void wsock_free(wsock_t* ws) {
wsock_close(ws);
buffer_free(ws->recv_buf);
buffer_free(ws->frag_buf);
free(ws);
}