Mercurial
view seobeo/s_network.c @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 609d3c6aff4e |
| children |
line wrap: on
line source
#include "seobeo/seobeo.h" static pthread_once_t g_sigpipe_once = PTHREAD_ONCE_INIT; static void Seobeo_Process_Ignore_Sigpipe(void) { signal(SIGPIPE, SIG_IGN); } static void Seobeo_Socket_Disable_Sigpipe(int socket_fd) { #ifdef SO_NOSIGPIPE int enabled = 1; setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)); #else (void)socket_fd; #endif } static void Seobeo_Socket_Set_Close_On_Exec(int socket_fd) { int flags = fcntl(socket_fd, F_GETFD, 0); if (flags >= 0) fcntl(socket_fd, F_SETFD, flags | FD_CLOEXEC); } static ssize_t Seobeo_Socket_Write( int socket_fd, const void *buffer, size_t length) { #ifdef MSG_NOSIGNAL return send(socket_fd, buffer, length, MSG_NOSIGNAL); #else return send(socket_fd, buffer, length, 0); #endif } Seobeo_Handle *Seobeo_Stream_Handle_Server_Create(const char *host, const char* port) { pthread_once(&g_sigpipe_once, Seobeo_Process_Ignore_Sigpipe); Seobeo_Handle *p_handle; struct addrinfo hints, *server_infos, *free_server_info; int32 socket_fd = -1; int32 yes = 1; 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 ) { int socket_type = free_server_info->ai_socktype; #ifdef SOCK_CLOEXEC socket_type |= SOCK_CLOEXEC; #endif if((socket_fd = socket(free_server_info->ai_family, socket_type, free_server_info->ai_protocol)) == -1) { perror("socket"); continue; } Seobeo_Socket_Set_Close_On_Exec(socket_fd); Seobeo_Socket_Disable_Sigpipe(socket_fd); if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { perror("setsockopt SO_REUSEADDR"); close(socket_fd); socket_fd = -1; 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"); close(socket_fd); socket_fd = -1; continue; } #endif if (bind(socket_fd, free_server_info->ai_addr, free_server_info->ai_addrlen) == -1) { perror("bind"); close(socket_fd); socket_fd = -1; continue; } break; } freeaddrinfo(server_infos); if (socket_fd < 0) return NULL; 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 (flags < 0 || fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0) { perror("fcntl"); close(socket_fd); return NULL; } p_handle = calloc(1, sizeof(*p_handle)); if (!p_handle) { close(socket_fd); return NULL; } p_handle->socket = socket_fd; p_handle->type = SEOBEO_STREAM_TYPE_SERVER; p_handle->connected = FALSE; p_handle->host = strdup(host != NULL ? 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; p_handle->is_sse = FALSE; if (!p_handle->host || !p_handle->port || !p_handle->read_buffer || !p_handle->write_buffer) { Seobeo_Handle_Destroy(p_handle); return NULL; } return p_handle; } Seobeo_Handle *Seobeo_Stream_Handle_Client_Create(const char *host, const char* port, boolean use_tls) { pthread_once(&g_sigpipe_once, Seobeo_Process_Ignore_Sigpipe); 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; } int socket_type = server_infos->ai_socktype; #ifdef SOCK_CLOEXEC socket_type |= SOCK_CLOEXEC; #endif if((socket_fd = socket(server_infos->ai_family, socket_type, server_infos->ai_protocol)) == -1) { perror("socket"); return NULL; } Seobeo_Socket_Set_Close_On_Exec(socket_fd); Seobeo_Socket_Disable_Sigpipe(socket_fd); if (connect(socket_fd, server_infos->ai_addr, server_infos->ai_addrlen) != 0) { perror("connect"); return NULL; } freeaddrinfo(server_infos); // Set non-blocking int flags = fcntl(socket_fd, F_GETFL, 0); if (flags == -1 || fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0) { perror("fcntl"); close(socket_fd); free(p_handle); return NULL; } 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; p_handle->is_sse = 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; Seobeo_Socket_Set_Close_On_Exec(client_fd); Seobeo_Socket_Disable_Sigpipe(client_fd); // 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; p_client_handle->is_sse = 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; } Seobeo_SSE_Server_Detach_Handle(p_handle); 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 >= 0) { 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) { if (!p_handle || !p_handle->write_buffer) return -1; 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) { if (sent) { memmove( p_handle->write_buffer, p_handle->write_buffer + sent, total - sent); p_handle->write_buffer_len = total - sent; } return -1; } if (n == 0) { if (sent) { memmove( p_handle->write_buffer, p_handle->write_buffer + sent, total - sent); p_handle->write_buffer_len = total - sent; } return 1; } sent += (uint32)n; }else { Seobeo_Log(SEOBEO_DEBUG, "Flushing socket: %d\n", p_handle->socket); ssize_t n = Seobeo_Socket_Write( p_handle->socket, p_handle->write_buffer + sent, total - sent ); if (n < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { if (sent) { memmove( p_handle->write_buffer, p_handle->write_buffer + sent, total - sent); p_handle->write_buffer_len = total - sent; } return 1; } if (sent) { memmove( p_handle->write_buffer, p_handle->write_buffer + sent, total - sent); p_handle->write_buffer_len = total - sent; } 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; if (p_handle->ssl) { n = Seobeo_SSL_Write(p_handle, data + offset, data_size - offset); } else { n = Seobeo_Socket_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); }