libdiscord

libdiscord.git
git clone git://git.lenczewski.org/libdiscord.git
Log | Files | Refs | README | LICENSE

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:
Mextras/testbot.c | 22+++++++++-------------
Minclude/libdiscord.h | 18++++++++++--------
Asrc/conn.c | 27+++++++++++++++++++++++++++
Asrc/gateway.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/internal.c | 5+++++
Asrc/internal.h | 293+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ioreq.c | 42++++++++++++++++++++++++++++++++++++++++++
Msrc/libdiscord.c | 169++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Asrc/voice.c | 29+++++++++++++++++++++++++++++
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; +}