commit 8119a66137fbc20a624ecfd47b10e72ac3b2d422
Author: Mikołaj Lenczewski <mblenczewski@gmail.com>
Date: Sat, 7 Jun 2025 15:42:45 +0000
Initial commit
Diffstat:
17 files changed, 541 insertions(+), 0 deletions(-)
diff --git a/.editorconfig b/.editorconfig
@@ -0,0 +1,22 @@
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+# visual studio editorconfig plugin specific settings
+guidelines = 80, 120, 160
+
+[*.{c,h}]
+indent_style = tab
+indent_size = 8
+
+[*.{sh}]
+indent_style = tab
+indent_size = 8
+
+[*.{conf,json,md,txt}]
+indent_style = space
+indent_size = 2
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,7 @@
+bin/
+lib/
+obj/
+
+rfcs/
+
+**/.*.swp
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,18 @@
+The MIT-Zero License
+
+Copyright (c) 2025 Mikołaj Lenczewski <mikolaj@lenczewski.org>
+
+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.
+
+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.
diff --git a/README b/README
@@ -0,0 +1,11 @@
+libdiscord
+==============================================================================
+A simple discord library, written in C11.
+
+libdiscord: Building
+------------------------------------------------------------------------------
+To build, simply run `./build.sh`. Similarly, to clean, run `./clean.sh`.
+
+To build with the "-Werror" compiler argument, run `WERROR=1 ./build.sh`.
+
+To build extra test programs, run `BUILD_EXTRAS=1 ./build.sh`.
diff --git a/build.sh b/build.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+CC="${CC:-clang}"
+AR="${AR:-llvm-ar}"
+RANLIB="${RANLIB:-llvm-ranlib}"
+
+WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror} -Wno-unused-parameter -Wno-format-pedantic"
+FLAGS="-std=c11 -Og -g"
+
+VERSION_MAJOR="$(grep -oe '#define LIBDISCORD_VERSION_MAJOR "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')"
+VERSION_MINOR="$(grep -oe '#define LIBDISCORD_VERSION_MINOR "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')"
+VERSION_PATCH="$(grep -oe '#define LIBDISCORD_VERSION_PATCH "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')"
+VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH"
+
+DEPS="liburing openssl"
+INCS="$(pkg-config --cflags $DEPS)"
+LIBS="$(pkg-config --libs $DEPS)"
+
+set -ex
+
+mkdir -p bin lib obj
+
+$CC -o obj/libdiscord.o -c src/libdiscord.c $WARNINGS $FLAGS -Iinclude $INCS
+$AR rcs lib/libdiscord.$VERSION.a obj/libdiscord.o
+$RANLIB lib/libdiscord.$VERSION.a
+
+$CC -o bin/testbot extras/testbot.c $WARNINGS $FLAGS \
+ -Iinclude $INCS -Llib -ldiscord.$VERSION $LIBS
+
+[ -z "$BUILD_EXTRAS" ] && exit
+
diff --git a/clean.sh b/clean.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+rm -rf bin lib obj
diff --git a/extras/testbot.c b/extras/testbot.c
@@ -0,0 +1,60 @@
+#include <alloca.h>
+#include <stdio.h>
+
+#include "libdiscord.h"
+
+static char buf[8192];
+
+int
+main(void)
+{
+ printf("discord version: %s\n", LIBDISCORD_VERSION);
+
+ struct discord *discord = alloca(discord_sizeof());
+
+ if (discord_init(discord, buf, sizeof buf) < 0) {
+ fprintf(stderr, "Failed to initialise discord ctx\n");
+ return -1;
+ }
+
+ if (discord_connect_gateway(discord) < 0) {
+ fprintf(stderr, "Failed to connect to discord gateway\n");
+ 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;
+ }
+
+ for (int i = 0; i < len; 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);
+ break;
+
+ case DISCORD_EVENT_GATEWAY:
+ printf("got gateway message: \"%.*s\"\n",
+ ev->gateway.len,
+ ev->gateway.ptr);
+ break;
+
+ case DISCORD_EVENT_VOICE:
+ printf("got voice message!\n");
+ break;
+ }
+ }
+ }
+
+ discord_free(discord);
+
+ return 0;
+}
diff --git a/include/libdiscord.h b/include/libdiscord.h
@@ -0,0 +1,112 @@
+#ifndef LIBDISCORD_H
+#define LIBDISCORD_H
+
+#define LIBDISCORD_VERSION_MAJOR "0"
+#define LIBDISCORD_VERSION_MINOR "1"
+#define LIBDISCORD_VERSION_PATCH "0"
+
+#define LIBDISCORD_VERSION \
+ LIBDISCORD_VERSION_MAJOR "." \
+ LIBDISCORD_VERSION_MINOR "." \
+ LIBDISCORD_VERSION_PATCH
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct discord_api_event {
+ char const *ptr;
+ int len;
+};
+
+enum discord_gateway_opcode {
+ DISCORD_GATEWAY_DISPATCH,
+ DISCORD_GATEWAY_HEARTBEAT,
+ DISCORD_GATEWAY_IDENTIFY,
+ DISCORD_GATEWAY_PRESENCE_UPDATE,
+ DISCORD_GATEWAY_VOICE_STATE_UPDATE,
+ DISCORD_GATEWAY_RESUME,
+ DISCORD_GATEWAY_RECONNECT,
+ DISCORD_GATEWAY_REQUEST_GUILD_MEMBERS,
+ DISCORD_GATEWAY_INVALID_SESSION,
+ DISCORD_GATEWAY_HELLO,
+ DISCORD_GATEWAY_HEARTBEAT_ACK,
+ DISCORD_GATEWAY_REQUEST_SOUNDBOARD_SOUNDS,
+};
+
+enum discord_gateway_error {
+ DISCORD_GATEWAY_UNKNOWN_ERROR = 4000,
+ DISCORD_GATEWAY_UNKNOWN_OPCODE = 4001,
+ DISCORD_GATEWAY_DECODE_ERROR = 4002,
+ DISCORD_GATEWAY_NOT_AUTHENTICATED = 4003,
+ DISCORD_GATEWAY_AUTHENTICATION_FAILED = 4004,
+ DISCORD_GATEWAY_ALREADY_AUTHENTICATED = 4005,
+ /* reserved */
+ DISCORD_GATEWAY_INVALID_SEQ = 4007,
+ DISCORD_GATEWAY_RATE_LIMITED = 4008,
+ DISCORD_GATEWAY_SESSION_TIMED_OUT = 4009,
+ DISCORD_GATEWAY_INVALID_SHARD = 4010,
+ DISCORD_GATEWAY_SHARDING_REQUIRED = 4011,
+ DISCORD_GATEWAY_INVALID_API_VERSION = 4012,
+ DISCORD_GATEWAY_INVALID_INTENTS = 4013,
+ DISCORD_GATEWAY_DISALLOWED_INTENTS = 4014,
+};
+
+struct discord_gateway_event {
+ char const *ptr;
+ int len;
+};
+
+struct discord_voice_event {
+ char const *ptr;
+ int len;
+};
+
+enum discord_event_type {
+ DISCORD_EVENT_API,
+ DISCORD_EVENT_GATEWAY,
+ DISCORD_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;
+ };
+};
+
+struct discord;
+
+size_t
+discord_sizeof(void);
+
+int
+discord_init(struct discord *ctx, void *mem, size_t len);
+
+int
+discord_connect_gateway(struct discord *ctx);
+
+int
+discord_connect_voice(struct discord *ctx);
+
+int
+discord_poll_events(struct discord *ctx,
+ struct discord_event *evs, size_t cap);
+
+void
+discord_error(struct discord *ctx);
+
+void
+discord_free(struct discord *ctx);
+
+// discord api calls
+// ========================================================================
+
+// discord gateway calls
+// ========================================================================
+
+// discord voice calls
+// ========================================================================
+
+#endif /* LIBDISCORD_H */
diff --git a/src/arena.c b/src/arena.c
@@ -0,0 +1,7 @@
+#include "arena.h"
+
+extern inline void
+arena_reset(struct arena *arena);
+
+extern inline void *
+arena_alloc(struct arena *arena, size_t size, size_t alignment);
diff --git a/src/arena.h b/src/arena.h
@@ -0,0 +1,51 @@
+#ifndef ARENA_H
+#define ARENA_H
+
+#include <assert.h>
+#include <stdalign.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define IS_POW2(v) (((v) & ((v) - 1)) == 0)
+
+#define IS_ALIGNED(v, align) (((v) & ((align) - 1)) == 0)
+#define ALIGN_PREV(v, align) ((v) & ~((align) - 1))
+#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + ((align) - 1), (align))
+
+struct arena {
+ void *ptr;
+ size_t cap, len;
+};
+
+inline void
+arena_reset(struct arena *arena)
+{
+ arena->len = 0;
+}
+
+inline void *
+arena_alloc(struct arena *arena, size_t size, size_t alignment)
+{
+ assert(size);
+ assert(alignment);
+ assert(IS_POW2(alignment));
+
+ uintptr_t base = (uintptr_t) arena->ptr;
+ uintptr_t end = base + arena->cap;
+
+ uintptr_t aligned_ptr = ALIGN_NEXT(base + arena->len, alignment);
+ if (end < aligned_ptr + size)
+ return NULL;
+
+ arena->len = (aligned_ptr + size) - base;
+
+ return (void *) aligned_ptr;
+}
+
+#define ARENA_ALLOC_ARRAY(arena, T, n) \
+ arena_alloc((arena), sizeof(T) * (n), alignof(T))
+
+#define ARENA_ALLOC_SIZED(arena, T) \
+ ARENA_ALLOC_ARRAY((arena), T, 1)
+
+#endif /* ARENA_H */
diff --git a/src/libdiscord.c b/src/libdiscord.c
@@ -0,0 +1,54 @@
+#include "libdiscord.h"
+
+#include "utils.h"
+
+struct discord {
+ struct arena arena;
+};
+
+size_t
+discord_sizeof(void)
+{
+ return sizeof(struct discord);
+}
+
+int
+discord_init(struct discord *ctx, void *mem, size_t cap)
+{
+ ctx->arena.ptr = mem;
+ ctx->arena.cap = cap;
+ arena_reset(&ctx->arena);
+
+ return 0;
+}
+
+int
+discord_connect_gateway(struct discord *ctx)
+{
+ return -1;
+}
+
+int
+discord_connect_voice(struct discord *ctx)
+{
+ return -1;
+}
+
+int
+discord_poll_events(struct discord *ctx,
+ struct discord_event *evs, size_t cap)
+{
+ return -1;
+}
+
+void
+discord_error(struct discord *ctx)
+{
+}
+
+void
+discord_free(struct discord *ctx)
+{
+}
+
+#include "utils.c"
diff --git a/src/list.c b/src/list.c
@@ -0,0 +1,23 @@
+#include "list.h"
+
+extern inline void
+list_node_link(struct list_node *node,
+ struct list_node *prev,
+ struct list_node *next);
+
+extern inline struct list_node *
+list_node_unlink(struct list_node *node);
+
+extern inline void
+list_push_head(struct list_node *restrict list,
+ struct list_node *restrict node);
+
+extern inline void
+list_push_tail(struct list_node *restrict list,
+ struct list_node *restrict node);
+
+extern inline struct list_node *
+list_pop_head(struct list_node *list);
+
+extern inline struct list_node *
+list_pop_tail(struct list_node *list);
diff --git a/src/list.h b/src/list.h
@@ -0,0 +1,99 @@
+#ifndef LIST_H
+#define LIST_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define TYPEOF(v) (__typeof__ (v))
+
+#define TO_PARENT_PTR(ptr, T, member) \
+ ((T *) (((uintptr_t) (ptr)) - offsetof(T, member)))
+
+struct list_node {
+ struct list_node *prev, *next;
+};
+
+#define LIST_INIT(list) ((struct list_node) { &(list), &(list), })
+
+#define LIST_HEAD(list) ((list)->next)
+#define LIST_TAIL(list) ((list)->prev)
+
+#define LIST_EMPTY(list) \
+ (LIST_HEAD(list) == (list) && LIST_TAIL(list) == (list))
+
+#define LIST_NODE_ITER(list, it) \
+ for ((it) = LIST_HEAD(list); (it) != (list); (it) = LIST_HEAD(it))
+
+#define LIST_NODE_RITER(list, it) \
+ for ((it) = LIST_TAIL(list); (it) != (list); (it) = LIST_TAIL(it))
+
+#define LIST_NODE_ENTRY(node, T, member) \
+ TO_PARENT_PTR((node), T, member)
+
+#define LIST_ENTRY_ITER(list, it, member) \
+ for ((it) = LIST_NODE_ENTRY(LIST_HEAD(list), \
+ __typeof__ (*(it)), \
+ member); \
+ &(it)->member != (list); \
+ (it) = LIST_NODE_ENTRY(LIST_HEAD(&(it)->member), \
+ __typeof__ (*(it)), \
+ member))
+
+#define LIST_ENTRY_RITER(list, it, member) \
+ for ((it) = LIST_NODE_ENTRY(LIST_TAIL(list), \
+ __typeof__ (*(it)), \
+ member); \
+ &(it)->member != (list); \
+ (it) = LIST_NODE_ENTRY(LIST_TAIL(&(it)->member), \
+ __typeof__ (*(it)), \
+ member))
+
+
+inline void
+list_node_link(struct list_node *node,
+ struct list_node *prev,
+ struct list_node *next)
+{
+ node->prev = prev;
+ prev->next = node;
+ node->next = next;
+ next->prev = node;
+}
+
+inline struct list_node *
+list_node_unlink(struct list_node *node)
+{
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ return node;
+}
+
+inline void
+list_push_head(struct list_node *restrict list,
+ struct list_node *restrict node)
+{
+ list_node_link(node, list, LIST_HEAD(list));
+}
+
+inline void
+list_push_tail(struct list_node *restrict list,
+ struct list_node *restrict node)
+{
+ list_node_link(node, LIST_TAIL(list), list);
+}
+
+inline struct list_node *
+list_pop_head(struct list_node *list)
+{
+ struct list_node *res = list_node_unlink(LIST_HEAD(list));
+ return res;
+}
+
+inline struct list_node *
+list_pop_tail(struct list_node *list)
+{
+ struct list_node *res = list_node_unlink(LIST_TAIL(list));
+ return res;
+}
+
+#endif /* LIST_H */
diff --git a/src/stringview.c b/src/stringview.c
@@ -0,0 +1 @@
+#include "stringview.h"
diff --git a/src/stringview.h b/src/stringview.h
@@ -0,0 +1,29 @@
+#ifndef STRINGVIEW_H
+#define STRINGVIEW_H
+
+#include <stddef.h>
+#include <string.h>
+
+struct stringview {
+ unsigned char *ptr;
+ size_t len;
+};
+
+#define FROM_CSTR(cstr) ((struct stringview) { (cstr), strlen(cstr), })
+
+inline int
+svcmp(struct stringview *a, struct stringview *b)
+{
+ if (a->len != b->len)
+ return a->len - b->len;
+
+ return strncmp((char *) a->ptr, (char *) b->ptr, a->len);
+}
+
+inline unsigned char *
+svstr(struct stringview *haystack, struct stringview *needle)
+{
+ return NULL;
+}
+
+#endif /* STRINGVIEW_H */
diff --git a/src/utils.c b/src/utils.c
@@ -0,0 +1,3 @@
+#include "arena.c"
+#include "list.c"
+#include "stringview.c"
diff --git a/src/utils.h b/src/utils.h
@@ -0,0 +1,8 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include "arena.h"
+#include "list.h"
+#include "stringview.h"
+
+#endif /* UTILS_H */