aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Makefile29
-rw-r--r--README.org7
-rw-r--r--flake.lock26
-rw-r--r--flake.nix42
-rw-r--r--include/array.h18
-rw-r--r--include/better_string.h22
-rw-r--r--include/hash_table.h14
-rw-r--r--include/helpers.h12
-rw-r--r--include/ht.h3
-rw-r--r--include/opcodes.h49
-rw-r--r--include/protocol.h14
-rw-r--r--src/array.c37
-rw-r--r--src/better_string.c51
-rw-r--r--src/helpers.c28
-rw-r--r--src/main.c211
-rw-r--r--src/opcodes.c26
17 files changed, 594 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..720c57f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+obj/**
+bin/**
+.cache/**
+result/**
+compile_commands.json
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cafa911
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
+SRC_DIR := src
+OBJ_DIR := obj
+BIN_DIR := bin
+
+EXE := $(BIN_DIR)/umami
+SRC := $(wildcard $(SRC_DIR)/*.c)
+OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
+
+CFLAGS := -Wall -Iinclude
+LDFLAGS := -Llib
+LDLIBS := -lm
+
+.PHONY: all clean
+
+all: $(EXE)
+
+$(EXE): $(OBJ) | $(BIN_DIR)
+ $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
+
+$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(BIN_DIR) $(OBJ_DIR):
+ mkdir -p $@
+
+clean:
+ @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) result
+
+-include $(OBJ:.o=.d)
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..b81e4f0
--- /dev/null
+++ b/README.org
@@ -0,0 +1,7 @@
+* Introduction
+Umami is a simple messaging protocol built for simplicity, which integrates user permissions
+(such as mod, admin, etc...), log persistence, and account management in layer one. In other
+words, it is IRC, but modern. It will also support simple encryption as a layer one protocol.
+
+This first server will implement the minimal functionality that satisfies the following properties:
+1. It should store logs for all public channels, and should
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..bce6f7b
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,26 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1734424634,
+ "narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "ref": "nixos-unstable",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..7d8c456
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,42 @@
+{
+ description = "A Nix flake for creating a devshell and build environment for umami";
+
+ inputs = {
+ nixpkgs.url = "nixpkgs/nixos-unstable";
+ };
+
+ outputs = { self, nixpkgs }: {
+ packages.x86_64-linux = let
+ pkgs = import nixpkgs {
+ system = "x86_64-linux"; # Adjust for your system, e.g., aarch64-darwin
+ };
+ in
+ {
+ default = pkgs.stdenv.mkDerivation {
+ pname = "umami";
+ version = "1.0.0";
+
+ src = ./.;
+
+ buildInputs = with pkgs; [ clang gnumake valgrind bear ];
+
+ buildPhase = "make";
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp ./bin/umami $out/bin/
+ '';
+ };
+ };
+
+ devShell.x86_64-linux = let
+ pkgs = import nixpkgs {
+ system = "x86_64-linux";
+ };
+ in
+ pkgs.mkShell {
+ buildInputs = with pkgs; [ clang gnumake valgrind bear ];
+ };
+ };
+}
+
diff --git a/include/array.h b/include/array.h
new file mode 100644
index 0000000..48d82db
--- /dev/null
+++ b/include/array.h
@@ -0,0 +1,18 @@
+#ifndef ARRAY_H
+#define ARRAY_H
+#include <stdlib.h>
+#define DEFAULT_ARR_LEN 10
+typedef struct {
+ void **items;
+ size_t size;
+ size_t capacity;
+} array_t;
+
+array_t *init_array();
+
+void array_push(array_t *a, void *item);
+
+void *array_pop(array_t *a);
+
+void array_free(void *a, void (*freefunc)(void *));
+#endif
diff --git a/include/better_string.h b/include/better_string.h
new file mode 100644
index 0000000..b23010e
--- /dev/null
+++ b/include/better_string.h
@@ -0,0 +1,22 @@
+#ifndef STRING_H
+#define STRING_H
+#include <stdlib.h>
+#define DEFAULT_STR_SIZE 10
+typedef struct {
+ char *buf;
+ size_t len;
+ size_t size;
+} string_t;
+
+string_t *init_string(const char *source);
+
+void string_append(string_t *s, char c);
+
+void string_concat(string_t *s1, string_t *s2);
+
+void string_concat_const(string_t *s, const char *src);
+
+char string_pop(string_t *s);
+
+void string_free(void *s);
+#endif
diff --git a/include/hash_table.h b/include/hash_table.h
new file mode 100644
index 0000000..16d8fe9
--- /dev/null
+++ b/include/hash_table.h
@@ -0,0 +1,14 @@
+#ifndef HASH_TABLE_H
+#define HASH_TABLE_H
+typedef struct NODE_STRUCT {
+
+} node_t;
+
+typedef struct {
+
+} sll_t;
+
+typedef struct {
+
+} ht_t;
+#endif
diff --git a/include/helpers.h b/include/helpers.h
new file mode 100644
index 0000000..c685e09
--- /dev/null
+++ b/include/helpers.h
@@ -0,0 +1,12 @@
+#ifndef HELPERS_H
+#define HELPERS_H
+#include <stdlib.h>
+
+void die(const char *msg);
+
+void *safe_calloc(unsigned int i, size_t size);
+
+void *safe_realloc(void *x, size_t size);
+
+void alloc_zero(void *ptr, size_t size);
+#endif
diff --git a/include/ht.h b/include/ht.h
new file mode 100644
index 0000000..9172961
--- /dev/null
+++ b/include/ht.h
@@ -0,0 +1,3 @@
+#ifndef HT_H
+#define HT_H
+#endif
diff --git a/include/opcodes.h b/include/opcodes.h
new file mode 100644
index 0000000..3d5baf8
--- /dev/null
+++ b/include/opcodes.h
@@ -0,0 +1,49 @@
+#ifndef OPCODES_H
+#define OPCODES_H
+
+typedef enum {
+ CO_NOP,
+ CO_JN, /* join */
+ CO_DM,
+ CO_PST, /* post */
+ CO_MSG,
+ CO_REG,
+ CO_NCK, /* nick */
+ CO_PNG, /* pong */
+ CO_LVE, /* leave */
+ CO_QT, /* quit*/
+
+ CO_CLM, /* claim channel */
+ CO_DESC, /* change description */
+ CO_TRSFR, /* ownership transfer of admin */
+ CO_KCK, /* kick */
+ CO_BAN,
+ CO_KNGT, /* knight */
+ CO_DMT, /* demote */
+
+ CO_LOGS, /* allows users to download log file from date specified in unix time */
+
+ CO_UNRECOGNIZED
+} copcode_t;
+
+typedef enum {
+ SO_SUCCESS,
+ SO_FAIL_PARSE,
+ SO_FAIL_NOPERM,
+ SO_FAIL_USER_TAKEN,
+ SO_FAIL_RATE, /* rate limit */
+ SO_PNG, /* ping */
+ SO_BYE,
+} sopcode_t;
+
+void die_lz(int code, const char *msg);
+
+char *decode_client_opcode(int op);
+
+char *decode_server_opcode(int op);
+
+int encode_client_opcode(char *c);
+
+int encode_server_opcode(char *c);
+
+#endif
diff --git a/include/protocol.h b/include/protocol.h
new file mode 100644
index 0000000..7b5eb07
--- /dev/null
+++ b/include/protocol.h
@@ -0,0 +1,14 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+#define MAX_OP_LEN 10
+#define MAX_ARG_LEN 50
+#define MAX_ARGS 5
+#define MAX_BUFSIZE 4096
+#define DEFAULT_PORT 11111
+#define MAX_CONNECTIONS 32768
+#define DEFAULT_TIMEOUT 6000
+#define USERNAME_SIZE 30
+#define KEYLEN 512
+
+#endif
diff --git a/src/array.c b/src/array.c
new file mode 100644
index 0000000..84ed938
--- /dev/null
+++ b/src/array.c
@@ -0,0 +1,37 @@
+#include <array.h>
+#include <helpers.h>
+#include <stdlib.h>
+
+array_t *init_array() {
+ array_t *a = safe_calloc(1, sizeof(array_t));
+ a->capacity = DEFAULT_ARR_LEN;
+ a->size = 0;
+ a->items = safe_calloc(a->capacity, sizeof(void *));
+ return a;
+}
+
+void array_push(array_t *a, void *item) {
+ if (a->size >= a->capacity - 2) {
+ a->capacity *= 2;
+ a->items = realloc(a->items, a->capacity);
+ }
+ a->items[a->size] = item;
+ a->size++;
+}
+
+void *array_pop(array_t *a) {
+ if (a->size <= 0)
+ return NULL;
+ void *retval = a->items[a->size];
+ a->size--;
+ return retval;
+}
+
+void array_free(void *x, void (*freefunc)(void *)) {
+ array_t *a = (array_t *)x;
+ for (int i = 0; i < a->size; i++) {
+ freefunc(a->items[i]);
+ }
+ free(a->items);
+ free(a);
+}
diff --git a/src/better_string.c b/src/better_string.c
new file mode 100644
index 0000000..4413f77
--- /dev/null
+++ b/src/better_string.c
@@ -0,0 +1,51 @@
+#include <better_string.h>
+#include <helpers.h>
+#include <stdlib.h>
+#include <string.h>
+
+string_t *init_string(const char *src) {
+ string_t *s = safe_calloc(1, sizeof(string_t));
+ size_t len = src ? strlen(src) : DEFAULT_STR_SIZE;
+ size_t size = len * 2;
+ s->buf = safe_calloc(size, sizeof(char));
+ s->buf[0] = '\0';
+
+ if (src)
+ strcpy(s->buf, src);
+ s->len = len;
+ s->size = size;
+ return s;
+}
+
+void string_push(string_t *s, char c) {
+ if (s->len >= s->size - 2) {
+ s->size *= 2;
+ s->buf = safe_realloc(s->buf, s->size);
+ }
+ s->buf[s->len] = c;
+ s->len++;
+}
+
+char string_pop(string_t *s) {
+ char c = s->buf[s->len];
+ s->len--;
+ return c;
+}
+
+void string_concat_const(string_t *s1, const char *s2) {
+ for (int i = 0; i < strlen(s2); i++) {
+ string_push(s1, s2[i]);
+ }
+}
+
+void string_concat(string_t *s1, string_t *s2) {
+ for (int i = 0; i < s2->len; i++) {
+ string_push(s1, s2->buf[i]);
+ }
+}
+
+void string_free(void *x) {
+ string_t *s = x;
+ free(s->buf);
+ free(s);
+}
diff --git a/src/helpers.c b/src/helpers.c
new file mode 100644
index 0000000..ad7228e
--- /dev/null
+++ b/src/helpers.c
@@ -0,0 +1,28 @@
+#include <helpers.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void die(const char *msg) {
+ fprintf(stderr, "panic: ");
+ perror(msg);
+ exit(EXIT_FAILURE);
+}
+
+void *safe_calloc(unsigned int i, size_t size) {
+ void *x = calloc(i, size);
+ if (x == NULL) {
+ die("abort: calloc()");
+ }
+ return x;
+}
+
+void *safe_realloc(void *x, size_t size) {
+ void *p = realloc(x, size);
+ if (x == NULL) {
+ die("abort: realloc()");
+ }
+ return p;
+}
+
+void alloc_zero(void *ptr, size_t size) { memset(ptr, 0, size); }
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..8555da1
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,211 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <array.h>
+#include <better_string.h>
+#include <helpers.h>
+#include <opcodes.h>
+#include <protocol.h>
+
+int PORT = DEFAULT_PORT;
+int nfds = 1;
+struct pollfd fds[MAX_CONNECTIONS * 2];
+
+void handle_sigint(int sig) {
+ for (int i = 0; i < nfds; i++) {
+ if (fds[i].fd >= 0) {
+ close(fds[i].fd);
+ }
+ }
+
+ exit(0);
+}
+
+/* modifying function: modifies `in` to point to new location after opcode */
+int parse_op(char *in) {
+ char buf[MAX_OP_LEN];
+ buf[0] = '\0';
+
+ char cur;
+ cur = in[0];
+
+ bool found = false;
+ for (int i = 0; i < 11; i++) {
+ if (cur == '\t') {
+ found = true;
+ in++;
+ break;
+ } else if (cur == '\0') {
+ break;
+ }
+
+ buf[i] = in[0];
+ in++;
+ cur = in[0];
+ }
+
+ return found ? encode_client_opcode(buf) : CO_UNRECOGNIZED;
+}
+
+array_t *parse_args(char *buf) { return NULL; }
+
+void set_non_blocking(int sock) {
+ int flags = fcntl(sock, F_GETFL, 0);
+ int code = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+ die_lz(code, "fcntl()");
+}
+
+int main(int argc, char **argv) {
+ char buffer[MAX_BUFSIZE];
+ char res_buffer[MAX_BUFSIZE];
+ struct pollfd *local_fds1 = fds;
+ struct pollfd *local_fds2 = fds + MAX_CONNECTIONS;
+ struct pollfd *local_fds = local_fds1;
+
+ /* We start by initializing our net-related structs */
+ signal(SIGINT, handle_sigint);
+
+ int listen_sd = socket(AF_INET6, SOCK_STREAM, 0);
+ int optval = 1;
+ die_lz(listen_sd, "socket()");
+
+ int reuse = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval,
+ sizeof(optval));
+ die_lz(reuse, "setsockopt()");
+
+ int nonblock = ioctl(listen_sd, FIONBIO, (char *)&optval);
+ die_lz(nonblock, "ioctl()");
+
+ set_non_blocking(listen_sd);
+
+ /* Make new address */
+ struct sockaddr_in6 addr;
+ alloc_zero(&addr, sizeof(addr));
+
+ addr.sin6_family = AF_INET6;
+ memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));
+ addr.sin6_port = htons(DEFAULT_PORT);
+
+ int sock_bind = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr));
+ die_lz(sock_bind, "bind()");
+
+ int sock_listen = listen(listen_sd, 32);
+ die_lz(sock_listen, "listen()");
+
+ /*
+ The first fd entry is the listening socket for new connections;
+ all the rest are sockets we read from and write to. Whenever a client
+ connects, we know from the listening socket.
+ */
+
+ alloc_zero(fds, sizeof(fds));
+ fds[0].fd = listen_sd;
+ fds[0].events = POLLIN;
+
+ int timeout = 6000;
+ int sock_poll;
+ bool end_server = false;
+
+ /* mainloop */
+ do {
+ bool compress_array = false;
+ /*
+ if an fd loses connection, we want to remove all -1's from the fds array
+ */
+
+ sock_poll = poll(local_fds, nfds, timeout);
+ die_lz(sock_poll, "poll()");
+
+ for (int i = 0; i < nfds; i++) {
+ if (local_fds[i].revents == 0)
+ continue;
+
+ if (local_fds[i].revents != POLLIN) {
+ end_server = true;
+ break;
+ }
+
+ if (local_fds[i].fd == listen_sd) {
+ printf("socket is readable\n");
+ int new_sd;
+
+ do {
+ new_sd = accept(listen_sd, NULL, NULL);
+
+ if (new_sd < 0 && errno != EWOULDBLOCK) {
+ perror("accept() failed");
+ end_server = true;
+ break;
+ }
+
+ local_fds[nfds].fd = new_sd;
+ local_fds[nfds].events = POLLIN;
+ nfds++;
+ } while (new_sd >= 0);
+ } else {
+ bool close_conn = false;
+ int fd_recv;
+ int fd_send;
+ fd_recv = recv(local_fds[i].fd, buffer, sizeof(buffer), 0);
+
+ if (fd_recv < 0 && errno != EWOULDBLOCK) {
+ perror("recv() failed");
+ close_conn = true;
+ } else if (fd_recv == 0) {
+ printf("Connection closed\n");
+ close_conn = true;
+ } else {
+ /* echo server -- replace this with buffer parsing */
+ /* TODO: reply to client based on user input */
+ fd_send = send(local_fds[i].fd, buffer, fd_recv, 0);
+ if (fd_send < 0) {
+ perror("send()");
+ close_conn = true;
+ }
+ }
+
+ if (close_conn) {
+ close(local_fds[i].fd);
+ local_fds[i].fd = 0;
+ compress_array = true;
+ }
+ }
+ }
+
+ if (compress_array) {
+ printf("switching...\n");
+ int cur_nfds = nfds;
+ nfds = 0;
+ for (int i = 0; i < cur_nfds; i++) {
+ if (local_fds[i].fd != 0) {
+ local_fds2[nfds] = local_fds[i];
+ nfds ++;
+ }
+ }
+
+ local_fds1 = local_fds2;
+ local_fds2 = local_fds;
+ local_fds = local_fds1;
+ alloc_zero(local_fds2, MAX_CONNECTIONS);
+ }
+ } while (!end_server);
+
+ for (int i = 0; i < nfds; i++) {
+ if (fds[i].fd >= 0) {
+ close(fds[i].fd);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/opcodes.c b/src/opcodes.c
new file mode 100644
index 0000000..66742db
--- /dev/null
+++ b/src/opcodes.c
@@ -0,0 +1,26 @@
+#include <stdlib.h>
+#include <opcodes.h>
+#include <helpers.h>
+
+
+void die_lz(int code, const char *msg) {
+ if (code < 0) {
+ die(msg);
+ }
+}
+
+char *decode_client_opcode(int op) {
+ return "implement me";
+}
+
+char *decode_server_opcode(int op) {
+ return "implement me";
+}
+
+int encode_client_opcode(char *c) {
+ return -1;
+}
+
+int encode_server_opcode(char *c) {
+ return -1;
+}