godot/thirdparty/wslay/wslay_frame.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

439 lines
13 KiB
C
Raw Permalink Normal View History

/*
* Wslay - The WebSocket Library
*
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "wslay_frame.h"
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include "wslay_net.h"
#define wslay_min(A, B) (((A) < (B)) ? (A) : (B))
int wslay_frame_context_init(wslay_frame_context_ptr *ctx,
const struct wslay_frame_callbacks *callbacks,
void *user_data) {
*ctx = malloc(sizeof(struct wslay_frame_context));
if (*ctx == NULL) {
return -1;
}
memset(*ctx, 0, sizeof(struct wslay_frame_context));
(*ctx)->istate = RECV_HEADER1;
(*ctx)->ireqread = 2;
(*ctx)->ostate = PREP_HEADER;
(*ctx)->user_data = user_data;
(*ctx)->ibufmark = (*ctx)->ibuflimit = (*ctx)->ibuf;
(*ctx)->callbacks = *callbacks;
return 0;
}
void wslay_frame_context_free(wslay_frame_context_ptr ctx) { free(ctx); }
ssize_t wslay_frame_send(wslay_frame_context_ptr ctx,
struct wslay_frame_iocb *iocb) {
if (iocb->data_length > iocb->payload_length) {
return WSLAY_ERR_INVALID_ARGUMENT;
}
if (ctx->ostate == PREP_HEADER) {
uint8_t *hdptr = ctx->oheader;
memset(ctx->oheader, 0, sizeof(ctx->oheader));
*hdptr |= (uint8_t)((uint8_t)(iocb->fin << 7) & 0x80u);
*hdptr |= (uint8_t)((uint8_t)(iocb->rsv << 4) & 0x70u);
/* Suppress stubborn gcc-10 warning */
*hdptr |= (uint8_t)((uint8_t)(iocb->opcode << 0) & 0xfu);
++hdptr;
*hdptr |= (uint8_t)((uint8_t)(iocb->mask << 7) & 0x80u);
if (wslay_is_ctrl_frame(iocb->opcode) && iocb->payload_length > 125) {
return WSLAY_ERR_INVALID_ARGUMENT;
}
if (iocb->payload_length < 126) {
*hdptr |= (uint8_t)iocb->payload_length;
++hdptr;
} else if (iocb->payload_length < (1 << 16)) {
uint16_t len = htons((uint16_t)iocb->payload_length);
*hdptr |= 126;
++hdptr;
memcpy(hdptr, &len, 2);
hdptr += 2;
} else if (iocb->payload_length < (1ull << 63)) {
uint64_t len = hton64(iocb->payload_length);
*hdptr |= 127;
++hdptr;
memcpy(hdptr, &len, 8);
hdptr += 8;
} else {
/* Too large payload length */
return WSLAY_ERR_INVALID_ARGUMENT;
}
if (iocb->mask) {
if (ctx->callbacks.genmask_callback(ctx->omaskkey, 4, ctx->user_data) !=
0) {
return WSLAY_ERR_INVALID_CALLBACK;
} else {
ctx->omask = 1;
memcpy(hdptr, ctx->omaskkey, 4);
hdptr += 4;
}
}
ctx->ostate = SEND_HEADER;
ctx->oheadermark = ctx->oheader;
ctx->oheaderlimit = hdptr;
ctx->opayloadlen = iocb->payload_length;
ctx->opayloadoff = 0;
}
if (ctx->ostate == SEND_HEADER) {
ptrdiff_t len = ctx->oheaderlimit - ctx->oheadermark;
ssize_t r;
int flags = 0;
if (iocb->data_length > 0) {
flags |= WSLAY_MSG_MORE;
}
r = ctx->callbacks.send_callback(ctx->oheadermark, (size_t)len, flags,
ctx->user_data);
if (r > 0) {
if (r > len) {
return WSLAY_ERR_INVALID_CALLBACK;
} else {
ctx->oheadermark += r;
if (ctx->oheadermark == ctx->oheaderlimit) {
ctx->ostate = SEND_PAYLOAD;
} else {
return WSLAY_ERR_WANT_WRITE;
}
}
} else {
return WSLAY_ERR_WANT_WRITE;
}
}
if (ctx->ostate == SEND_PAYLOAD) {
size_t totallen = 0;
if (iocb->data_length > 0) {
if (ctx->omask) {
uint8_t temp[4096];
const uint8_t *datamark = iocb->data,
*datalimit = iocb->data + iocb->data_length;
while (datamark < datalimit) {
size_t datalen = (size_t)(datalimit - datamark);
const uint8_t *writelimit =
datamark + wslay_min(sizeof(temp), datalen);
size_t writelen = (size_t)(writelimit - datamark);
ssize_t r;
size_t i;
for (i = 0; i < writelen; ++i) {
temp[i] = datamark[i] ^ ctx->omaskkey[(ctx->opayloadoff + i) % 4];
}
r = ctx->callbacks.send_callback(temp, writelen, 0, ctx->user_data);
if (r > 0) {
if ((size_t)r > writelen) {
return WSLAY_ERR_INVALID_CALLBACK;
} else {
datamark += r;
ctx->opayloadoff += (uint64_t)r;
totallen += (size_t)r;
}
} else {
if (totallen > 0) {
break;
} else {
return WSLAY_ERR_WANT_WRITE;
}
}
}
} else {
ssize_t r;
r = ctx->callbacks.send_callback(iocb->data, iocb->data_length, 0,
ctx->user_data);
if (r > 0) {
if ((size_t)r > iocb->data_length) {
return WSLAY_ERR_INVALID_CALLBACK;
} else {
ctx->opayloadoff += (uint64_t)r;
totallen = (size_t)r;
}
} else {
return WSLAY_ERR_WANT_WRITE;
}
}
}
if (ctx->opayloadoff == ctx->opayloadlen) {
ctx->ostate = PREP_HEADER;
}
return (ssize_t)totallen;
}
return WSLAY_ERR_INVALID_ARGUMENT;
}
ssize_t wslay_frame_write(wslay_frame_context_ptr ctx,
struct wslay_frame_iocb *iocb, uint8_t *buf,
size_t buflen, size_t *pwpayloadlen) {
uint8_t *buf_last = buf;
size_t i;
size_t hdlen;
*pwpayloadlen = 0;
if (iocb->data_length > iocb->payload_length) {
return WSLAY_ERR_INVALID_ARGUMENT;
}
switch (ctx->ostate) {
case PREP_HEADER:
case PREP_HEADER_NOBUF:
hdlen = 2;
if (iocb->payload_length < 126) {
/* nothing to do */
} else if (iocb->payload_length < (1 << 16)) {
hdlen += 2;
} else if (iocb->payload_length < (1ull << 63)) {
hdlen += 8;
}
if (iocb->mask) {
hdlen += 4;
}
if (buflen < hdlen) {
ctx->ostate = PREP_HEADER_NOBUF;
return 0;
}
memset(buf_last, 0, hdlen);
*buf_last |= (uint8_t)((uint8_t)(iocb->fin << 7) & 0x80u);
*buf_last |= (uint8_t)((uint8_t)(iocb->rsv << 4) & 0x70u);
/* Suppress stubborn gcc-10 warning */
*buf_last |= (uint8_t)((uint8_t)(iocb->opcode << 0) & 0xfu);
++buf_last;
*buf_last |= (uint8_t)((uint8_t)(iocb->mask << 7) & 0x80u);
if (wslay_is_ctrl_frame(iocb->opcode) && iocb->payload_length > 125) {
return WSLAY_ERR_INVALID_ARGUMENT;
}
if (iocb->payload_length < 126) {
*buf_last |= (uint8_t)iocb->payload_length;
++buf_last;
} else if (iocb->payload_length < (1 << 16)) {
uint16_t len = htons((uint16_t)iocb->payload_length);
*buf_last |= 126;
++buf_last;
memcpy(buf_last, &len, 2);
buf_last += 2;
} else if (iocb->payload_length < (1ull << 63)) {
uint64_t len = hton64(iocb->payload_length);
*buf_last |= 127;
++buf_last;
memcpy(buf_last, &len, 8);
buf_last += 8;
} else {
/* Too large payload length */
return WSLAY_ERR_INVALID_ARGUMENT;
}
if (iocb->mask) {
if (ctx->callbacks.genmask_callback(ctx->omaskkey, 4, ctx->user_data) !=
0) {
return WSLAY_ERR_INVALID_CALLBACK;
} else {
ctx->omask = 1;
memcpy(buf_last, ctx->omaskkey, 4);
buf_last += 4;
}
}
ctx->ostate = SEND_PAYLOAD;
ctx->opayloadlen = iocb->payload_length;
ctx->opayloadoff = 0;
buflen -= (size_t)(buf_last - buf);
/* fall through */
case SEND_PAYLOAD:
if (iocb->data_length > 0) {
size_t writelen = wslay_min(buflen, iocb->data_length);
if (ctx->omask) {
for (i = 0; i < writelen; ++i) {
*buf_last++ =
iocb->data[i] ^ ctx->omaskkey[(ctx->opayloadoff + i) % 4];
}
} else {
memcpy(buf_last, iocb->data, writelen);
buf_last += writelen;
}
ctx->opayloadoff += writelen;
*pwpayloadlen = writelen;
}
if (ctx->opayloadoff == ctx->opayloadlen) {
ctx->ostate = PREP_HEADER;
}
return buf_last - buf;
default:
return WSLAY_ERR_INVALID_ARGUMENT;
}
}
static void wslay_shift_ibuf(wslay_frame_context_ptr ctx) {
ptrdiff_t len = ctx->ibuflimit - ctx->ibufmark;
memmove(ctx->ibuf, ctx->ibufmark, (size_t)len);
ctx->ibuflimit = ctx->ibuf + len;
ctx->ibufmark = ctx->ibuf;
}
static ssize_t wslay_recv(wslay_frame_context_ptr ctx) {
ssize_t r;
if (ctx->ibufmark != ctx->ibuf) {
wslay_shift_ibuf(ctx);
}
r = ctx->callbacks.recv_callback(
ctx->ibuflimit, (size_t)(ctx->ibuf + sizeof(ctx->ibuf) - ctx->ibuflimit),
0, ctx->user_data);
if (r > 0) {
ctx->ibuflimit += r;
} else {
r = WSLAY_ERR_WANT_READ;
}
return r;
}
#define WSLAY_AVAIL_IBUF(ctx) ((size_t)(ctx->ibuflimit - ctx->ibufmark))
ssize_t wslay_frame_recv(wslay_frame_context_ptr ctx,
struct wslay_frame_iocb *iocb) {
ssize_t r;
if (ctx->istate == RECV_HEADER1) {
uint8_t fin, opcode, rsv, payloadlen;
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
if ((r = wslay_recv(ctx)) <= 0) {
return r;
}
}
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
return WSLAY_ERR_WANT_READ;
}
fin = (ctx->ibufmark[0] >> 7) & 1;
rsv = (ctx->ibufmark[0] >> 4) & 7;
opcode = ctx->ibufmark[0] & 0xfu;
ctx->iom.opcode = opcode;
ctx->iom.fin = fin;
ctx->iom.rsv = rsv;
++ctx->ibufmark;
ctx->imask = (ctx->ibufmark[0] >> 7) & 1;
payloadlen = ctx->ibufmark[0] & 0x7fu;
++ctx->ibufmark;
if (wslay_is_ctrl_frame(opcode) && (payloadlen > 125 || !fin)) {
return WSLAY_ERR_PROTO;
}
if (payloadlen == 126) {
ctx->istate = RECV_EXT_PAYLOADLEN;
ctx->ireqread = 2;
} else if (payloadlen == 127) {
ctx->istate = RECV_EXT_PAYLOADLEN;
ctx->ireqread = 8;
} else {
ctx->ipayloadlen = payloadlen;
ctx->ipayloadoff = 0;
if (ctx->imask) {
ctx->istate = RECV_MASKKEY;
ctx->ireqread = 4;
} else {
ctx->istate = RECV_PAYLOAD;
}
}
}
if (ctx->istate == RECV_EXT_PAYLOADLEN) {
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
if ((r = wslay_recv(ctx)) <= 0) {
return r;
}
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
return WSLAY_ERR_WANT_READ;
}
}
ctx->ipayloadlen = 0;
ctx->ipayloadoff = 0;
memcpy((uint8_t *)&ctx->ipayloadlen + (8 - ctx->ireqread), ctx->ibufmark,
ctx->ireqread);
ctx->ipayloadlen = ntoh64(ctx->ipayloadlen);
ctx->ibufmark += ctx->ireqread;
if (ctx->ireqread == 8) {
if (ctx->ipayloadlen < (1 << 16) || ctx->ipayloadlen & (1ull << 63)) {
return WSLAY_ERR_PROTO;
}
} else if (ctx->ipayloadlen < 126) {
return WSLAY_ERR_PROTO;
}
if (ctx->imask) {
ctx->istate = RECV_MASKKEY;
ctx->ireqread = 4;
} else {
ctx->istate = RECV_PAYLOAD;
}
}
if (ctx->istate == RECV_MASKKEY) {
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
if ((r = wslay_recv(ctx)) <= 0) {
return r;
}
if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
return WSLAY_ERR_WANT_READ;
}
}
memcpy(ctx->imaskkey, ctx->ibufmark, 4);
ctx->ibufmark += 4;
ctx->istate = RECV_PAYLOAD;
}
if (ctx->istate == RECV_PAYLOAD) {
uint8_t *readlimit, *readmark;
uint64_t rempayloadlen = ctx->ipayloadlen - ctx->ipayloadoff;
if (WSLAY_AVAIL_IBUF(ctx) == 0 && rempayloadlen > 0) {
if ((r = wslay_recv(ctx)) <= 0) {
return r;
}
}
readmark = ctx->ibufmark;
readlimit = WSLAY_AVAIL_IBUF(ctx) < rempayloadlen
? ctx->ibuflimit
: ctx->ibufmark + rempayloadlen;
if (ctx->imask) {
for (; ctx->ibufmark != readlimit; ++ctx->ibufmark, ++ctx->ipayloadoff) {
ctx->ibufmark[0] ^= ctx->imaskkey[ctx->ipayloadoff % 4];
}
} else {
ctx->ibufmark = readlimit;
ctx->ipayloadoff += (uint64_t)(readlimit - readmark);
}
iocb->fin = ctx->iom.fin;
iocb->rsv = ctx->iom.rsv;
iocb->opcode = ctx->iom.opcode;
iocb->payload_length = ctx->ipayloadlen;
iocb->mask = ctx->imask;
iocb->data = readmark;
iocb->data_length = (size_t)(ctx->ibufmark - readmark);
if (ctx->ipayloadlen == ctx->ipayloadoff) {
ctx->istate = RECV_HEADER1;
ctx->ireqread = 2;
}
return (ssize_t)iocb->data_length;
}
return WSLAY_ERR_INVALID_ARGUMENT;
}