aboutsummaryrefslogtreecommitdiff
path: root/src/os/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/linux')
-rw-r--r--src/os/linux/linux_file.c36
-rw-r--r--src/os/linux/linux_library.c25
-rw-r--r--src/os/linux/linux_memory.c38
-rw-r--r--src/os/linux/linux_net_secure_stream.c579
-rw-r--r--src/os/linux/linux_sound.c269
-rw-r--r--src/os/linux/linux_time.c23
-rw-r--r--src/os/linux/linux_window.c304
7 files changed, 1274 insertions, 0 deletions
diff --git a/src/os/linux/linux_file.c b/src/os/linux/linux_file.c
new file mode 100644
index 0000000..5071f77
--- /dev/null
+++ b/src/os/linux/linux_file.c
@@ -0,0 +1,36 @@
+#include <os/os.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+
+char *
+os_file_read_as_string(Arena *arena, char *filepath, size_t *len)
+{
+ int fd = open(filepath, O_RDONLY);
+ if (fd == -1) {
+ printf("can't open file %s for reading\n", filepath);
+ return 0;
+ }
+
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) == -1) {
+ printf("can't fstat file %s\n", filepath);
+ return 0;
+ }
+
+ char *dest = arena_push(arena, statbuf.st_size+1);
+ ssize_t bytes_read = read(fd, dest, statbuf.st_size);
+ if (bytes_read != statbuf.st_size) {
+ printf("only read %ld/%ld bytes\n", statbuf.st_size, bytes_read);
+ arena->size_used -= statbuf.st_size+1; // Todo: cleanup
+ return 0;
+ }
+ dest[bytes_read] = '\0';
+ *len = statbuf.st_size;
+
+ return dest;
+}
+
+
diff --git a/src/os/linux/linux_library.c b/src/os/linux/linux_library.c
new file mode 100644
index 0000000..bd9efe1
--- /dev/null
+++ b/src/os/linux/linux_library.c
@@ -0,0 +1,25 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <SDL2/SDL.h>
+
+typedef void PlatformLibrary;
+
+PlatformLibrary *platform_library_open(const char *path) {
+ void *handle = dlopen(path, RTLD_NOW);
+ if (!handle) {
+ printf("could not open library %s\n", path);
+ return 0;
+ }
+
+ return handle;
+}
+
+void platform_library_close(PlatformLibrary *handle) {
+ dlclose(handle);
+}
+
+void *platform_library_get_proc(PlatformLibrary *handle, const char *name) {
+ void *addr = dlsym(handle, name);
+ return addr;
+}
+
diff --git a/src/os/linux/linux_memory.c b/src/os/linux/linux_memory.c
new file mode 100644
index 0000000..668604e
--- /dev/null
+++ b/src/os/linux/linux_memory.c
@@ -0,0 +1,38 @@
+#include <os/os.h>
+#include <basic/basic.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+
+b32
+os_memory_allocate(OSMemory *memory, size_t size)
+{
+ // @Performance: Keep the fd of /dev/zero open for the whole runtime?
+ int fd = open("/dev/zero", O_RDWR);
+ if (fd == -1) {
+ printf("open() failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ void *address = mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (address == MAP_FAILED) {
+ printf("mmap failed\n");
+ return false;
+ }
+
+ close(fd);
+
+ memory->size = size;
+ memory->p = address;
+ return true;
+}
+
+void
+os_memory_free(OSMemory *memory)
+{
+ munmap(memory->p, memory->size);
+}
+
diff --git a/src/os/linux/linux_net_secure_stream.c b/src/os/linux/linux_net_secure_stream.c
new file mode 100644
index 0000000..cfacfc3
--- /dev/null
+++ b/src/os/linux/linux_net_secure_stream.c
@@ -0,0 +1,579 @@
+#include <os/os.h>
+#include <basic/basic.h>
+#include <basic/arena.h>
+#include <crypto/rsa.h>
+#include <crypto/aes_gcm.h>
+
+#include <string.h>
+
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+#include <openssl/rand.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+typedef struct {
+ // space left for 512 - 64*2 - 2 = 382 for 4096-bit rsa and SHA-512
+
+ AesGcmKey aes_key_client_encrypt; // 32 bytes
+ AesGcmIv aes_iv_client_encrypt; // 12 bytes
+
+ AesGcmKey aes_key_client_decrypt; // 32 bytes
+ AesGcmIv aes_iv_client_decrypt; // 12 bytes
+
+ // space left for 382 - 2*32 - 2*12 = 294 for 4096-bit rsa and SHA-512
+
+ u8 client_random[256];
+} CM_Handshake;
+
+
+typedef struct {
+ u8 client_random[256];
+} SM_Handshake;
+
+
+typedef struct {
+ int fd;
+ OSNetSecureStreamStatus status;
+
+ EVP_PKEY *rsa_key;
+
+ AesGcmKey recv_aes_key; // recvd with CM_Handshake
+ AesGcmIv recv_aes_iv; // recvd_with CM_Handshake
+ AesGcmHeader recv_aes_header; // recvd with each aes message
+ b32 recv_aes_header_initialized;
+ size_t recv_ciphertext_size_filled;
+ u8 recv_ciphertext[1408];
+
+ size_t recv_plaintext_size_filled;
+ size_t recv_plaintext_size_processed;
+ u8 recv_plaintext[1408];
+
+ AesGcmKey send_aes_key; // recvd with CM_Handshake
+ AesGcmIv send_aes_iv; // recvd with CM_Handshake
+ size_t send_ciphertext_size_used;
+ u8 send_ciphertext[1408];
+} OSNetSecureStream;
+
+
+internal_var u32 s_max_secure_stream_count;
+internal_var OSNetSecureStream *s_secure_streams;
+
+internal_var u32 s_free_id_count;
+internal_var u32 *s_free_ids;
+
+
+
+OSNetSecureStreamStatus
+os_net_secure_stream_get_status(u32 id)
+{
+ return s_secure_streams[id].status;
+}
+
+internal_fn b32
+recv_and_decrypt_aes_package(OSNetSecureStream *secure_stream)
+{
+ i64 size_to_recv;
+ i64 size_recvd;
+
+ // recv aes header
+ if (!secure_stream->recv_aes_header_initialized) {
+ void *dest = secure_stream->recv_ciphertext + secure_stream->recv_ciphertext_size_filled;
+ size_to_recv = sizeof(AesGcmHeader) - secure_stream->recv_ciphertext_size_filled;
+ size_recvd = recv(secure_stream->fd, dest, size_to_recv, 0);
+ if (size_recvd < 0) {
+ if (errno != EAGAIN) {
+ printf("error: recv aes header failed with errno = %d\n", errno);
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ }
+ return false;
+ }
+ else if (size_recvd == 0) {
+ secure_stream->status = OS_NET_SECURE_STREAM_DISCONNECTED;
+ return false;
+ }
+ else if (size_recvd < size_to_recv) {
+ secure_stream->recv_ciphertext_size_filled += size_recvd;
+ return false;
+ }
+
+ AesGcmHeader *aes_header = (AesGcmHeader*)secure_stream->recv_ciphertext;
+ if (aes_header->payload_size > sizeof(secure_stream->recv_ciphertext)) {
+ printf("error: aes header has invalid payload size\n");
+ secure_stream->recv_ciphertext_size_filled = 0;
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ return false;
+ }
+
+ secure_stream->recv_aes_header = *(AesGcmHeader*)(secure_stream->recv_ciphertext);
+ secure_stream->recv_aes_header_initialized = true;
+ secure_stream->recv_ciphertext_size_filled = 0;
+ }
+
+
+ // recv aes payload
+ size_to_recv = secure_stream->recv_aes_header.payload_size - secure_stream->recv_ciphertext_size_filled;
+ size_recvd = recv(secure_stream->fd, secure_stream->recv_ciphertext, size_to_recv, 0);
+ if (size_recvd < 0) {
+ if (errno != EAGAIN) {
+ printf("error: recv aes payload failed with errno = %d\n", errno);
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ }
+ return false;
+ }
+ else if (size_recvd == 0) {
+ secure_stream->status = OS_NET_SECURE_STREAM_DISCONNECTED;
+ return false;
+ }
+ else if (size_recvd < size_to_recv) {
+ secure_stream->recv_ciphertext_size_filled += size_recvd;
+ return false;
+ }
+
+ secure_stream->recv_ciphertext_size_filled += size_recvd;
+
+
+ // decrypt
+ b32 decrypted = aes_gcm_decrypt(&secure_stream->recv_aes_key,
+ &secure_stream->recv_aes_iv,
+ secure_stream->recv_plaintext,
+ secure_stream->recv_ciphertext,
+ secure_stream->recv_ciphertext_size_filled,
+ secure_stream->recv_aes_header.tag,
+ sizeof(secure_stream->recv_aes_header.tag));
+ if (!decrypted) {
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ return false;
+ }
+
+ secure_stream->recv_plaintext_size_filled = secure_stream->recv_ciphertext_size_filled;
+ secure_stream->recv_aes_header_initialized = false;
+ secure_stream->recv_plaintext_size_processed = 0;
+ secure_stream->recv_ciphertext_size_filled = 0;
+
+ return true;
+}
+
+
+i64
+os_net_secure_stream_recv(u32 id, u8 *buff, size_t size)
+{
+ OSNetSecureStream *secure_stream = &s_secure_streams[id];
+ if (secure_stream->status != OS_NET_SECURE_STREAM_CONNECTED) {
+ return -1;
+ }
+
+ size_t size_delivered = 0;
+ while (size_delivered < size) {
+ size_t plaintext_size_avail = secure_stream->recv_plaintext_size_filled - secure_stream->recv_plaintext_size_processed;
+ if (plaintext_size_avail > 0) {
+ u8 *src = secure_stream->recv_plaintext + secure_stream->recv_plaintext_size_processed;
+ u8 *dest = buff + size_delivered;
+ size_t size_to_copy = MIN(size, plaintext_size_avail);
+ memcpy(dest, src, size_to_copy);
+ secure_stream->recv_plaintext_size_processed += size_to_copy;
+ size_delivered += size_to_copy;
+ }
+ else {
+ b32 recvd_and_decrypted = recv_and_decrypt_aes_package(secure_stream);
+ if (!recvd_and_decrypted) {
+ if (secure_stream->status == OS_NET_SECURE_STREAM_ERROR) {
+ return -1;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+
+ return size_delivered;
+}
+
+i64
+os_net_secure_stream_send(u32 id, u8 *buff, size_t size)
+{
+ OSNetSecureStream *secure_stream = &s_secure_streams[id];
+ if (secure_stream->status != OS_NET_SECURE_STREAM_CONNECTED) {
+ return -1;
+ }
+
+
+ size_t size_original = size;
+
+ while (size) {
+ AesGcmHeader *aes_header = (AesGcmHeader*)secure_stream->send_ciphertext;
+ void *aes_payload = secure_stream->send_ciphertext + sizeof(*aes_header);
+ i64 aes_payload_size = MIN(size, sizeof(secure_stream->send_ciphertext) - sizeof(*aes_header));
+
+ aes_header->payload_size = aes_payload_size;
+ b32 encrypted = aes_gcm_encrypt(&secure_stream->send_aes_key,
+ &secure_stream->send_aes_iv,
+ aes_payload, buff, aes_payload_size,
+ aes_header->tag, sizeof(aes_header->tag));
+ if (!encrypted) {
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ return -1;
+ }
+
+ size_t size_to_send = sizeof(*aes_header) + aes_payload_size;
+ i64 size_sent = send(secure_stream->fd, secure_stream->send_ciphertext, size_to_send, 0);
+ if (size_sent != size_to_send) {
+ printf("error: send only sent %ld/%ld bytes\n", size_to_send, size_sent);
+ secure_stream->status = OS_NET_SECURE_STREAM_ERROR;
+ return -1;
+ }
+
+ buff += aes_payload_size;
+ size -= aes_payload_size;
+ }
+
+ i64 result = size_original - size;
+ return result;
+}
+
+int
+os_net_secure_stream_get_fd(u32 id)
+{
+ OSNetSecureStream *secure_stream = &s_secure_streams[id];
+ return secure_stream->fd;
+}
+
+
+internal_fn void
+os_net_secure_stream_free(u32 id)
+{
+ s_free_ids[s_free_id_count] = id;
+ s_free_id_count += 1;
+}
+
+
+internal_fn u32
+os_net_secure_stream_alloc(void)
+{
+ assert(s_free_id_count > 0);
+ if (s_free_id_count == 0) {
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ u32 id = s_free_ids[s_free_id_count-1];
+ s_free_id_count -= 1;
+
+ return id;
+}
+
+
+void
+os_net_secure_stream_close(u32 id)
+{
+ OSNetSecureStream *secure_stream = &s_secure_streams[id];
+
+ close(secure_stream->fd);
+ memset(secure_stream, 0, sizeof(*secure_stream));
+
+ os_net_secure_stream_free(id);
+}
+
+u32
+os_net_secure_stream_listen(u16 port, EVP_PKEY *rsa_pri)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd == -1) {
+ perror("socket()");
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ int enable_reuse = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable_reuse, sizeof(int)) < 0) {
+ perror("setsockopt(SO_REUSEADDR)");
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ struct sockaddr_in local_addr;
+ local_addr.sin_family = AF_INET;
+ local_addr.sin_port = htons(port);
+ local_addr.sin_addr.s_addr = INADDR_ANY;
+ if (bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
+ perror("bind()");
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ int backlog = 128;
+ if (listen(fd, backlog) == -1) {
+ perror("listen()");
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ u32 id = os_net_secure_stream_alloc();
+
+ OSNetSecureStream *secure_stream = &s_secure_streams[id];
+ secure_stream->fd = fd;
+ secure_stream->status = OS_NET_SECURE_STREAM_CONNECTED;
+ secure_stream->rsa_key = rsa_pri;
+
+
+ return id;
+}
+
+
+internal_fn b32
+set_socket_nonblocking(int fd)
+{
+ int flags;
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
+ perror("fcntl(F_GETFL)");
+ return false;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("fcntl(F_SETFL)");
+ return false;
+ }
+ return true;
+}
+
+u32
+os_net_secure_stream_accept(u32 listener_id)
+{
+ OSNetSecureStream *listener = &s_secure_streams[listener_id];
+
+
+ struct sockaddr_in addr;
+ socklen_t addr_size = sizeof(addr);
+
+ int fd = accept(listener->fd, (struct sockaddr*)&addr, &addr_size);
+ if (fd == -1) {
+ printf("accept() failed\n");
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ u32 secure_stream_id = os_net_secure_stream_alloc();
+ if (secure_stream_id == OS_NET_SECURE_STREAM_ID_INVALID) {
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ OSNetSecureStream *secure_stream = &s_secure_streams[secure_stream_id];
+ secure_stream->fd = fd;
+ secure_stream->status = OS_NET_SECURE_STREAM_HANDSHAKING;
+
+
+ // recv rsa request
+ u8 encrypted_cm_handshake[512]; // Todo: use secure_stream->recv_buff
+ i64 recvd_size = recv(secure_stream->fd, encrypted_cm_handshake, sizeof(encrypted_cm_handshake), MSG_WAITALL);
+ if (recvd_size != sizeof(encrypted_cm_handshake)) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ // decrypt rsa request
+ CM_Handshake cm_handshake;
+ if (!rsa_decrypt(listener->rsa_key, &cm_handshake, encrypted_cm_handshake, sizeof(encrypted_cm_handshake))) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ // process request
+ memcpy(&secure_stream->recv_aes_key, &cm_handshake.aes_key_client_encrypt, sizeof(cm_handshake.aes_key_client_encrypt));
+ memcpy(&secure_stream->recv_aes_iv, &cm_handshake.aes_iv_client_encrypt, sizeof(cm_handshake.aes_iv_client_encrypt));
+ memcpy(&secure_stream->send_aes_key, &cm_handshake.aes_key_client_decrypt, sizeof(cm_handshake.aes_key_client_decrypt));
+ memcpy(&secure_stream->send_aes_iv, &cm_handshake.aes_iv_client_decrypt, sizeof(cm_handshake.aes_iv_client_decrypt));
+
+
+ // prepare aes response
+ AesGcmHeader *aes_header = (AesGcmHeader*)secure_stream->send_ciphertext;
+
+ SM_Handshake sm_handshake;
+ memcpy(sm_handshake.client_random, cm_handshake.client_random, sizeof(cm_handshake.client_random));
+
+ // encrypt aes response
+ if (!aes_gcm_encrypt(&secure_stream->send_aes_key,
+ &secure_stream->send_aes_iv,
+ secure_stream->send_ciphertext + sizeof(*aes_header), (u8*)&sm_handshake, sizeof(sm_handshake),
+ aes_header->tag, sizeof(aes_header->tag))) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+ aes_header->payload_size = sizeof(sm_handshake);
+ secure_stream->send_ciphertext_size_used = sizeof(*aes_header) + sizeof(sm_handshake);
+
+ // send response
+ i64 sent_size = send(secure_stream->fd, secure_stream->send_ciphertext, secure_stream->send_ciphertext_size_used, 0);
+ if (sent_size != secure_stream->send_ciphertext_size_used) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ if (!set_socket_nonblocking(fd)) {
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ secure_stream->status = OS_NET_SECURE_STREAM_CONNECTED;
+ return secure_stream_id;
+}
+
+u32
+os_net_secure_stream_connect(char *address, u16 port, EVP_PKEY *server_rsa_pub)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd == -1) {
+ printf("cant open socket\n");
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ struct sockaddr_in target_addr;
+ memset(&target_addr, 0, sizeof(target_addr));
+ target_addr.sin_family = AF_INET;
+ target_addr.sin_port = htons(port);
+ target_addr.sin_addr.s_addr = inet_addr(address);
+ if (connect(fd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {
+ printf("connect failed\n");
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+
+ u32 secure_stream_id = os_net_secure_stream_alloc();
+ if (secure_stream_id == OS_NET_SECURE_STREAM_ID_INVALID) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ OSNetSecureStream *secure_stream = &s_secure_streams[secure_stream_id];
+ secure_stream->fd = fd;
+ secure_stream->rsa_key = server_rsa_pub;
+
+
+ if (!aes_gcm_key_init_random(&secure_stream->send_aes_key)) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+ if (!aes_gcm_iv_init(&secure_stream->send_aes_iv)) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+ if (!aes_gcm_key_init_random(&secure_stream->recv_aes_key)) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+ if (!aes_gcm_iv_init(&secure_stream->recv_aes_iv)) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ /* Request */
+
+ // prepare rsa request
+ CM_Handshake cm_handshake;
+ memcpy(&cm_handshake.aes_key_client_encrypt, &secure_stream->send_aes_key, sizeof(secure_stream->send_aes_key));
+ memcpy(&cm_handshake.aes_iv_client_encrypt, &secure_stream->send_aes_iv, sizeof(secure_stream->send_aes_iv));
+ memcpy(&cm_handshake.aes_key_client_decrypt, &secure_stream->recv_aes_key, sizeof(secure_stream->recv_aes_key));
+ memcpy(&cm_handshake.aes_iv_client_decrypt, &secure_stream->recv_aes_iv, sizeof(secure_stream->recv_aes_iv));
+ if (RAND_bytes(cm_handshake.client_random, sizeof(cm_handshake.client_random)) != 1) {
+ printf("RAND_bytes failed at %s:%d", __FILE__, __LINE__);
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ // encrypt rsa request
+ void *encrypted_req = secure_stream->send_ciphertext;
+ if (!rsa_encrypt(server_rsa_pub, encrypted_req, &cm_handshake, sizeof(cm_handshake))) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ // send rsa request
+ i64 sent_size = send(secure_stream->fd, encrypted_req, 512, 0);
+ if (sent_size != 512) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ /* Response */
+
+ // recv aes response
+ AesGcmHeader *aes_header = (AesGcmHeader*)secure_stream->recv_ciphertext;
+ void *aes_payload = secure_stream->recv_ciphertext + sizeof(*aes_header);
+
+ size_t response_size = sizeof(*aes_header) + sizeof(SM_Handshake);
+
+ i64 recvd_size = recv(secure_stream->fd, secure_stream->recv_ciphertext, response_size, 0);
+ if (recvd_size != response_size) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ // decrypt aes response
+ SM_Handshake sm_handshake;
+ if (!aes_gcm_decrypt(&secure_stream->recv_aes_key,
+ &secure_stream->recv_aes_iv,
+ (u8*)&sm_handshake, aes_payload, sizeof(sm_handshake),
+ aes_header->tag, sizeof(aes_header->tag))) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+ // verify aes response
+ assert(sizeof(cm_handshake.client_random) == sizeof(sm_handshake.client_random));
+ if (memcmp(cm_handshake.client_random, sm_handshake.client_random, sizeof(cm_handshake.client_random)) != 0) {
+ close(fd);
+ os_net_secure_stream_free(secure_stream_id);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ if (!set_socket_nonblocking(fd)) {
+ close(fd);
+ return OS_NET_SECURE_STREAM_ID_INVALID;
+ }
+
+
+ secure_stream->status = OS_NET_SECURE_STREAM_CONNECTED;
+ secure_stream->rsa_key = server_rsa_pub;
+ return secure_stream_id;
+}
+
+void
+os_net_secure_streams_init(Arena *arena, size_t max_count)
+{
+ s_max_secure_stream_count = max_count;
+ s_secure_streams = arena_push(arena, max_count * sizeof(OSNetSecureStream));
+ memset(s_secure_streams, 0, max_count * sizeof(OSNetSecureStream));
+
+ s_free_id_count = max_count;
+ s_free_ids = arena_push(arena, max_count * sizeof(u32));
+ for (size_t i = 0; i < max_count; i++) {
+ s_free_ids[i] = i;
+ }
+}
+
diff --git a/src/os/linux/linux_sound.c b/src/os/linux/linux_sound.c
new file mode 100644
index 0000000..2669d40
--- /dev/null
+++ b/src/os/linux/linux_sound.c
@@ -0,0 +1,269 @@
+// Todo: Use a high-priority thread for playing the sound buffer.
+// Todo: refactor SND_CALL and other snd_... calls to deal with errors/state better.
+// Note: Currently, the snd-api's buffer is large enough to write everything at once
+
+#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 <alsa/asoundlib.h>
+
+
+struct OSSoundPlayer {
+ snd_pcm_t *pcm;
+ u32 frames_per_period;
+ u32 samples_per_second;
+ i32 channel_count;
+ i32 max_sample_count;
+ OSSoundBuffer sound_buffer;
+};
+
+
+#define SND_CALL(snd_call) \
+{ \
+ int error = snd_call; \
+ if (error < 0) { \
+ const char *error_str = snd_strerror(error); \
+ printf(#snd_call" err: %s\n", error_str); \
+ snd_pcm_close(pcm); \
+ return false; \
+ } \
+}
+
+internal_fn void
+os_list_sound_devices(void)
+{
+ int status;
+ int card = -1;
+ char* longname = NULL;
+ char* shortname = NULL;
+
+ if ((status = snd_card_next(&card)) < 0) {
+ printf("cannot determine card number: %s", snd_strerror(status));
+ return;
+ }
+ if (card < 0) {
+ printf("no sound cards found");
+ return;
+ }
+ while (card >= 0) {
+ printf("Card %d:", card);
+ if ((status = snd_card_get_name(card, &shortname)) < 0) {
+ printf("cannot determine card shortname: %s", snd_strerror(status));
+ break;
+ }
+ if ((status = snd_card_get_longname(card, &longname)) < 0) {
+ printf("cannot determine card longname: %s", snd_strerror(status));
+ break;
+ }
+ printf("\tLONG NAME: %s\n", longname);
+ printf("\tSHORT NAME: %s\n", shortname);
+
+
+ char cardname[128];
+ //sprintf(cardname, "default");
+ sprintf(cardname, "hw:%d", card);
+ printf("cardname = %s\n", cardname);
+
+ snd_ctl_t *ctl;
+ int mode = SND_PCM_ASYNC; // SND_PCM_NONBLOCK or SND_PCM_ASYNC
+ if (snd_ctl_open(&ctl, cardname, mode) >= 0) {
+ printf("cannot snd_stl_open\n");
+ int device = -1;
+ if (snd_ctl_pcm_next_device(ctl, &device) < 0) {
+ printf("no devices found\n");
+ device = -1;
+ }
+
+ if (device == -1) {
+ printf("no device\n");
+ }
+ while (device >= 0) {
+ char devicename[256];
+ sprintf(devicename, "%s,%d", cardname, device);
+ printf("devicename = %s\n", devicename);
+
+ snd_pcm_t *pcm_handle;
+ snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; // SND_PCM_STREAM_PLAYBACK or SND_PCM_STREAM_CAPTURE
+ if (snd_pcm_open(&pcm_handle, devicename, stream, mode) >= 0) {
+ printf("snd_pcm_open success\n");
+ snd_pcm_close(pcm_handle);
+ }
+ else {
+ printf("snd_pcm_open failed\n");
+ }
+
+ if (snd_ctl_pcm_next_device(ctl, &device) < 0) {
+ printf("snd_ctl_pcm_next_device error\n");
+ break;
+ }
+ }
+
+ snd_ctl_close(ctl);
+ }
+ else {
+ printf("snd_ctl_open failed\n");
+ }
+
+
+ if ((status = snd_card_next(&card)) < 0) {
+ printf("cannot determine card number: %s", snd_strerror(status));
+ break;
+ }
+ }
+}
+
+internal_fn b32
+os_sound_player_recover(snd_pcm_t *pcm, i64 err)
+{
+ if (err == -EPIPE) {
+ err = snd_pcm_prepare(pcm);
+ if (err < 0) {
+ printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
+ return false;
+ }
+ return true;
+ }
+ else if (err == -ESTRPIPE) {
+ while ((err = snd_pcm_resume(pcm)) == -EAGAIN) {
+ struct timespec ts = {0, 100};
+ nanosleep(&ts, 0);
+ }
+ if (err < 0) {
+ err = snd_pcm_prepare(pcm);
+ if (err < 0) {
+ printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
+ return false;
+ }
+ return true;
+ }
+ return true;
+ }
+ printf("Can't recover: %s\n", snd_strerror(err));
+ return false;
+}
+
+
+void
+os_sound_player_play(OSSoundPlayer *player)
+{
+ OSSoundBuffer *buffer = &player->sound_buffer;
+
+ i16 *samples = player->sound_buffer.samples;
+ u32 frames_to_write = buffer->sample_count / player->channel_count;
+ while (frames_to_write) {
+ snd_pcm_sframes_t frames_written = snd_pcm_writei(player->pcm, samples, frames_to_write);
+ if (frames_written < 0) {
+ if (!os_sound_player_recover(player->pcm, frames_written)) {
+ return;
+ }
+ continue;
+ }
+ samples += frames_written * player->channel_count;
+ frames_to_write -= frames_written;
+ }
+
+ int state = snd_pcm_state(player->pcm);
+ if (state != SND_PCM_STATE_RUNNING) {
+ snd_pcm_t *pcm = player->pcm;
+ snd_pcm_start(pcm);
+ }
+}
+
+void
+os_sound_player_close(OSSoundPlayer *player)
+{
+ snd_pcm_close(player->pcm);
+ free(player->sound_buffer.samples);
+}
+
+OSSoundBuffer *
+platform_sound_player_get_buffer(OSSoundPlayer *player)
+{
+ return &player->sound_buffer;
+}
+
+OSSoundBuffer *
+os_sound_player_get_buffer(OSSoundPlayer *player)
+{
+ i64 frames_avail = snd_pcm_avail(player->pcm);
+ if (frames_avail < 0) {
+ if (os_sound_player_recover(player->pcm, frames_avail)) {
+ frames_avail = 0;
+ }
+ frames_avail = snd_pcm_avail(player->pcm);
+ if (frames_avail < 0) {
+ frames_avail = 0;
+ }
+ }
+
+ OSSoundBuffer *buffer = &player->sound_buffer;
+ buffer->sample_count = frames_avail*2;
+ memset(buffer->samples, 0, buffer->sample_count * sizeof(i16));
+ return buffer;
+}
+
+OSSoundPlayer *
+os_sound_player_create(Arena *arena, i32 samples_per_second)
+{
+ // open pcm
+ snd_pcm_t *pcm = 0;
+ const char *device_name = "default";
+ int stream = SND_PCM_STREAM_PLAYBACK;
+ int mode = SND_PCM_ASYNC;
+
+
+ SND_CALL(snd_pcm_open(&pcm, device_name, stream, mode));
+
+
+ // set hw params
+
+ snd_pcm_hw_params_t *params;
+ snd_pcm_hw_params_alloca(&params);
+
+ SND_CALL(snd_pcm_hw_params_any(pcm, params)); // get full configuration space
+
+ int access = SND_PCM_ACCESS_RW_INTERLEAVED;
+ SND_CALL(snd_pcm_hw_params_set_access(pcm, params, access));
+
+ int format = SND_PCM_FORMAT_S16_LE;
+ SND_CALL(snd_pcm_hw_params_set_format(pcm, params, format));
+
+ u32 channels = 2;
+ SND_CALL(snd_pcm_hw_params_set_channels(pcm, params, channels));
+
+ u32 sample_rate = 44100;
+ SND_CALL(snd_pcm_hw_params_set_rate_near(pcm, params, &sample_rate, 0));
+
+ u32 period_count = 3;
+ u32 sample_count = period_count * (sample_rate / 30);
+ u32 frame_count = sample_count / channels;
+ SND_CALL(snd_pcm_hw_params_set_buffer_size(pcm, params, frame_count));
+
+ u32 frames_per_period = frame_count / period_count;
+ SND_CALL(snd_pcm_hw_params_set_period_size(pcm, params, frames_per_period, 0));
+
+ SND_CALL(snd_pcm_hw_params(pcm, params));
+
+
+ // create player
+ OSSoundPlayer *player = arena_push(arena, sizeof(OSSoundPlayer));
+ memset(player, 0, sizeof(*player));
+ player->pcm = pcm;
+ player->frames_per_period = frames_per_period;
+ player->samples_per_second = sample_rate;
+ player->channel_count = channels;
+
+ // Todo: Redesign this player & buffer interaction.
+ // create buffer
+ OSSoundBuffer *sound_buffer = &player->sound_buffer;
+ sound_buffer->sample_count = 0;
+ sound_buffer->max_sample_count = sample_count;
+ sound_buffer->samples = arena_push(arena, sample_count * sizeof(i16));
+
+ return player;
+}
+
diff --git a/src/os/linux/linux_time.c b/src/os/linux/linux_time.c
new file mode 100644
index 0000000..9a8f2cf
--- /dev/null
+++ b/src/os/linux/linux_time.c
@@ -0,0 +1,23 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <os/os.h>
+
+#include <string.h>
+#include <time.h>
+
+OSTime
+os_time_get_now(void)
+{
+ struct timespec ts;
+ memset(&ts, 0, sizeof(ts));
+ i32 err = clock_gettime(CLOCK_REALTIME, &ts);
+ if (err != 0) {
+ printf("failed to get time\n");
+ }
+
+ OSTime result;
+ result.seconds = ts.tv_sec;
+ result.nanoseconds = ts.tv_nsec;
+ return result;
+}
+
diff --git a/src/os/linux/linux_window.c b/src/os/linux/linux_window.c
new file mode 100644
index 0000000..d739b3c
--- /dev/null
+++ b/src/os/linux/linux_window.c
@@ -0,0 +1,304 @@
+// Note: This is probably deprecated and can be deleted.
+
+#include <basic/basic.h>
+#include <os/os.h>
+
+#include <SDL3/SDL.h>
+#include <X11/Xlib.h>
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+struct OSWindow {
+ Window xid;
+ Atom wm_delete_window;
+ u32 event_mask;
+
+ i32 width;
+ i32 height;
+
+ GLXContext glx_context;
+ u32 gl_texture_id;
+ OSOffscreenBuffer offscreen_buffer;
+
+ OSEvent current_event;
+};
+
+
+internal_var Display *g_display;
+
+internal_fn b32
+os_connect_to_x(void)
+{
+ const char *display_name = 0;
+ g_display = XOpenDisplay(display_name);
+ if (!g_display)
+ {
+ printf("XOpenDisplay(%s) failed\n", display_name);
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+os_window_destroy_offscreen_buffer(OSOffscreenBuffer *offscreen_buffer)
+{
+ free(offscreen_buffer->pixels);
+}
+
+void
+os_offscreen_buffer_resize(OSOffscreenBuffer *offscreen, i32 width, i32 height)
+{
+ u32 *new_pixels = realloc(offscreen->pixels, width*height*4);
+ if (new_pixels) {
+ offscreen->width = width;
+ offscreen->height = height;
+ offscreen->pixels = new_pixels;
+ glViewport(0, 0, width, height);
+ }
+}
+
+void
+os_offscreen_buffer_destroy(OSOffscreenBuffer *offscreen_buffer)
+{
+ free(offscreen_buffer->pixels);
+ free(offscreen_buffer);
+}
+
+OSOffscreenBuffer *
+os_offscreen_buffer_create(i32 width, i32 height)
+{
+ OSOffscreenBuffer *offscreen_buffer = malloc(sizeof(OSOffscreenBuffer));
+ if (offscreen_buffer) {
+ offscreen_buffer->green_shift = 8;
+ offscreen_buffer->blue_shift = 16;
+ offscreen_buffer->red_shift = 0;
+ offscreen_buffer->alpha_shift = 24;
+ offscreen_buffer->width = 0;
+ offscreen_buffer->height = 0;
+ offscreen_buffer->pixels = 0;
+ }
+ os_offscreen_buffer_resize(offscreen_buffer, width, height);
+ return offscreen_buffer;
+}
+
+void
+os_window_swap_buffers(OSWindow *window, OSOffscreenBuffer *offscreen_buffer)
+{
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, window->gl_texture_id);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, offscreen_buffer->width, offscreen_buffer->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, offscreen_buffer->pixels);
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
+ glTexCoord2f(1.0f, 0.0f); glVertex2f( 1.0f, -1.0f);
+ glTexCoord2f(1.0f, 1.0f); glVertex2f( 1.0f, 1.0f);
+ glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f);
+ glEnd();
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glXSwapBuffers(g_display, window->xid);
+}
+
+
+#define ADD_KEY_PRESS(c) \
+ event->type = OS_EVENT_KEY_PRESS; \
+ event->ev.key_press.code = c; \
+ event->ev.key_press.is_unicode = true; \
+ return true;
+#define ADD_SPECIAL_KEY_PRESS(c) \
+ event->type = OS_EVENT_KEY_PRESS; \
+ event->ev.key_press.code = c; \
+ event->ev.key_press.is_unicode = false; \
+ return true;
+
+b32
+os_window_get_event(OSWindow *window, OSEvent *event)
+{
+ while (XPending(g_display)) {
+ XEvent xevent;
+ XNextEvent(g_display, &xevent);
+
+ // Todo: Rework this whole shift/caps-lock logic.
+ persist_var b32 is_caps;
+ persist_var b32 is_shift_l;
+ persist_var b32 is_shift_r;
+ b32 is_uppercase = ( is_caps && !(is_shift_l || is_shift_r)) ||
+ (!is_caps && (is_shift_l || is_shift_r));
+
+ switch (xevent.type) {
+ case ClientMessage: {
+ if ((Atom)xevent.xclient.data.l[0] == window->wm_delete_window) {
+ event->type = OS_EVENT_WINDOW_DESTROYED;
+ return true;
+ }
+ continue;
+ } break;
+
+ case DestroyNotify: {
+ event->type = OS_EVENT_WINDOW_DESTROYED;
+ return true;
+ } break;
+
+ case ConfigureNotify: {
+ i32 width = xevent.xconfigure.width;
+ i32 height = xevent.xconfigure.height;
+ if (width != window->width || height != window->height) {
+ event->type = OS_EVENT_WINDOW_RESIZE;
+ event->ev.resize.width = width;
+ event->ev.resize.height = height;
+ window->width = width;
+ window->height = height;
+ return true;
+ }
+ continue;
+ } break;
+
+ case KeyPress: {
+ int index = is_uppercase ? 1 : 0;
+ KeySym keysym = XLookupKeysym(&xevent.xkey, index);
+ if (keysym >= 32 && keysym <= 126) {ADD_KEY_PRESS(keysym);}
+ else if (keysym == XK_Tab) {ADD_KEY_PRESS('\t'); }
+ else if (keysym == XK_Return) {ADD_KEY_PRESS('\r'); }
+ else if (keysym == XK_BackSpace) {ADD_KEY_PRESS('\b'); }
+ else if (keysym == XK_Delete) {ADD_KEY_PRESS(127); }
+ else if (keysym == XK_Left) {ADD_SPECIAL_KEY_PRESS(OS_KEYCODE_LEFT); }
+ else if (keysym == XK_Right) {ADD_SPECIAL_KEY_PRESS(OS_KEYCODE_RIGHT);}
+ else if (keysym == XK_Up) {ADD_SPECIAL_KEY_PRESS(OS_KEYCODE_UP); }
+ else if (keysym == XK_Down) {ADD_SPECIAL_KEY_PRESS(OS_KEYCODE_DOWN); }
+ else if (keysym == XK_Shift_L) is_shift_l = true;
+ else if (keysym == XK_Shift_R) is_shift_r = true;
+ else if (keysym == XK_Caps_Lock) is_caps = true;
+ } break;
+
+ case KeyRelease: {
+ int index = 0;
+ KeySym keysym = XLookupKeysym(&xevent.xkey, index);
+ if (keysym == XK_Shift_L) is_shift_l = false;
+ else if (keysym == XK_Shift_R) is_shift_r = false;
+ else if (keysym == XK_Caps_Lock) is_caps = false;
+ continue; // ignore this for now i guess
+ } break;
+
+ case GraphicsExpose: {
+ printf("graphics exposure happened\n");
+ continue;
+ } break;
+
+ default:;
+ }
+ }
+ return false;
+}
+
+#undef ADD_KEY_PRESS
+#undef ADD_SPECIAL_KEY_PRESS
+
+
+void os_window_destroy(OSWindow *window) {
+ os_window_destroy_offscreen_buffer(&window->offscreen_buffer);
+ XDestroyWindow(g_display, window->xid);
+ free(window);
+}
+
+OSWindow *os_window_create(const char *name, i32 width, i32 height) {
+ if (!g_display) {
+ if (!os_connect_to_x())
+ return 0;
+ }
+
+ int screen_number = XDefaultScreen(g_display);
+ Window root_window_id = XRootWindow(g_display, screen_number);
+ if (!root_window_id)
+ {
+ printf("XDefaultRootWindow(display) failed\n");
+ XCloseDisplay(g_display);
+ return 0;
+ }
+
+ // get visual info
+ int depth = 24;
+ int va[] = {
+ GLX_RGBA, 1,
+ GLX_DOUBLEBUFFER, 1,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 8,
+ GLX_DEPTH_SIZE, depth,
+ None
+ };
+ XVisualInfo* vinfo = glXChooseVisual(g_display, screen_number, va);
+ if (!vinfo)
+ {
+ printf("glXChooseVisual failed\n");
+ return 0;
+ }
+
+ // set window attribs
+ long swa_mask = CWEventMask | CWColormap | CWBackPixmap |CWBorderPixel;
+ long swa_event_mask = PropertyChangeMask | SubstructureNotifyMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask;
+ Colormap colormap = XCreateColormap(g_display, root_window_id, vinfo->visual, AllocNone);
+
+ XSetWindowAttributes swa;
+ memset(&swa, 0, sizeof(swa));
+ swa.background_pixmap = None;
+ swa.border_pixel = 0;
+ swa.event_mask = swa_event_mask;
+ swa.colormap = colormap;
+
+ Window window_id = XCreateWindow(g_display, root_window_id, 0, 0, width, height, 0, depth, InputOutput, vinfo->visual, swa_mask, &swa);
+ XStoreName(g_display, window_id, name);
+ XMapWindow(g_display, window_id);
+
+
+ // I support the WM_DELETE_WINDOW protocol @Leak?
+ Atom wm_delete_window = XInternAtom(g_display, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(g_display, window_id, &wm_delete_window, 1);
+
+
+ // glx context
+ Bool direct = True;
+ GLXContext glx_context = glXCreateContext(g_display, vinfo, 0, direct);
+ if (glx_context == 0)
+ {
+ printf("glXCreateContext failed\n");
+ return 0;
+ }
+ Bool made_current = glXMakeCurrent(g_display, window_id, glx_context);
+ if (made_current == False)
+ {
+ printf("glXMakeCurrent failed\n");
+ return 0;
+ }
+
+
+ OSWindow *window = malloc(sizeof(OSWindow));
+ window->xid = window_id;
+ window->wm_delete_window = wm_delete_window;
+ window->event_mask = swa_event_mask;
+ window->glx_context = glx_context;
+ XSync(g_display, False);
+
+
+ glEnable(GL_TEXTURE_2D);
+
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, &window->gl_texture_id);
+ glBindTexture(GL_TEXTURE_2D, window->gl_texture_id);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+
+ return window;
+}
+