aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/c2s_handler.c159
-rw-r--r--src/server/c2s_handler.h10
-rw-r--r--src/server/client_connections.c249
-rw-r--r--src/server/client_connections.h44
-rw-r--r--src/server/main.c78
-rw-r--r--src/server/s2c_sender.c77
-rw-r--r--src/server/s2c_sender.h14
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