diff options
author | Raymaekers Luca <luca@spacehb.net> | 2025-04-27 12:52:06 +0200 |
---|---|---|
committer | Raymaekers Luca <luca@spacehb.net> | 2025-04-27 13:05:34 +0200 |
commit | f87f7b4f0aaccc65d03ccee5bb11915ead6fb0e1 (patch) | |
tree | d54df0bfde3dbffa02b1f138af4f12456f261e54 /protocol.h | |
parent | 0574f5a7c5159a2ae1d7d2182cec982509947db9 (diff) |
First pass at preparing for Github
Diffstat (limited to 'protocol.h')
-rw-r--r-- | protocol.h | 375 |
1 files changed, 0 insertions, 375 deletions
diff --git a/protocol.h b/protocol.h deleted file mode 100644 index 0c5b04a..0000000 --- a/protocol.h +++ /dev/null @@ -1,375 +0,0 @@ -#ifndef PROTOCOL_H -#define PROTOCOL_H - -#include "arena.h" -#include "types.h" - -/// Protocol -// - every message has format Header + Message -// TODO: security -// -/// ID -// - So clients can be identified uniquely. -// - 8 bytes -// - number that increments for each new client -// -/// Strings -// - strings are sent with their null terminator -// -/// Authentication -// Each header contains the id of the sender, because ids start at 1 -// message with id 0 is considered unauthenticated. -// -// When the server receives a header with id 0, this can happen -// Scenario 1: IDMessage, client already has an ID -// 1. client-> Send own ID -// 2. server-> knows ID? -// y. server-> Success -// n. 1. server-> Error 'notfound' -// 2. client-> exit -// Scenario 2: IntroductionMessage, client requests a new ID -// 1. client-> Introduces -// 2. server-> Sends & Saves ID -// 3. Save ID -// -// BIFD & UNIFD -// Each client has 2 connections that must be authenticated one for 2-way -// communication and one for 1-way communication. Respectively BIFD and -// UNIFD. BIFD must be authenticated first and is meant for requests such -// as getting an IntroductionMessage for a sent IDMessage. -// UNIFD is for messages that are like notifications. For example -// PresenceMessage that tells us when another user connected. -// These two connections separate these message types so we do not have to -// worry about receiving a PresenceMessage when waiting for an a response. -// -/// Naming conventions -// 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 -// Size of author string including null terminator -#define AUTHOR_LEN 13 -// Size of formatted timestamp string including null terminator -#define TIMESTAMP_LEN 9 -#define TIMESTAMP_FORMAT "%H:%M:%S" - -typedef u64 ID; - -// - 2 bytes for version -// - 1 byte for message type -// - 16 bytes for checksum -typedef struct { - u16 version; - u8 type; - ID id; -} HeaderMessage; - -typedef enum { - HEADER_TYPE_TEXT = 0, - HEADER_TYPE_HISTORY, - HEADER_TYPE_PRESENCE, - HEADER_TYPE_ID, - HEADER_TYPE_INTRODUCTION, - HEADER_TYPE_ERROR -} HeaderType; -// shorthand for creating a header with a value from the enum -#define HEADER_INIT(t) {.version = PROTOCOL_VERSION, .type = t, .id = 0} -// from Tsoding video on minicel (https://youtu.be/HCAgvKQDJng?t=4546) -// sv(https://github.com/tsoding/sv) -#define HEADER_FMT "header: v%d %s(%d) [%d]" -#define HEADER_ARG(header) header.version, headerTypeString(header.type), header.type, header.id - -// For sending texts to other clients -// - 13 bytes for the author -// - 8 bytes for the timestamp -// - 2 bytes for the text length -// - x*4 bytes for the text -typedef struct { - u64 timestamp; // timestamp of when the message was sent - u16 len; - wchar_t* text; // placeholder for indexing - // wchar_t* is used, because this renders the text in the debugger -} TextMessage; -// Size of TextMessage without text pointer -#define TEXTMESSAGE_SIZE (sizeof(TextMessage) - sizeof(u32*)) - -// Requesting messages sent after a timestamp. -// - 8 bytes for the timestamp -typedef struct { - u64 timestamp; -} HistoryMessage; - -// Introduce the client to the server by sending the client's information. -// See "First connection". -// - 13 bytes for author -typedef struct { - u8 author[AUTHOR_LEN]; -} IntroductionMessage; -#define INTRODUCTION_FMT "introduction: %s" -#define INTRODUCTION_ARG(message) message.author - -// Notifying the sender's state, such as "connected", "disconnected", "AFK", ... -// - 1 byte for type -typedef struct { - u8 type; -} PresenceMessage; -typedef enum { - PRESENCE_TYPE_CONNECTED = 0, - PRESENCE_TYPE_DISCONNECTED, - PRESENCE_TYPE_AFK -} PresenceType; - -// Send an error message -// - 1 byte for type -typedef struct { - u8 type; -} ErrorMessage; -typedef enum { - ERROR_TYPE_BADMESSAGE = 0, - ERROR_TYPE_NOTFOUND, - ERROR_TYPE_SUCCESS, - ERROR_TYPE_ALREADYCONNECTED, - ERROR_TYPE_TOOMANYCONNECTIONS -} ErrorType; -#define ERROR_INIT(t) {.type = t} - -typedef struct { - ID id; -} IDMessage; - -typedef struct { - s32 nrecv; - TextMessage* message; -} recvTextMessageResult; - -// Returns string for type byte in HeaderMessage -u8* -headerTypeString(HeaderType type) -{ - switch (type) - { - case HEADER_TYPE_TEXT: return (u8*)"TextMessage"; - case HEADER_TYPE_HISTORY: return (u8*)"HistoryMessage"; - case HEADER_TYPE_PRESENCE: return (u8*)"PresenceMessage"; - case HEADER_TYPE_ID: return (u8*)"IDMessage"; - case HEADER_TYPE_INTRODUCTION: return (u8*)"IntroductionMessage"; - case HEADER_TYPE_ERROR: return (u8*)"ErrorMessage"; - default: return (u8*)"Unknown"; - } -} - -u8* -presenceTypeString(PresenceType type) -{ - switch (type) - { - case PRESENCE_TYPE_CONNECTED: return (u8*)"connected"; - case PRESENCE_TYPE_DISCONNECTED: return (u8*)"disconnected"; - case PRESENCE_TYPE_AFK: return (u8*)"afk"; - default: return (u8*)"Unknown"; - } -} - -u8* -errorTypeString(ErrorType type) -{ - switch (type) - { - case ERROR_TYPE_BADMESSAGE: return (u8*)"bad message"; - case ERROR_TYPE_NOTFOUND: return (u8*)"not found"; - case ERROR_TYPE_SUCCESS: return (u8*)"success"; - case ERROR_TYPE_ALREADYCONNECTED: return (u8*)"already connected"; - case ERROR_TYPE_TOOMANYCONNECTIONS: return (u8*)"too many connections"; - default: return (u8*)"Unknown"; - } -} - -// Formats time t into tmsp string -void -formatTimestamp(u8 timestamp_str[TIMESTAMP_LEN], u64 timestamp) -{ - struct tm* ltime; - ltime = localtime((time_t*)×tamp); - strftime((char*)timestamp_str, TIMESTAMP_LEN, TIMESTAMP_FORMAT, ltime); -} - -// Receive a message from fd and store it in the msgsArena, -// Returns pointer to the allocated memory -TextMessage* -recvTextMessage(Arena* msgsArena, u32 fd) -{ - TextMessage* message = ArenaPush(msgsArena, TEXTMESSAGE_SIZE); - - // Receive everything but the text so we can know the text's size and act accordingly - s32 nrecv = recv(fd, message, TEXTMESSAGE_SIZE, 0); - assert(nrecv != -1); - assert(nrecv == TEXTMESSAGE_SIZE); - - // 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 == (s32)(message->len * sizeof(*message->text))); - - return message; -} - -typedef struct { - HeaderMessage* header; - void* message; -} Message; - -u32 -getMessageSize(HeaderType type) -{ - u32 size = 0; - switch (type) - { - case HEADER_TYPE_ERROR: size = sizeof(ErrorMessage); break; - case HEADER_TYPE_HISTORY: size = sizeof(HistoryMessage); break; - case HEADER_TYPE_INTRODUCTION: size = sizeof(IntroductionMessage); break; - case HEADER_TYPE_PRESENCE: size = sizeof(PresenceMessage); break; - case HEADER_TYPE_ID: size = sizeof(IDMessage); break; - default: assert(0); - } - return size; -} - -s32 -recvAnyMessageType(s32 fd, HeaderMessage* header, void *anyMessage, HeaderType type) -{ - s32 nrecv = recv(fd, header, sizeof(*header), 0); - if (nrecv == -1 || nrecv == 0) - return nrecv; - assert(nrecv == sizeof(*header)); - - s32 size = 0; - switch (type) - { - case HEADER_TYPE_ERROR: - case HEADER_TYPE_HISTORY: - case HEADER_TYPE_INTRODUCTION: - case HEADER_TYPE_PRESENCE: - case HEADER_TYPE_ID: - size = getMessageSize(header->type); - break; - case HEADER_TYPE_TEXT: - { - TextMessage* message = anyMessage; - size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); - } break; - default: assert(0); break; - } - assert(header->type == type); - - nrecv = recv(fd, anyMessage, size, 0); - assert(nrecv != -1); - assert(nrecv == size); - - return size; -} - -// Get any message into arena -Message -recvAnyMessage(Arena* arena, s32 fd) -{ - HeaderMessage* header = ArenaPush(arena, sizeof(*header)); - s32 nrecv = recv(fd, header, sizeof(*header), 0); - assert(nrecv != -1); - assert(nrecv == sizeof(*header)); - - s32 size = 0; - switch (header->type) - { - case HEADER_TYPE_TEXT: - { - Message result; - result.header = header; - result.message = recvTextMessage(arena, fd); - return result; - } break; - default: - { - size = getMessageSize(header->type); - } break; - } - - void* message = ArenaPush(arena, size); - nrecv = recv(fd, message, size, 0); - assert(nrecv != -1); - assert(nrecv == size); - - Message result; - result.header = header; - result.message = message; - - return result; -} - -Message -waitForMessageType(Arena* arena, Arena* queueArena, u32 fd, HeaderType type) -{ - Message message; - while (1) - { - message = recvAnyMessage(arena, fd); - if (message.header->type == type) - break; - ArenaPush(queueArena, getMessageSize(message.header->type)); - } - return message; -} - -// Generic sending function for sending any type of message to fd -// Returns number of bytes sent in message or -1 if there was an error. -s32 -sendAnyMessage(u32 fd, HeaderMessage header, void* anyMessage) -{ - s32 nsend_total; - s32 nsend = send(fd, &header, sizeof(header), 0); - if (nsend == -1) return nsend; - LoggingF("sendAnyMessage (%d)|sending "HEADER_FMT"\n", fd, HEADER_ARG(header)); - assert(nsend == sizeof(header)); - nsend_total = nsend; - - s32 size = 0; - switch (header.type) - { - case HEADER_TYPE_ERROR: - case HEADER_TYPE_HISTORY: - case HEADER_TYPE_INTRODUCTION: - case HEADER_TYPE_PRESENCE: - case HEADER_TYPE_ID: - size = getMessageSize(header.type); - break; - case HEADER_TYPE_TEXT: - { - nsend = send(fd, anyMessage, TEXTMESSAGE_SIZE, 0); - assert(nsend != -1); - assert(nsend == TEXTMESSAGE_SIZE); - nsend_total += nsend; - // set size to remaning text size that should be sent - TextMessage* message = (TextMessage*)anyMessage; - size = message->len * sizeof(*message->text); - nsend = 0; - - anyMessage = &message->text; - } break; - default: - LoggingF("sendAnyMessage (%d)|Cannot send %s\n", fd, headerTypeString(header.type)); - return -1; - } - - nsend = send(fd, anyMessage, size, 0); - if (nsend == -1) return nsend; - assert(nsend == size); - nsend_total += nsend; - - return nsend_total; -} - -#endif |