diff seobeo/s_network.c @ 119:c39582f937e5

[Seobeo Client] Added client side logic which will be used for all my other calls instead of curl.
author June Park <parkjune1995@gmail.com>
date Wed, 07 Jan 2026 16:05:57 -0800
parents seobeo/s_linux_network.c@70401cf61e97
children 7eb79fd91c7e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/s_network.c	Wed Jan 07 16:05:57 2026 -0800
@@ -0,0 +1,380 @@
+#include "seobeo/seobeo.h"
+
+
+Seobeo_Handle *Seobeo_Stream_Handle_Server_Create(const char *host,  const char* port)
+{
+  Seobeo_Handle *p_handle;
+  struct addrinfo hints, *server_infos, *free_server_info;
+  int32 socket_fd, yes = 1;  // Need this for setsockopt 
+
+  memset(&hints, 0, sizeof hints);
+  hints.ai_family   = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = IPPROTO_TCP;
+  hints.ai_flags    = AI_PASSIVE;
+
+  if (getaddrinfo(host, port, &hints, &server_infos) != 0)
+  { perror("getaddrinfo"); return NULL; }
+
+  for
+  (
+    free_server_info = server_infos;
+    free_server_info != NULL;
+    free_server_info = free_server_info->ai_next
+  )
+  {
+    if((socket_fd = socket(free_server_info->ai_family,
+                        free_server_info->ai_socktype, free_server_info->ai_protocol)) == -1)
+    { perror("socket"); continue; }
+
+     if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
+     { perror("setsockopt SO_REUSEADDR"); continue; }
+
+#ifdef SO_REUSEPORT
+     // SO_REUSEPORT allows multiple threads/processes to bind to the same port
+     // The kernel will distribute incoming connections among them
+     if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) == -1)
+     { perror("setsockopt SO_REUSEPORT"); continue; }
+#endif
+
+     if (bind(socket_fd, free_server_info->ai_addr, free_server_info->ai_addrlen) == -1)
+     { perror("v_network: Couldn't make socket non-blocking\n"); continue; }
+
+     break;
+  }
+
+  if (listen(socket_fd, 16) != 0)
+  { 
+    Seobeo_Log(SEOBEO_DEBUG, "Closing socket: %d\n", socket_fd);
+    perror("listen"); close(socket_fd); return NULL; 
+  }
+
+  int flags = fcntl(socket_fd, F_GETFL, 0);
+	if(fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0) { perror("fcntl"); return NULL; }
+  freeaddrinfo(server_infos);
+
+  p_handle = malloc(sizeof(*p_handle));
+  p_handle->socket = socket_fd;
+  p_handle->type = SEOBEO_STREAM_TYPE_SERVER;
+  p_handle->connected = FALSE;
+
+  p_handle->host = host != NULL ? strdup(host) : "localhost";
+  p_handle->port = strdup(port);
+
+  p_handle->ssl_ctx              = NULL;
+  p_handle->ssl                  = NULL;
+
+
+  p_handle->read_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
+  p_handle->read_buffer_capacity = INITIAL_BUFFER_CAPACITY;
+  p_handle->read_buffer_len = 0;
+
+  p_handle->write_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
+  p_handle->write_buffer_capacity = INITIAL_BUFFER_CAPACITY;
+  p_handle->write_buffer_len = 0;
+
+  p_handle->destroyed = FALSE;
+
+  return p_handle;
+}
+
+
+Seobeo_Handle *Seobeo_Stream_Handle_Client_Create(const char *host,  const char* port, boolean use_tls)
+{
+  Seobeo_Handle *p_handle;
+  p_handle = malloc(sizeof(*p_handle));
+
+  struct addrinfo hints, *server_infos;
+  int32 socket_fd;  // Need this for setsockopt 
+
+  memset(&hints, 0, sizeof hints);
+  hints.ai_family   = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+
+  if (getaddrinfo(host, port, &hints, &server_infos) != 0)
+  { perror("getaddrinfo"); return NULL; }
+
+
+  if((socket_fd = socket(server_infos->ai_family,
+                      server_infos->ai_socktype, server_infos->ai_protocol)) == -1)
+  { perror("socket"); return NULL; }
+
+  if (connect(socket_fd, server_infos->ai_addr, server_infos->ai_addrlen) != 0)
+  { perror("connect"); return NULL; }
+  freeaddrinfo(server_infos);
+
+  p_handle->socket = socket_fd;
+  p_handle->type = SEOBEO_STREAM_TYPE_CLIENT;
+
+  p_handle->ssl_ctx = NULL;
+  p_handle->ssl = NULL;
+
+  if (use_tls)
+  {
+    if (Seobeo_SSL_Setup_Client(p_handle, host, socket_fd) != 0)
+    {
+      free(p_handle);
+      return NULL;
+    }
+  }
+  p_handle->connected = TRUE;
+
+  p_handle->host = host != NULL ? strdup(host) : "localhost";
+  p_handle->port = strdup(port);
+
+  p_handle->read_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
+  p_handle->read_buffer_capacity = INITIAL_BUFFER_CAPACITY;
+  p_handle->read_buffer_len = 0;
+
+  p_handle->write_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
+  p_handle->write_buffer_capacity = INITIAL_BUFFER_CAPACITY;
+  p_handle->write_buffer_len = 0;
+
+  p_handle->destroyed = FALSE;
+
+  return p_handle;
+}
+
+Seobeo_Handle *Seobeo_Stream_Handle_Server_Accept(Seobeo_Handle *p_server_handle)
+{
+  struct sockaddr_storage addr;
+  socklen_t addrlen = sizeof addr;
+  char client_inet_addr[INET6_ADDRSTRLEN];
+
+  int client_fd = accept(p_server_handle->socket,
+                         (struct sockaddr*)&addr,
+                         &addrlen);
+  inet_ntop(
+      addr.ss_family,
+      Seobeo_Get_IP4_Or_IP6((struct sockaddr *)&addr),
+      client_inet_addr, sizeof client_inet_addr);
+  if (client_fd == -1) return NULL;
+
+  // Set non blocking...
+  int flags = fcntl(client_fd, F_GETFL, 0);
+  if (flags == -1) return NULL;
+  fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
+
+  Seobeo_Handle *p_client_handle        = malloc(sizeof *p_client_handle);
+
+  p_client_handle->socket               = client_fd;
+  p_client_handle->type                 = SEOBEO_STREAM_TYPE_CLIENT;
+  p_client_handle->connected            = TRUE;
+
+  // TODO: support SSL in the future.
+  p_client_handle->ssl_ctx              = NULL;
+  p_client_handle->ssl                  = NULL;
+
+  p_client_handle->host                 = strdup(client_inet_addr);
+  p_client_handle->port                 = NULL;
+
+  p_client_handle->read_buffer_capacity = p_server_handle->read_buffer_capacity;
+  p_client_handle->read_buffer_len      = 0;
+  p_client_handle->read_buffer          = malloc(p_client_handle->read_buffer_capacity);
+
+  p_client_handle->write_buffer_capacity = p_server_handle->write_buffer_capacity;
+  p_client_handle->write_buffer_len      = 0;
+  p_client_handle->write_buffer          = malloc(p_client_handle->write_buffer_capacity);
+
+  p_client_handle->read_buffer_used      = 0;
+  p_client_handle->file                  = NULL;
+  p_client_handle->text_copy             = NULL;
+  p_client_handle->file_name             = NULL;
+  p_client_handle->destroyed             = FALSE;
+
+  return p_client_handle;
+}
+
+void Seobeo_Handle_Destroy(Seobeo_Handle *p_handle)
+{
+  if (!p_handle) return;
+
+  boolean expected = FALSE;
+  // Need to check
+  if (!atomic_compare_exchange_strong(&p_handle->destroyed, &expected, TRUE))
+  {
+    return;
+  }
+
+  if (p_handle->host) Dowa_Free(p_handle->host);
+  if (p_handle->port) Dowa_Free(p_handle->port);
+
+  Seobeo_SSL_Cleanup(p_handle);
+
+  if (p_handle->socket) {
+    Seobeo_Log(SEOBEO_DEBUG, "Closing handle socket: %d\n", p_handle->socket);
+    close(p_handle->socket);
+  }
+
+  if (p_handle->read_buffer) Dowa_Free(p_handle->read_buffer);
+  if (p_handle->write_buffer) Dowa_Free(p_handle->write_buffer);
+
+  Dowa_Free(p_handle);
+}
+
+
+int32 Seobeo_Handle_Flush(Seobeo_Handle *p_handle)
+{
+  uint32 total = p_handle->write_buffer_len;
+  uint32 sent  = 0;
+
+  Seobeo_Log(SEOBEO_DEBUG, "Write buffer total: %d\n", p_handle->write_buffer_len);
+
+  while (sent < total)
+  {
+    if (p_handle->ssl)
+    {
+      int n = Seobeo_SSL_Write(p_handle, p_handle->write_buffer + sent, total - sent);
+      if (n < 0) return -1;
+      if (n == 0) return 0;  // would block
+      sent += (uint32)n;
+    }else
+    {
+      Seobeo_Log(SEOBEO_DEBUG, "Flushing socket: %d\n", p_handle->socket);
+      ssize_t n = write(
+        p_handle->socket,
+        p_handle->write_buffer + sent,
+        total - sent
+      );
+      if (n < 0) {
+        if (errno == EINTR)  continue;
+        if (errno == EAGAIN) return 1;
+        return -1;
+      }
+      sent += (uint32)n;
+    }
+  }
+
+  p_handle->write_buffer_len = 0;
+  return 0;
+}
+
+int32 Seobeo_Handle_Queue(Seobeo_Handle *p_handle, const uint8 *data, uint32 data_size)
+{
+  if (p_handle->write_buffer_len + data_size > p_handle->write_buffer_capacity)
+  {
+    int32 rc = Seobeo_Handle_Flush(p_handle);
+    if (rc < 0) return -1;
+    if (rc > 0) return 1;
+  }
+
+  if (data_size > p_handle->write_buffer_capacity)
+  {
+    uint32 offset = 0;
+    while (offset < data_size)
+    {
+      ssize_t n = write(p_handle->socket,
+                data + offset,
+                data_size - offset);
+      if (n==0)
+      {
+        // DEBUG
+        Seobeo_Log(SEOBEO_DEBUG, "Write offset: %d\n", offset);
+        break;
+      }
+      if (n < 0)
+      {
+        if (errno == EINTR || errno == EAGAIN)
+        {
+          // DEBUG
+          // printf("Partial write, returning early (offset=%d)\n", offset);
+          continue;
+        }
+        if (errno == EAGAIN) return 1;
+        return -1;
+      }
+      offset += (uint32)n;
+      // DEBUG
+      Seobeo_Log(SEOBEO_DEBUG, "Write completed - offset: %d, data_size: %d\n", offset, data_size);
+    }
+    // DEBUG
+    Seobeo_Log(SEOBEO_DEBUG, "Total bytes written: %d\n", offset);
+    return 0;
+  }
+
+  if (!p_handle)
+  {
+    Seobeo_Log(SEOBEO_ERROR, "p_handle is NULL before memcpy\n");
+    return -1;
+  }
+  
+  if (!p_handle->write_buffer)
+  {
+    Seobeo_Log(SEOBEO_ERROR, "p_handle->write_buffer is NULL (len=%u, size=%u)\n",
+            p_handle->write_buffer_len, data_size);
+    return -1;
+  }
+  
+  Seobeo_Log(SEOBEO_DEBUG, "memcpy -> dest=%p (write_buffer=%p + offset=%u), src=%p, size=%u\n",
+          p_handle->write_buffer + p_handle->write_buffer_len,
+          p_handle->write_buffer,
+          p_handle->write_buffer_len,
+          data,
+          data_size); 
+
+  memcpy(p_handle->write_buffer + p_handle->write_buffer_len,
+         data,
+         data_size);
+  p_handle->write_buffer_len += data_size;
+  return 0;
+}
+
+int32 Seobeo_Handle_Read(Seobeo_Handle *p_handle)
+{
+  int32 read_size;
+  if (!p_handle) return -1;
+
+  // How many bytes we can still read into the buffer
+  uint32 free_space = p_handle->read_buffer_capacity - p_handle->read_buffer_len;
+  if (free_space == 0)
+    return -1;
+
+  if (p_handle->ssl)
+  {
+    read_size = Seobeo_SSL_Read(p_handle, p_handle->read_buffer + p_handle->read_buffer_len, free_space);
+    if (read_size < 0) return read_size;  // -1 for error, -2 for closed
+    if (read_size == 0) return 0;  // would block
+  }
+  else
+  {
+    read_size = (int32)read(p_handle->socket,
+                            p_handle->read_buffer + p_handle->read_buffer_len,
+                            free_space);
+    if (read_size == 0) return -2; 
+    if (read_size < 0)
+    {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) return 0;
+      return -1;
+    }
+  }
+
+  p_handle->read_buffer_len += (uint32)read_size;
+  return read_size;
+}
+
+void Seobeo_Handle_Consume(Seobeo_Handle *p_handle, uint32 consumed)
+{
+  if (consumed >= p_handle->read_buffer_len)
+  {
+    p_handle->read_buffer_len = 0;
+    return;
+  }
+
+  // Slide remaining bytes to the front
+  memmove(
+    p_handle->read_buffer,
+    p_handle->read_buffer + consumed,
+    p_handle->read_buffer_len - consumed
+  );
+  p_handle->read_buffer_len -= consumed;
+}
+
+void *Seobeo_Get_IP4_Or_IP6(struct sockaddr *sa)
+{
+  if (sa->sa_family == AF_INET) 
+  {
+    return &(((struct sockaddr_in*)sa)->sin_addr);
+  }
+
+  return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}