aboutsummaryrefslogtreecommitdiff
path: root/chatty.h
diff options
context:
space:
mode:
Diffstat (limited to 'chatty.h')
-rw-r--r--chatty.h239
1 files changed, 239 insertions, 0 deletions
diff --git a/chatty.h b/chatty.h
new file mode 100644
index 0000000..916ae4a
--- /dev/null
+++ b/chatty.h
@@ -0,0 +1,239 @@
+#ifndef CHATTY_IMPL
+
+#include <assert.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <wchar.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+typedef enum {
+ False = 0,
+ True = 1
+} Bool;
+
+// port for chatty
+#define PORT 9983
+
+#define Kilobytes(Value) ((Value) * 1024)
+#define Megabytes(Value) (Kilobytes(Value) * 1024)
+#define Gigabytes(Value) (Megabytes((u64)Value) * 1024)
+#define Terabytes(Value) (Gigabytes((u64)Value) * 1024)
+#define PAGESIZE 4096
+
+struct Arena {
+ void* addr;
+ u64 size;
+ u64 pos;
+} typedef Arena;
+
+#define PushArray(arena, type, count) (type*)ArenaPush((arena), sizeof(type) * (count))
+#define PushArrayZero(arena, type, count) (type*)ArenaPushZero((arena), sizeof(type) * (count))
+#define PushStruct(arena, type) PushArray((arena), (type), 1)
+#define PushStructZero(arena, type) PushArrayZero((arena), (type), 1)
+
+Arena*
+ArenaAlloc(u64 size)
+{
+ Arena* arena = (Arena*)malloc(sizeof(Arena));
+
+ arena->addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (arena->addr == MAP_FAILED)
+ return NULL;
+ arena->pos = 0;
+ arena->size = size;
+
+ return arena;
+}
+
+void
+ArenaRelease(Arena* arena)
+{
+ munmap(arena->addr, arena->size);
+ free(arena);
+}
+
+void*
+ArenaPush(Arena* arena, u64 size)
+{
+ u8* mem;
+ mem = (u8*)arena->addr + arena->pos;
+ arena->pos += size;
+ return mem;
+}
+
+/// Protocol
+// - every message has format Header + Message
+// TODO: authentication
+// TODO: encryption
+
+/// Protocol Header
+// - 2 bytes for version
+// - 1 byte for message type
+// - 16 bytes for checksum
+//
+// Text Message
+// - 12 bytes for the author
+// - 8 bytes for the timestamp
+// - 2 bytes for the text length
+// - x*4 bytes for the text
+//
+// History Message
+// This message is for requesting messages sent after a timestamp.
+// - 8 bytes for the timestamp
+
+/// Naming convention
+// Messages end with the Message suffix (eg. TextMessag, HistoryMessage)
+// A function that is coupled to a type works like
+// <noun><type> eg. (printTextMessage, formatTimestamp)
+
+#define PROTOCOL_VERSION 0
+
+typedef struct {
+ u16 version;
+ u8 type;
+} HeaderMessage;
+
+enum { HEADER_TYPE_TEXT = 0,
+ HEADER_TYPE_HISTORY,
+ HEADER_TYPE_PRESENCE };
+#define HEADER_TEXTMESSAGE {.version = PROTOCOL_VERSION, .type = HEADER_TYPE_TEXT};
+#define HEADER_HISTORYMESSAGE {.version = PROTOCOL_VERSION, .type = HEADER_TYPE_HISTORY};
+#define HEADER_PRESENCEMESSAGE {.version = PROTOCOL_VERSION, .type = HEADER_TYPE_PRESENCE};
+
+// Size of author string including null terminator
+#define AUTHOR_LEN 13
+// Size of formatted timestamp string including null terminator
+#define TIMESTAMP_LEN 9
+
+typedef struct {
+ u8 checksum[16];
+ u8 author[AUTHOR_LEN];
+ u64 timestamp;
+ u16 len; // including null terminator
+ u32* text; // placeholder for indexing
+ // TODO: 0-length field?
+} TextMessage;
+
+// Size of TextMessage without text pointer, used when receiving the message over a stream
+#define TEXTMESSAGE_TEXT_SIZE(m) (m.len * sizeof(*m.text))
+#define TEXTMESSAGE_SIZE (sizeof(TextMessage) - sizeof(u32*))
+
+typedef struct {
+ u64 timestamp;
+} HistoryMessage;
+
+typedef struct {
+ u8 author[AUTHOR_LEN];
+ u8 type;
+} PresenceMessage;
+enum { PRESENCE_TYPE_CONNECTED = 0,
+ PRESENCE_TYPE_DISCONNECTED };
+
+// Returns string for type byte in HeaderMessage
+u8*
+headerTypeString(u8 type)
+{
+ switch (type) {
+ case HEADER_TYPE_TEXT: return (u8*)"TextMessage";
+ case HEADER_TYPE_HISTORY: return (u8*)"HistoryMessage";
+ case HEADER_TYPE_PRESENCE: return (u8*)"PresenceMessage";
+ default: return (u8*)"Unknown";
+ }
+}
+
+u8*
+presenceTypeString(u8 type)
+{
+ switch (type) {
+ case PRESENCE_TYPE_CONNECTED: return (u8*)"connected";
+ case PRESENCE_TYPE_DISCONNECTED: return (u8*)"disconnected";
+ default: return (u8*)"Unknown";
+ }
+}
+
+// from Tsoding video on minicel (https://youtu.be/HCAgvKQDJng?t=4546)
+// sv(https://github.com/tsoding/sv)
+#define PH_FMT "header: v%d %s(%d)"
+#define PH_ARG(header) header.version, headerTypeString(header.type), header.type
+
+void
+formatTimestamp(u8 tmsp[TIMESTAMP_LEN], u64 t)
+{
+ struct tm* ltime;
+ ltime = localtime((time_t*)&t);
+ strftime((char*)tmsp, TIMESTAMP_LEN, "%H:%M:%S", ltime);
+}
+
+void
+printTextMessage(TextMessage* message, u8 wide)
+{
+ u8 timestamp[TIMESTAMP_LEN] = {0};
+ formatTimestamp(timestamp, message->timestamp);
+
+ assert(setlocale(LC_ALL, "") != NULL);
+
+ if (wide)
+ wprintf(L"TextMessage: %s [%s] %ls\n", timestamp, message->author, (wchar_t*)&message->text);
+ else {
+ u8 str[message->len];
+ wcstombs((char*)str, (wchar_t*)&message->text, message->len * sizeof(*message->text));
+ printf("TextMessage: %s [%s] (%d)%s\n", timestamp, message->author, message->len, str);
+ }
+}
+
+// Receive a message from fd and store it to the msgsArena,
+// if dest is not NULL point it to the new message created on msgsArena
+// Returns the number of bytes received
+u32
+recvTextMessage(Arena* msgsArena, u32 fd, TextMessage** dest)
+{
+ s32 nrecv = 0;
+
+ TextMessage* message = ArenaPush(msgsArena, TEXTMESSAGE_SIZE);
+ if (dest != NULL)
+ *dest = message;
+
+ // Receive everything but the text so we can know the text's size and act accordingly
+ nrecv = recv(fd, message, TEXTMESSAGE_SIZE, 0);
+ assert(nrecv != -1);
+ assert(nrecv == TEXTMESSAGE_SIZE);
+
+ nrecv = 0;
+
+ // Allocate memory for text and receive in that memory
+ u32 text_size = message->len * sizeof(*message->text);
+ ArenaPush(msgsArena, text_size);
+
+ nrecv = recv(fd, (u8*)&message->text, text_size, 0);
+ assert(nrecv != -1);
+ assert(nrecv == message->len * sizeof(*message->text));
+
+ return TEXTMESSAGE_SIZE + nrecv;
+}
+
+u32
+wstrlen(u32* str)
+{
+ u32 i = 0;
+ while (str[i] != 0)
+ i++;
+ return i;
+}
+
+#endif
+#define CHATTY_H