diff options
| author | fschildt <florian.schildt@protonmail.com> | 2025-08-22 15:23:11 +0200 | 
|---|---|---|
| committer | fschildt <florian.schildt@protonmail.com> | 2025-10-15 11:33:23 +0200 | 
| commit | 04e4627e6c11254ee6f49edf5feb1b8d711da41a (patch) | |
| tree | e28f2e62d3e1b83f9686cdeb102e3f47379e6793 /src/server | |
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/c2s_handler.c | 159 | ||||
| -rw-r--r-- | src/server/c2s_handler.h | 10 | ||||
| -rw-r--r-- | src/server/client_connections.c | 249 | ||||
| -rw-r--r-- | src/server/client_connections.h | 44 | ||||
| -rw-r--r-- | src/server/main.c | 78 | ||||
| -rw-r--r-- | src/server/s2c_sender.c | 77 | ||||
| -rw-r--r-- | src/server/s2c_sender.h | 14 | 
7 files changed, 631 insertions, 0 deletions
diff --git a/src/server/c2s_handler.c b/src/server/c2s_handler.c new file mode 100644 index 0000000..4406aa7 --- /dev/null +++ b/src/server/c2s_handler.c @@ -0,0 +1,159 @@ +#include <basic/basic.h> +#include <messages/messages.h> +#include <server/client_connections.h> +#include <server/c2s_handler.h> +#include <server/s2c_sender.h> + + +internal_fn void +handle_c2s_chat_message(ClientConnections *conns, ClientConnection *conn) +{ +    // Todo: verify package size +    C2S_ChatMessage *chat_message = (C2S_ChatMessage*)conn->recv_buff; +    chat_message->content = (void*)chat_message + (size_t)chat_message->content; + +    Time now = os_time_get_now(); + +    // Todo: make proper groups +    for (size_t i = 0; i < conns->max_connection_count; i++) { +        ClientConnection *connection = conns->connections + i; +        if (connection->username->len > 0) { +            send_s2c_chat_message(connection, conn->username, chat_message->content, now); +        } +    } +} + + +internal_fn void +handle_c2s_login(ClientConnections *conns, ClientConnection *conn) +{ +    // init package +    C2S_Login *login = (C2S_Login*)conn->recv_buff; +    login->username = (void*)login + (size_t)login->username; +    login->password = (void*)login + (size_t)login->password; + + +    // verify package +    if (login->username->len <= 0) { +        printf("handle_c2s_login error: username len %d is invalid\n", login->username->len); +    } +    if (login->username->len > MESSAGES_MAX_USERNAME_LEN) { +        printf("handle_c2s_login error: username len %d/%d\n", login->username->len, MESSAGES_MAX_USERNAME_LEN); +        return; // Todo: rm connection +    } +    if (login->username->len > MESSAGES_MAX_PASSWORD_LEN) { +        printf("handle_c2s_login error: password len %d/%d\n", login->password->len, MESSAGES_MAX_PASSWORD_LEN); +        return; // Todo: rm connection +    } +    size_t message_size = sizeof(C2S_Login) + 2*sizeof(String32) + sizeof(u32) * (login->username->len + login->password->len); +    if (message_size != conn->recv_buff_size_used) { +        printf("handle_c2s_login error: message size is %zu/%d\n", message_size, conn->recv_buff_size_used); +        return; // Todo: rm connection +    } + + +    // temporary check if username already connected (use a hashmap for this) +    b32 login_success = true; +    for (size_t i = 0; i < conns->max_connection_count; i++) { +        ClientConnection *connection = conns->connections + i; +        if (string32_equal(connection->username, login->username)) { +            login_success = false; +            break; +        } +    } + + +    // login +    for (size_t i = 0; i < login->username->len; i++) { +        conn->username->codepoints[i] = login->username->codepoints[i]; +    } +    conn->username->len = login->username->len; + + +    if (!login_success) { +        send_s2c_login(conn, S2C_LOGIN_ERROR); +    } + + +    send_s2c_login(conn, S2C_LOGIN_SUCCESS); + +    // send everyone else's user update to conn +    // Todo: make proper groups +    for (size_t i = 0; i < conns->max_connection_count; i++) { +        ClientConnection *connection = conns->connections + i; +        if (string32_equal(connection->username, conn->username)) { +            continue; +        } +        if (connection->username->len > 0) { +            send_s2c_user_update(conn, connection->username, S2C_USER_UPDATE_ONLINE); +        } +    } + +    // send conn's user update to everyone else +    // Todo: make proper groups +    for (size_t i = 0; i < conns->max_connection_count; i++) { +        ClientConnection *connection = conns->connections + i; +        if (connection->username->len > 0) { +            send_s2c_user_update(connection, conn->username, S2C_USER_UPDATE_ONLINE); +        } +    } + +    // Todo: make function string32_printf +    printf("<"); +    string32_print(conn->username);; +    printf("> connected to the server\n"); +} + + +b32 +handle_c2s(ClientConnections *conns, ClientConnection *conn) +{ +    // recv header +    if (conn->recv_buff_size_used < sizeof(MessageHeader)) { +        size_t size_to_recv = sizeof(MessageHeader) - conn->recv_buff_size_used; +        i64 size_recvd = os_net_secure_stream_recv(conn->secure_stream_id, conn->recv_buff + conn->recv_buff_size_used, size_to_recv); +        if (size_recvd < 0) { +            return false; +        } +        else if (size_recvd == 0) { +            return false; +        } + +        conn->recv_buff_size_used += size_recvd; +        if (conn->recv_buff_size_used < sizeof(MessageHeader)) { +            return true; +        } +    } + + +    // recv body +    MessageHeader *header = (MessageHeader*)conn->recv_buff; +    if (conn->recv_buff_size_used < header->size) { +        size_t size_to_recv = header->size - conn->recv_buff_size_used; +        i64 size_recvd = os_net_secure_stream_recv(conn->secure_stream_id, conn->recv_buff + conn->recv_buff_size_used, size_to_recv); +        if (size_recvd < 0) { +            return false; +        } +        else if (size_recvd == 0) { +            return false; +        } + +        conn->recv_buff_size_used += size_recvd; +        if (conn->recv_buff_size_used < header->size) { +            return true; +        } +    } + + +    // dispatch +    switch (header->type) { +        case C2S_LOGIN:        handle_c2s_login(conns, conn);        break; +        case C2S_CHAT_MESSAGE: handle_c2s_chat_message(conns, conn); break; +    } + + +    // cleanup +    conn->recv_buff_size_used = 0; +    return true; +} + diff --git a/src/server/c2s_handler.h b/src/server/c2s_handler.h new file mode 100644 index 0000000..0ae32c3 --- /dev/null +++ b/src/server/c2s_handler.h @@ -0,0 +1,10 @@ +#ifndef C2S_HANDLER_H +#define C2S_HANDLER_H + +#include <server/client_connections.h> + + +b32 handle_c2s(ClientConnections *conns, ClientConnection *conn); + + +#endif // C2S_HANDLER_H diff --git a/src/server/client_connections.c b/src/server/client_connections.c new file mode 100644 index 0000000..262d1d9 --- /dev/null +++ b/src/server/client_connections.c @@ -0,0 +1,249 @@ +#include <basic/string32.h> +#include <basic/basic.h> +#include <messages/messages.h> +#include <crypto/rsa.h> +#include <os/os.h> +#include <server/client_connections.h> +#include <server/c2s_handler.h> +#include <server/s2c_sender.h> + +#include <pthread.h> +#include <sys/socket.h> +#include <unistd.h> +#include <sys/epoll.h> + + + +ClientConnection * +client_connection_id_to_ptr(ClientConnections *conns, u32 id) +{ +    ClientConnection *conn = &conns->connections[id]; +    return conn; +} + + +void +client_connection_rm(ClientConnections *conns, u32 connection_id) +{ +    ClientConnection *conn = client_connection_id_to_ptr(conns, connection_id); + +    int fd = os_net_secure_stream_get_fd(conn->secure_stream_id); +    int deleted = epoll_ctl(conns->epoll_fd, EPOLL_CTL_DEL, fd, 0);  +    if (deleted == -1) { +        perror("epoll_ctl<del>:"); +    } + +    os_net_secure_stream_close(conn->secure_stream_id); + + +    // Todo: use string32_create with a (temporary) arena +    String32 username; +    u32 username_codepoints[MESSAGES_MAX_MESSAGE_LEN]; +    for (size_t i = 0; i < conn->username->len; i++) { +        username_codepoints[i] = conn->username->codepoints[i]; +    } +    username.len = conn->username->len; + + +    conn->username->len = 0; +    conns->free_ids[conns->free_id_count++] = connection_id; + + +    if (username.len) { +        // send user updates +        // Todo: make proper groups +        for (size_t i = 0; i < conns->max_connection_count; i++) { +            ClientConnection *connection = conns->connections + i; +            if (connection->username->len > 0) { +                send_s2c_user_update(connection, &username, S2C_USER_UPDATE_OFFLINE); +            } +        } + + +        // Todo: string32_printf(...) +        printf("<"); +        string32_print(&username); +        printf("> disconnected\n"); +    } +} + + +u32 +client_connection_add(ClientConnections *conns, u32 secure_stream_id) +{ +    if (conns->free_id_count == 0) { +        return CLIENT_CONNECTION_INVALID_ID; +    } +    u32 conn_id = conns->free_ids[--conns->free_id_count]; +    ClientConnection *conn = client_connection_id_to_ptr(conns, conn_id); +    conn->id = conn_id; + + +    struct epoll_event event; +    event.events = EPOLLIN; +    event.data.u32 = conn_id; + +    int fd = os_net_secure_stream_get_fd(secure_stream_id); + +    if (epoll_ctl(conns->epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) { +        conns->free_ids[conns->free_id_count++] = conn_id; +        perror("epoll_ctl<add>:"); +        return false; +    } + + +    conn->secure_stream_id = secure_stream_id; +    conn->recv_buff_size_used = 0; + +    return conn_id; +} + + +internal_fn void +handle_client_event(ClientConnections *conns, struct epoll_event event) +{ +    u32 conn_id = event.data.u32; +    ClientConnection *conn = client_connection_id_to_ptr(conns, conn_id); + + +    u32 secure_stream_id = conn->secure_stream_id; +    OSNetSecureStreamStatus status = os_net_secure_stream_get_status(conn_id); +    if (status == OS_NET_SECURE_STREAM_ERROR || +        status == OS_NET_SECURE_STREAM_DISCONNECTED) { +        client_connection_rm(conns, conn_id); +        return; +    } + + +    int fd = os_net_secure_stream_get_fd(conn->secure_stream_id); + +    if (event.events & EPOLLERR) { +        printf("EPOLLERR occured for client_id = %d, fd = %d\n", conn_id, fd); +        client_connection_rm(conns, conn_id); +    } +    else if (event.events & EPOLLHUP) { +        printf("EPOLLHUP occured for client_id = %d, fd = %d\n", conn_id, fd); +        client_connection_rm(conns, conn_id); +    } +    else if (event.events & EPOLLIN) { +        if (!handle_c2s(conns, conn)) { +            return client_connection_rm(conns, conn_id); +        } +    } +    else { +        printf("EPOLL??? (%d) occured for client_id = %d, fd = %d\n", event.events, conn_id, fd); +        client_connection_rm(conns, conn_id); +    } +} + + +internal_fn void +handle_listener_event(ClientConnections *conns, struct epoll_event event) +{ +    if (event.events & (EPOLLHUP | EPOLLERR)) { +        printf("listener failed\n"); +        exit(0); +    } +    if (event.events & EPOLLIN) { +        u32 secure_stream_id = os_net_secure_stream_accept(conns->listener_id); +        if (secure_stream_id == OS_NET_SECURE_STREAM_ID_INVALID) { +            return; +        } + +        client_connection_add(conns, secure_stream_id); +    } +    else { +        printf("unhandled listener event %d\n", event.events); +    } +} + + +void +client_connections_manage(ClientConnections *conns) +{ +    struct epoll_event listener_events; +    listener_events.data.u32 = conns->listener_id; +    listener_events.events = EPOLLIN; +    int listener_fd = os_net_secure_stream_get_fd(conns->listener_id); +    if (epoll_ctl(conns->epoll_fd, EPOLL_CTL_ADD, listener_fd, &listener_events) < 0) { +        perror("epoll_ctl add for listener fd:"); +        return; +    } + +    for (;;) { +        struct epoll_event events[conns->max_connection_count]; +        i32 event_count = epoll_wait(conns->epoll_fd, events, ARRAY_COUNT(events), -1); +        if (event_count < -1) { +            perror("epoll_wait:"); +            continue; +        } + +        if (event_count > 0) { +            printf("event.events = %d\n", events[0].events); +        } + +        for (size_t i = 0; i < event_count; i++) { +            if (events[i].data.u32 == conns->listener_id) { +                handle_listener_event(conns, events[i]); +            } else { +                handle_client_event(conns, events[i]); +            } +        } +    } +} + + +ClientConnections * +client_connections_create(Arena *arena, u16 port) +{ +    u32 max_user_count = MESSAGES_MAX_USER_COUNT; +    size_t max_username_len = MESSAGES_MAX_USERNAME_LEN; + + +    os_net_secure_streams_init(arena, max_user_count + 1); + + +    ClientConnections *conns = arena_push(arena, sizeof(ClientConnections)); + +    conns->server_rsa_pri = rsa_create_via_file(arena, "./server_rsa_pri.pem", false); + +    conns->listener_id = os_net_secure_stream_listen(port, conns->server_rsa_pri); +    if (conns->listener_id == OS_NET_SECURE_STREAM_ID_INVALID) { +        return 0; +    } + + +    size_t push_size = sizeof(ClientConnection) * max_user_count; +    conns->max_connection_count = max_user_count; +    conns->connections = arena_push(arena, push_size); + +    ClientConnection *conn = conns->connections; +    for (size_t i = 0; i < conns->max_connection_count; i++) { +        conn->secure_stream_id = CLIENT_CONNECTION_INVALID_ID; +        conn->recv_buff_size_used = 0; + +        conn->username = (String32*)conn->username_buff; +        conn->username->len = 0; + +        conn++; +    } + + +    conns->free_id_count = max_user_count; +    conns->free_ids = arena_push(arena, max_user_count * sizeof(*conns->free_ids)); +    for (size_t i = 0; i < max_user_count; i++) { +        conns->free_ids[i] = i; +    } + + +    conns->epoll_fd = epoll_create(max_user_count); +    if (conns->epoll_fd < 0) { +        perror("epoll_create:"); +        return 0; +    } + +    s2c_sender_init(arena); + +    return conns; +} + diff --git a/src/server/client_connections.h b/src/server/client_connections.h new file mode 100644 index 0000000..0c0a275 --- /dev/null +++ b/src/server/client_connections.h @@ -0,0 +1,44 @@ +#ifndef CLIENT_CONNECTIONS_H +#define CLIENT_CONNECTIONS_H + +#include <basic/basic.h> +#include <basic/string32.h> +#include <os/os.h> +#include <messages/messages.h> + + +#define CLIENT_CONNECTION_INVALID_ID U32_MAX + + +typedef struct { +    u32 id; +    u32 secure_stream_id; +    u32 recv_buff_size_used; +    u8 recv_buff[1408]; + +    String32 *username; +    u8 username_buff[sizeof(String32) + MESSAGES_MAX_USERNAME_LEN * sizeof(u32)]; +} ClientConnection; + + +typedef struct { +    EVP_PKEY *server_rsa_pri; +    u32 listener_id; +    int epoll_fd; + +    size_t max_connection_count; +    ClientConnection *connections; + +    u32 free_id_count; +    u32 *free_ids; +} ClientConnections; + + + +ClientConnections *client_connections_create(Arena *arena, u16 port); +void client_connections_manage(ClientConnections *connections); + +ClientConnection* client_connection_id_to_ptr(ClientConnections *connections, u32 id); + + +#endif // CLIENT_CONNECTIONS_H diff --git a/src/server/main.c b/src/server/main.c new file mode 100644 index 0000000..c92eb75 --- /dev/null +++ b/src/server/main.c @@ -0,0 +1,78 @@ +#ifndef _POXIS_C_SOURCE +#define _POSIX_C_SOURCE 200809L // enable POSIX.1-2017 +#endif + +#include <os/os.h> +#include <basic/basic.h> +#include <basic/arena.h> +#include <basic/string32.h> +#include <server/client_connections.h> + +#include <stdio.h> +#include <string.h> + + +typedef struct { +    u16 port; +} ParsedArgs;  + + +internal_fn b32 +parse_args(ParsedArgs *args, int argc, char **argv) +{ +    if (argc != 3) { +        goto format_err; +    } + + +    if (strcmp(argv[1], "-port") != 0) { +        goto format_err; +    } + +    u16 port = atoi(argv[2]); +    if (port == 0) { +        printf("port number is invalid\n"); +        return false; +    } + + +    args->port = port; +    return true; + + +format_err: +    printf("invocation format error, execpting \"-port <portnum>\"\n"); +    return false; +} + + +int +main(int argc, char **argv) +{ +    u64 perma_storage_size = MEBIBYTES(2); +    OSMemory memory; +    if (!os_memory_allocate(&memory, perma_storage_size)) { +        return EXIT_FAILURE; +    } + + +    Arena perma_arena; +    perma_arena.size_used = 0; +    perma_arena.size_max = MEBIBYTES(2); +    perma_arena.memory = memory.p; + + +    ParsedArgs args; +    if (!parse_args(&args, argc, argv)) { +        return EXIT_FAILURE; +    } + +    ClientConnections *client_connections = client_connections_create(&perma_arena, args.port); +    if (!client_connections) { +        return EXIT_FAILURE; +    } +    client_connections_manage(client_connections); + + +    return EXIT_SUCCESS; +} diff --git a/src/server/s2c_sender.c b/src/server/s2c_sender.c new file mode 100644 index 0000000..37e9ffc --- /dev/null +++ b/src/server/s2c_sender.c @@ -0,0 +1,77 @@ +#include <server/s2c_sender.h> + +internal_var Arena s_send_arena; + +void +send_s2c_chat_message(ClientConnection *conn, String32 *username, String32 *content, OSTime now) +{ +    OSTime time = os_time_get_now(); +    Arena *arena = &s_send_arena; + + +    S2C_ChatMessage *chat_message = arena_push(arena, sizeof(S2C_ChatMessage)); + +    String32 *username_copy = string32_create_from_string32(arena, username); +    chat_message->username = (String32*)((u8*)username_copy - (u8*)chat_message); + +    String32 *content_copy = string32_create_from_string32(arena, content); +    chat_message->content = (String32*)((u8*)content_copy - (u8*)chat_message); + +    chat_message->epoch_time_seconds = time.seconds; +    chat_message->epoch_time_nanoseconds = time.nanoseconds; + +    chat_message->header.type = S2C_CHAT_MESSAGE; +    chat_message->header.size = arena->size_used; + + +    os_net_secure_stream_send(conn->secure_stream_id, arena->memory, arena->size_used); +    arena_clear(arena); +} + + +void +send_s2c_user_update(ClientConnection *conn, String32 *username, u32 online_status) +{ +    Arena *arena = &s_send_arena; + + +    S2C_UserUpdate *user_update = arena_push(arena, sizeof(S2C_UserUpdate)); + +    user_update->status = online_status; + +    String32 *username_copy = string32_create_from_string32(arena, username); +    user_update->username = (String32*)((u8*)username_copy - (u8*)user_update); + +    user_update->header.type = S2C_USER_UPDATE; +    user_update->header.size = arena->size_used; + + +    os_net_secure_stream_send(conn->secure_stream_id, arena->memory, arena->size_used); +    arena_clear(arena); +} + + +void +send_s2c_login(ClientConnection *conn, u32 login_result) +{ +    Arena *arena = &s_send_arena; + + +    S2C_Login *login = arena_push(arena, sizeof(S2C_Login)); +    login->login_result = login_result; + +    login->header.type = S2C_LOGIN; +    login->header.size = arena->size_used; + + +    os_net_secure_stream_send(conn->secure_stream_id, arena->memory, arena->size_used); +    arena_clear(arena); +} + + +void +s2c_sender_init(Arena *arena) +{ +    arena_init(&s_send_arena, 1408); +} + diff --git a/src/server/s2c_sender.h b/src/server/s2c_sender.h new file mode 100644 index 0000000..ac84793 --- /dev/null +++ b/src/server/s2c_sender.h @@ -0,0 +1,14 @@ +#ifndef S2C_SENDER_H +#define S2C_SENDER_H + +#include <basic/time.h> +#include <basic/arena.h> +#include <server/client_connections.h> + +void s2c_sender_init(Arena *arena); + +void send_s2c_login(ClientConnection *conn, u32 login_result); +void send_s2c_chat_message(ClientConnection *conn, String32 *username, String32 *content, Time time); +void send_s2c_user_update(ClientConnection *conn, String32 *username, u32 online_status); + +#endif // S2C_SENDER_H  | 
