view seobeo/s_linux_network.c @ 71:75de5903355c

Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
author June Park <parkjune1995@gmail.com>
date Sun, 28 Dec 2025 20:34:22 -0800
parents 6626ec933933
children 70401cf61e97
line wrap: on
line source

#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)
  { 
    printf("Closing: %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;

  bool 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) {
    printf("Closing: %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;

  printf("Total: %d\n\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
    {
      printf("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
        printf("NONE %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
      printf("\n\noffset: %d data_size: %d\n\n", offset, data_size);
    }
    // DEBUG
    printf("\n\nTotal: %d\n", offset);
    return 0;
  }

  if (!p_handle)
  {
    printf("[ERROR] p_handle is NULL before memcpy\n");
    return -1;
  }
  
  if (!p_handle->write_buffer)
  {
    printf("[ERROR] p_handle->write_buffer is NULL (len=%zu, size=%zu)\n",
            p_handle->write_buffer_len, data_size);
    return -1;
  }
  
  printf("[DEBUG] memcpy -> dest=%p (write_buffer=%p + offset=%zu), src=%p, size=%zu\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);
}