Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions src/channels/discord.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ struct discord_ctx {
char *rx_buf;
size_t rx_len;
size_t rx_cap;
int rx_skip;
int heartbeat_interval_ms;
uint64_t last_seq;
char *session_id;
Expand Down Expand Up @@ -180,25 +181,8 @@ static void discord_rx_reset(struct discord_ctx *dc)
/** Appends one RX fragment; returns 0 ok, -1 overflow. */
static int discord_rx_append(struct discord_ctx *dc, const void *in, size_t len)
{
if (len > RX_MAX || dc->rx_len > RX_MAX - len)
return -1;
if (dc->rx_cap < dc->rx_len + len) {
size_t need = dc->rx_len + len + 1;
size_t ncap = dc->rx_cap ? dc->rx_cap * 2 : 4096;
while (ncap < need && ncap < RX_MAX)
ncap *= 2;
if (need > RX_MAX)
return -1;
char *p = realloc(dc->rx_buf, ncap);
if (!p)
return -1;
dc->rx_buf = p;
dc->rx_cap = ncap;
}
memcpy(dc->rx_buf + dc->rx_len, in, len);
dc->rx_len += len;
dc->rx_buf[dc->rx_len] = '\0';
return 0;
return discord_helpers_rx_append(&dc->rx_buf, &dc->rx_len, &dc->rx_cap, in, len,
RX_MAX);
}

static int discord_send_json(struct lws *wsi, const char *json)
Expand Down Expand Up @@ -558,9 +542,15 @@ static int callback_discord(struct lws *wsi, enum lws_callback_reasons reason,
}
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
if (dc->rx_skip) {
if (lws_is_final_fragment(wsi))
dc->rx_skip = 0;
break;
}
if (discord_rx_append(dc, in, len) != 0) {
fprintf(stderr, "shellclaw: discord: gateway payload too large\n");
discord_rx_reset(dc);
dc->rx_skip = !lws_is_final_fragment(wsi);
break;
}
if (lws_is_final_fragment(wsi)) {
Expand Down
36 changes: 36 additions & 0 deletions src/channels/discord_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "channels/discord_helpers.h"
#include "channels/channel.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *discord_lifecycle_str(discord_lifecycle_t lc)
Expand Down Expand Up @@ -139,3 +140,38 @@ int discord_helpers_send_backoff_ms(int attempt, double retry_after_sec, int jit
sleep_ms = cap_ms;
return sleep_ms;
}

int discord_helpers_rx_append(char **buf, size_t *len, size_t *cap, const void *in,
size_t chunk_len, size_t max_cap)
{
size_t need;
size_t ncap;
char *p;

if (!buf || !len || !cap || (!in && chunk_len != 0) || max_cap < 1)
return -1;
if (chunk_len > max_cap || *len > max_cap - chunk_len)
return -1;
need = *len + chunk_len + 1;
if (need > max_cap)
return -1;
if (*cap < need) {
ncap = *cap ? *cap * 2 : 4096;
while (ncap < need)
ncap *= 2;
if (ncap > max_cap)
ncap = max_cap;
if (ncap < need)
return -1;
p = realloc(*buf, ncap);
if (!p)
return -1;
*buf = p;
*cap = ncap;
}
if (chunk_len > 0)
memcpy(*buf + *len, in, chunk_len);
*len += chunk_len;
(*buf)[*len] = '\0';
return 0;
}
9 changes: 8 additions & 1 deletion src/channels/discord_helpers.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @file discord_helpers.h
* @brief Pure helpers shared by Discord channel and unit tests (allowlist, session id, mentions,
* REST 429 backoff calculation).
* REST 429 backoff calculation, Gateway RX append).
*/

#ifndef SHELLCLAW_DISCORD_HELPERS_H
Expand Down Expand Up @@ -53,6 +53,13 @@ int discord_helpers_route_message_create(const cJSON *payload, const char *const
int allowed_count, const char *bot_user_id,
char *session_out, size_t session_outsz);

/**
* Append one Gateway RX fragment, growing so payload plus a trailing NUL always fit.
* @return 0 on success, -1 if the total would exceed @p max_cap or allocation failed.
*/
int discord_helpers_rx_append(char **buf, size_t *len, size_t *cap, const void *in,
size_t chunk_len, size_t max_cap);

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/gateway/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static const struct lws_protocols protocols[] = {
{
.name = "ws",
.callback = ws_callback,
.rx_buffer_size = 256,
.rx_buffer_size = WS_RX_BUFFER_SIZE,
},
{ .name = NULL },
};
Expand Down
2 changes: 2 additions & 0 deletions src/gateway/http_lws.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ extern "C" {
#define CONFIG_BODY_MAX 65536
#define BODY_BUF_SIZE CONFIG_BODY_MAX
#define ASAP_BODY_MAX (1024 * 1024)
/** Per-callback WebChat RX. Must fit a full `{"type":"message","text":...}` frame (MSG_MAX). */
#define WS_RX_BUFFER_SIZE 32768

enum http_method {
HTTP_GET = 1,
Expand Down
62 changes: 62 additions & 0 deletions tests/test_discord_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "channels/channel.h"
#include "cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ASSERT(c) do { if (!(c)) { fprintf(stderr, "FAIL: %s:%d %s\n", __FILE__, __LINE__, #c); return 1; } } while (0)
Expand Down Expand Up @@ -99,6 +100,65 @@ static int test_route_message_create_fixture(void)
return 0;
}

/** Exact payload fill of a power-of-two cap must still reserve one byte for NUL. */
static int test_rx_append_grows_for_nul(void)
{
char *buf = NULL;
size_t len = 0;
size_t cap = 0;
char a[4095];
char one = 'Z';
char *big;
char *big2;

memset(a, 'A', sizeof(a));
ASSERT(discord_helpers_rx_append(&buf, &len, &cap, a, sizeof(a), 512 * 1024) == 0);
ASSERT(len == sizeof(a));
ASSERT(cap >= len + 1);
ASSERT(buf[len] == '\0');
ASSERT(discord_helpers_rx_append(&buf, &len, &cap, &one, 1, 512 * 1024) == 0);
ASSERT(len == 4096);
ASSERT(cap >= 4097);
ASSERT(buf[4096] == '\0');
ASSERT(buf[4095] == 'Z');

free(buf);
buf = NULL;
len = 0;
cap = 0;
big = malloc(65536);
big2 = malloc(65536);
ASSERT(big && big2);
memset(big, 'B', 65536);
memset(big2, 'C', 65536);
ASSERT(discord_helpers_rx_append(&buf, &len, &cap, big, 65536, 512 * 1024) == 0);
ASSERT(len == 65536);
ASSERT(cap == 131072);
ASSERT(discord_helpers_rx_append(&buf, &len, &cap, big2, 65536, 512 * 1024) == 0);
ASSERT(len == 131072);
ASSERT(cap >= 131073);
ASSERT(buf[131072] == '\0');
ASSERT(buf[0] == 'B' && buf[65535] == 'B');
ASSERT(buf[65536] == 'C' && buf[131071] == 'C');
free(big);
free(big2);
free(buf);
return 0;
}

static int test_rx_append_rejects_over_max(void)
{
char *buf = NULL;
size_t len = 0;
size_t cap = 0;
char chunk[16];

memset(chunk, 'x', sizeof(chunk));
ASSERT(discord_helpers_rx_append(&buf, &len, &cap, chunk, sizeof(chunk), 8) != 0);
ASSERT(buf == NULL);
return 0;
}

int main(void)
{
RUN(test_allow_entry_equals());
Expand All @@ -108,6 +168,8 @@ int main(void)
RUN(test_backoff_math());
RUN(test_lifecycle_str());
RUN(test_route_message_create_fixture());
RUN(test_rx_append_grows_for_nul());
RUN(test_rx_append_rejects_over_max());
printf("test_discord_helpers: all tests passed\n");
return 0;
}
Loading