commit b0b64cb48afab868eda1690d342815db559df460
parent 8119a66137fbc20a624ecfd47b10e72ac3b2d422
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Fri, 20 Jun 2025 20:28:58 +0100
Connected to gateway.discord.gg
Diffstat:
9 files changed, 618 insertions(+), 36 deletions(-)
diff --git a/extras/testbot.c b/extras/testbot.c
@@ -22,29 +22,25 @@ main(void)
return -1;
}
- while (1) {
- struct discord_event evs[16];
- size_t cap = sizeof evs / sizeof evs[0];
- int len = discord_poll_events(discord, evs, cap);
- if (len < 0) {
- fprintf(stderr, "error: ");
- discord_error(discord);
- break;
- }
+ struct discord_event evs[16];
+ size_t cap = sizeof evs / sizeof evs[0];
- for (int i = 0; i < len; i++) {
+ int res;
+ while ((res = discord_poll_events(discord, evs, cap)) > 0) {
+ for (int i = 0; i < res; i++) {
struct discord_event *ev = &evs[i];
switch (ev->type) {
case DISCORD_EVENT_API:
printf("got api message: \"%.*s\"\n",
- ev->api.len, ev->api.ptr);
+ ev->tag.api.len,
+ ev->tag.api.ptr);
break;
case DISCORD_EVENT_GATEWAY:
printf("got gateway message: \"%.*s\"\n",
- ev->gateway.len,
- ev->gateway.ptr);
+ ev->tag.gateway.len,
+ ev->tag.gateway.ptr);
break;
case DISCORD_EVENT_VOICE:
diff --git a/include/libdiscord.h b/include/libdiscord.h
@@ -67,13 +67,15 @@ enum discord_event_type {
DISCORD_EVENT_VOICE,
};
+union discord_event_tag {
+ struct discord_api_event api;
+ struct discord_gateway_event gateway;
+ struct discord_voice_event voice;
+};
+
struct discord_event {
enum discord_event_type type;
- union {
- struct discord_api_event api;
- struct discord_gateway_event gateway;
- struct discord_voice_event voice;
- };
+ union discord_event_tag tag;
};
struct discord;
@@ -84,6 +86,9 @@ discord_sizeof(void);
int
discord_init(struct discord *ctx, void *mem, size_t len);
+void
+discord_free(struct discord *ctx);
+
int
discord_connect_gateway(struct discord *ctx);
@@ -97,9 +102,6 @@ discord_poll_events(struct discord *ctx,
void
discord_error(struct discord *ctx);
-void
-discord_free(struct discord *ctx);
-
// discord api calls
// ========================================================================
diff --git a/src/conn.c b/src/conn.c
@@ -0,0 +1,27 @@
+#include "internal.h"
+
+extern inline int
+conn_queue_connect(struct io_uring *uring, struct conn *conn);
+
+extern inline void
+conn_finish_connect(struct conn *conn, int result);
+
+extern inline int
+conn_queue_recv(struct io_uring *uring, struct conn *conn);
+
+extern inline void
+conn_finish_recv(struct conn *conn, int result);
+
+extern inline int
+conn_queue_send(struct io_uring *uring, struct conn *conn);
+
+extern inline void
+conn_finish_send(struct conn *conn, int result);
+
+extern inline int
+conn_do_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+extern inline int
+conn_handle_io(struct io_uring *uring, struct conn *conn, int result,
+ struct discord_event *ev);
diff --git a/src/gateway.c b/src/gateway.c
@@ -0,0 +1,49 @@
+#include "internal.h"
+
+int
+gateway_connect(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ if (conn->socket < 0 && !conn->ai_ptr->ai_next) {
+ freeaddrinfo(conn->addrinfo);
+ return -1;
+ }
+
+ if (conn->socket < 0 && conn->ai_ptr->ai_next) {
+ conn->ai_ptr = conn->ai_ptr->ai_next;
+ return conn_queue_connect(uring, conn);
+ }
+
+ char host[NI_MAXHOST], serv[NI_MAXSERV];
+ getnameinfo(conn->ai_ptr->ai_addr, conn->ai_ptr->ai_addrlen,
+ host, sizeof host, serv, sizeof serv, NI_NUMERICSERV);
+ printf("discord: connected successfully to %s (%s:%s)\n",
+ conn->ai_ptr->ai_canonname, host, serv);
+
+ freeaddrinfo(conn->addrinfo);
+
+ return conn_do_tls_handshake(uring, conn, ev);
+}
+
+int
+gateway_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ printf("discord: connected over tls\n");
+
+ return -1;
+}
+
+int
+gateway_recv(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}
+
+int
+gateway_send(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}
diff --git a/src/internal.c b/src/internal.c
@@ -0,0 +1,5 @@
+#include "utils.c"
+#include "ioreq.c"
+#include "conn.c"
+#include "gateway.c"
+#include "voice.c"
diff --git a/src/internal.h b/src/internal.h
@@ -0,0 +1,293 @@
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#define _GNU_SOURCE 1
+#define _XOPEN_SOURCE 700
+
+#include <unistd.h>
+
+#include <liburing.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <netdb.h>
+
+#include "libdiscord.h"
+#include "utils.h"
+
+struct ioreq_connect {
+ int fd;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+};
+
+struct ioreq_recv {
+ int fd;
+ void *buf;
+ size_t len;
+ int flags;
+};
+
+struct ioreq_send {
+ int fd;
+ void *buf;
+ size_t len;
+ int flags;
+};
+
+union ioreq_tag {
+ struct ioreq_connect connect;
+ struct ioreq_recv recv;
+ struct ioreq_send send;
+};
+
+struct conn;
+
+struct ioreq {
+ enum { IOREQ_CONNECT, IOREQ_RECV, IOREQ_SEND, } type;
+ union ioreq_tag tag;
+};
+
+int
+queue_ioreqs(struct io_uring *uring, struct ioreq *reqs, size_t len);
+
+struct conn_ops {
+ int (*connect)(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+ int (*tls_handshake)(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+ int (*send)(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+ int (*recv)(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+};
+
+struct conn {
+ int socket;
+ struct addrinfo *addrinfo, *ai_ptr;
+
+ SSL *ssl;
+ BIO *ssl_bio, *net_bio;
+
+ struct ioreq ioreq;
+
+ unsigned char *buf;
+ size_t cur, len, cap;
+
+ struct conn_ops *ops;
+};
+
+inline int
+conn_queue_connect(struct io_uring *uring, struct conn *conn)
+{
+ int sock = socket(conn->ai_ptr->ai_family,
+ conn->ai_ptr->ai_socktype | SOCK_NONBLOCK,
+ conn->ai_ptr->ai_protocol);
+ if (sock < 0)
+ return -1;
+
+ conn->socket = sock;
+
+ conn->ioreq.type = IOREQ_CONNECT;
+ conn->ioreq.tag.connect.fd = conn->socket;
+ conn->ioreq.tag.connect.addr = conn->ai_ptr->ai_addr;
+ conn->ioreq.tag.connect.addrlen = conn->ai_ptr->ai_addrlen;
+
+ char host[NI_MAXHOST], serv[NI_MAXSERV];
+ getnameinfo(conn->ai_ptr->ai_addr, conn->ai_ptr->ai_addrlen,
+ host, sizeof host, serv, sizeof serv, NI_NUMERICSERV);
+ printf("discord: connecting to %s (%s:%s)\n",
+ conn->ai_ptr->ai_canonname, host, serv);
+
+ return queue_ioreqs(uring, &conn->ioreq, 1);
+}
+
+inline void
+conn_finish_connect(struct conn *conn, int result)
+{
+ if (result < 0) {
+ close(conn->socket);
+ conn->socket = -1;
+ }
+}
+
+inline int
+conn_queue_recv(struct io_uring *uring, struct conn *conn)
+{
+ char *buf;
+ int len = BIO_nwrite0(conn->net_bio, &buf);
+
+ conn->ioreq.type = IOREQ_RECV;
+ conn->ioreq.tag.recv.buf = buf;
+ conn->ioreq.tag.recv.len = len;
+ conn->ioreq.tag.recv.flags = 0;
+
+ printf("discord: receiving %d bytes\n", len);
+
+ return queue_ioreqs(uring, &conn->ioreq, 1);
+}
+
+inline void
+conn_finish_recv(struct conn *conn, int result)
+{
+ printf("discord: received %d bytes\n", result);
+
+ BIO_nwrite(conn->net_bio, NULL, result);
+}
+
+inline int
+conn_queue_send(struct io_uring *uring, struct conn *conn)
+{
+ char *buf;
+ int len = BIO_nread0(conn->net_bio, &buf);
+
+ conn->ioreq.type = IOREQ_SEND;
+ conn->ioreq.tag.send.buf = buf;
+ conn->ioreq.tag.send.len = len;
+ conn->ioreq.tag.send.flags = 0;
+
+ printf("discord: sending %d bytes\n", len);
+
+ return queue_ioreqs(uring, &conn->ioreq, 1);
+}
+
+inline void
+conn_finish_send(struct conn *conn, int result)
+{
+ printf("discord: sent %d bytes\n", result);
+
+ BIO_nread(conn->net_bio, NULL, result);
+}
+
+inline int
+conn_do_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ int ret = SSL_do_handshake(conn->ssl);
+ if (ret == 1) /* handshake completed */
+ return conn->ops->tls_handshake(uring, conn, ev);
+
+ if (ret == 0) /* connection closed */
+ return -1;
+
+ int err = SSL_get_error(conn->ssl, ret);
+ if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)
+ goto error;
+
+ int pending = BIO_ctrl_pending(conn->net_bio);
+ if (pending) /* need to send bytes to advance handshake */
+ return conn_queue_send(uring, conn);
+
+ int expecting = BIO_ctrl_get_read_request(conn->net_bio);
+ if (expecting) /* need to recv bytes to advance handshake */
+ return conn_queue_recv(uring, conn);
+
+error:
+ fprintf(stderr, "discord: failed to complete tls handshake: %d\n", err);
+ ERR_print_errors_fp(stderr);
+ return -1;
+}
+
+inline int
+conn_handle_io(struct io_uring *uring, struct conn *conn, int result,
+ struct discord_event *ev)
+{
+ switch (conn->ioreq.type) {
+ case IOREQ_CONNECT: {
+ conn_finish_connect(conn, result);
+
+ return conn->ops->connect(uring, conn, ev);
+ } break;
+
+ case IOREQ_RECV: {
+ conn_finish_recv(conn, result);
+
+ if (!SSL_is_init_finished(conn->ssl))
+ return conn_do_tls_handshake(uring, conn, ev);
+
+ return conn->ops->recv(uring, conn, ev);
+ } break;
+
+ case IOREQ_SEND: {
+ conn_finish_send(conn, result);
+
+ if (!SSL_is_init_finished(conn->ssl))
+ return conn_do_tls_handshake(uring, conn, ev);
+
+ return conn->ops->send(uring, conn, ev);
+ } break;
+ }
+}
+
+#define GATEWAY_HOST "gateway.discord.gg"
+#define GATEWAY_PORT "443"
+
+struct discord_gateway {
+ struct conn conn;
+
+ unsigned char buf[4096];
+};
+
+#define VOICE_HOST "voice.discord.com"
+#define VOICE_PORT "443"
+
+struct discord_voice {
+ struct conn conn;
+
+ unsigned char buf[4096];
+};
+
+#define DISCORD_HOST "discord.com"
+#define DISCORD_PORT "443"
+
+struct discord {
+ struct arena arena;
+
+ struct io_uring io_uring;
+
+ SSL_CTX *ssl_ctx;
+
+ struct discord_gateway gateway;
+ struct discord_voice voice;
+};
+
+int
+gateway_connect(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+gateway_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+gateway_recv(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+gateway_send(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+voice_connect(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+voice_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+voice_recv(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+int
+voice_send(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev);
+
+#endif /* INTERNAL_H */
diff --git a/src/ioreq.c b/src/ioreq.c
@@ -0,0 +1,42 @@
+#include "internal.h"
+
+int
+queue_ioreqs(struct io_uring *uring, struct ioreq *reqs, size_t len)
+{
+ for (size_t i = 0; i < len; i++) {
+ struct ioreq *ioreq = &reqs[i];
+
+ struct io_uring_sqe *sqe = io_uring_get_sqe(uring);
+ if (!sqe)
+ return -1;
+
+ switch (ioreq->type) {
+ case IOREQ_CONNECT:
+ io_uring_prep_connect(sqe,
+ ioreq->tag.connect.fd,
+ ioreq->tag.connect.addr,
+ ioreq->tag.connect.addrlen);
+ break;
+
+ case IOREQ_SEND:
+ io_uring_prep_send(sqe,
+ ioreq->tag.send.fd,
+ ioreq->tag.recv.buf,
+ ioreq->tag.recv.len,
+ ioreq->tag.recv.flags);
+ break;
+
+ case IOREQ_RECV:
+ io_uring_prep_recv(sqe,
+ ioreq->tag.recv.fd,
+ ioreq->tag.recv.buf,
+ ioreq->tag.recv.len,
+ ioreq->tag.recv.flags);
+ break;
+ }
+
+ io_uring_sqe_set_data(sqe, ioreq);
+ }
+
+ return 0;
+}
diff --git a/src/libdiscord.c b/src/libdiscord.c
@@ -1,10 +1,4 @@
-#include "libdiscord.h"
-
-#include "utils.h"
-
-struct discord {
- struct arena arena;
-};
+#include "internal.h"
size_t
discord_sizeof(void)
@@ -12,20 +6,129 @@ discord_sizeof(void)
return sizeof(struct discord);
}
+static SSL_CTX *
+create_ssl_ctx(void)
+{
+ SSL_CTX *ctx;
+ if (!(ctx = SSL_CTX_new(TLS_method())))
+ return NULL;
+
+ SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+
+ if (!SSL_CTX_set_default_verify_paths(ctx))
+ goto error;
+
+ return ctx;
+
+error:
+ SSL_CTX_free(ctx);
+
+ return NULL;
+}
+
int
discord_init(struct discord *ctx, void *mem, size_t cap)
{
+ memset(ctx, 0, discord_sizeof());
+
ctx->arena.ptr = mem;
ctx->arena.cap = cap;
arena_reset(&ctx->arena);
+ unsigned entries = 32, flags = 0;
+ if (io_uring_queue_init(entries, &ctx->io_uring, flags) < 0)
+ return -1;
+
+ if (!(ctx->ssl_ctx = create_ssl_ctx())) {
+ io_uring_queue_exit(&ctx->io_uring);
+ return -1;
+ }
+
return 0;
}
+void
+discord_free(struct discord *ctx)
+{
+ SSL_shutdown(ctx->gateway.conn.ssl);
+ SSL_free(ctx->gateway.conn.ssl);
+ BIO_free(ctx->gateway.conn.net_bio);
+
+ SSL_shutdown(ctx->voice.conn.ssl);
+ SSL_free(ctx->voice.conn.ssl);
+ BIO_free(ctx->voice.conn.net_bio);
+
+ io_uring_queue_exit(&ctx->io_uring);
+
+ SSL_CTX_free(ctx->ssl_ctx);
+}
+
+static SSL *
+create_ssl(SSL_CTX *ctx, BIO **ssl_bio, BIO **net_bio, size_t buffer_size,
+ char const *host)
+{
+ SSL *ssl;
+ if (!(ssl = SSL_new(ctx)))
+ return NULL;
+
+ SSL_set_connect_state(ssl);
+ SSL_set_tlsext_host_name(ssl, host);
+ SSL_set1_host(ssl, host);
+
+ if (BIO_new_bio_pair(ssl_bio, buffer_size, net_bio, buffer_size) < 0)
+ goto error;
+
+ SSL_set_bio(ssl, *ssl_bio, *ssl_bio);
+
+ return ssl;
+
+error:
+ SSL_free(ssl);
+
+ return NULL;
+}
+
int
discord_connect_gateway(struct discord *ctx)
{
- return -1;
+ static struct conn_ops gateway_ops = {
+ .connect = gateway_connect,
+ .tls_handshake = gateway_tls_handshake,
+ .recv = gateway_recv,
+ .send = gateway_send,
+ };
+
+ struct conn *conn = &ctx->gateway.conn;
+
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP,
+ .ai_flags = AI_CANONNAME | AI_NUMERICSERV,
+ }, *addrinfo;
+
+ if (getaddrinfo(GATEWAY_HOST, GATEWAY_PORT, &hints, &addrinfo)) {
+ return -1;
+ }
+
+ conn->addrinfo = conn->ai_ptr = addrinfo;
+
+ conn->ssl = create_ssl(ctx->ssl_ctx,
+ &conn->ssl_bio, &conn->net_bio, conn->cap,
+ GATEWAY_HOST);
+
+ if (!conn->ssl) {
+ freeaddrinfo(conn->addrinfo);
+ return -1;
+ }
+
+ conn->buf = ctx->gateway.buf;
+ conn->cap = sizeof ctx->gateway.buf;
+
+ conn->ops = &gateway_ops;
+
+ return conn_queue_connect(&ctx->io_uring, conn);
}
int
@@ -38,17 +141,53 @@ int
discord_poll_events(struct discord *ctx,
struct discord_event *evs, size_t cap)
{
- return -1;
-}
+ assert(evs);
+ assert(cap);
-void
-discord_error(struct discord *ctx)
-{
+ int ret = 0, have_ev = 0;
+ while (ret >= 0 && !have_ev) {
+ io_uring_submit_and_wait(&ctx->io_uring, 1);
+
+ unsigned head, seen = 0;
+ struct io_uring_cqe *cqe;
+ io_uring_for_each_cqe(&ctx->io_uring, head, cqe) {
+ struct ioreq *ioreq = io_uring_cqe_get_data(cqe);
+ assert(ioreq);
+
+ struct conn *conn = TO_PARENT_PTR(ioreq,
+ struct conn,
+ ioreq);
+
+ ret = conn_handle_io(&ctx->io_uring, conn,
+ cqe->res, evs);
+
+ seen++;
+
+ if (ret < 0) /* error */
+ break;
+
+ if (ret == 0) /* more io needed */
+ continue;
+
+ /* have event */
+ have_ev = 1;
+ evs++;
+ cap--;
+
+ if (!cap)
+ break;
+ }
+
+ io_uring_cq_advance(&ctx->io_uring, seen);
+ }
+
+ return ret;
}
void
-discord_free(struct discord *ctx)
+discord_error(struct discord *ctx)
{
+ fprintf(stderr, "internal discord error\n");
}
-#include "utils.c"
+#include "internal.c"
diff --git a/src/voice.c b/src/voice.c
@@ -0,0 +1,29 @@
+#include "internal.h"
+
+int
+voice_connect(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}
+
+int
+voice_tls_handshake(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}
+
+int
+voice_recv(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}
+
+int
+voice_send(struct io_uring *uring, struct conn *conn,
+ struct discord_event *ev)
+{
+ return -1;
+}