#pragma once #include #include #include #include #include #include #include "prelude.h" /// file descriptor typedef int FD; /// Número de puerto en network byte order typedef uint16_t Port; /// /// Un buffer de bytes /// /// @note No controla la memoria: el puntero debe seguir siendo válido /// durante toda la vida del Buffer. typedef struct { Byte *buf; ///< Puntero al inicio del bloque size_t len; ///< Longitud en bytes } Buffer; /// /// Convierte una string en un Buffer /// /// El buffer resultante apunta directamente a @p str; no se copia memoria /// /// @param str un string terminado en null /// @return Buffer cuyo @c buf apunta a @p str y @c len es @c strlen(str) /// Buffer atob(Str str); /// Dirección IPv4. /// /// Permite acceder a la dirección byte a byte o como entero de 32 bits /// en network byte order. /// /// @example /// @code /// IPv4 loopback = { .bytes = {127, 0, 0, 1} }; /// @endcode typedef union { Byte bytes[4]; ///< Octetos individuales: {a, b, c, d} → a.b.c.d uint32_t ip; ///< Representación entera en network byte order. } IPv4; /// Socket TCP junto con la dirección que tiene asociada. /// /// Usado tanto en el lado cliente (después de @ref enchufa) como en el lado /// servidor (tanto en el socket de escucha como en las conexiones aceptadas). typedef struct { FD fd; ///< File descriptor del socket. struct sockaddr_in addr; ///< Dirección IP y puerto. socklen_t addrlen; ///< Tamaño de @c addr. } Enchufe; /// Dirección de red sin file descriptor asociado. /// /// Representa un extremo de conexión (IP + puerto) antes de que se le asigne /// un socket. Sirve como argumento intermedio para @ref aplasta. typedef struct { struct sockaddr_in addr; ///< Dirección IP y puerto. socklen_t addrlen; ///< Tamaño de @c addr. } Receptaculo; /// Crea un nuevo socket TCP. /// /// Llama a @c socket(PF_INET, SOCK_STREAM, 0). Termina el proceso con /// @c exit(1) si falla. /// /// @return File descriptor del socket recién creado. static inline FD nuevo(void) { FD fd = socket(PF_INET, SOCK_STREAM, 0); try(fd); return fd; } /// Construye un Receptaculo a partir de una dirección IP y un puerto. /// /// @param ip Dirección IPv4 en network byte order. /// @param port Puerto en network byte order (usa @c htons antes de pasar). /// @return Receptaculo listo para pasarse a @ref aplasta o @ref enchufa. static inline Receptaculo receptaculo(IPv4 ip, Port port) { struct sockaddr_in name = { .sin_family = AF_INET, .sin_port = port, .sin_addr = { .s_addr = ip.ip, }, }; return (Receptaculo){ .addr = name, .addrlen = sizeof(name), }; } /// Combina un file descriptor y un Receptaculo en un Enchufe. /// /// @param fd Socket creado con @ref nuevo. /// @param rec Dirección construida con @ref receptaculo. /// @return Enchufe que agrupa el fd y la dirección. static inline Enchufe aplasta(FD fd, Receptaculo rec) { return (Enchufe){ .fd = fd, .addr = rec.addr, .addrlen = rec.addrlen, }; } /// Crea un Enchufe listo para conectar o escuchar. /// /// Equivale a: @c aplasta(nuevo(), receptaculo(ip, port)). /// /// @param ip Dirección IPv4 en network byte order. /// @param port Puerto en network byte order (usa @c htons antes de pasar). /// @return Enchufe con un socket TCP nuevo y la dirección configurada. Enchufe enchufa(IPv4 ip, Port port); /// Conecta el socket a la dirección configurada en el Enchufe (lado cliente). /// /// Llama a @c connect(). Termina el proceso si la conexión falla. /// /// @param enchufe Enchufe creado con @ref enchufa apuntando al servidor. void conecta(Enchufe enchufe); /// Enlaza el socket a la dirección configurada (lado servidor). /// /// Llama a @c bind(). Termina el proceso si falla. /// Usa puerto 0 para dejar que el SO asigne un puerto libre. /// /// @param enchufe Enchufe creado con @ref enchufa con la dirección local. void amarra(Enchufe enchufe); /// Pone el socket en modo escucha (lado servidor). /// /// Llama a @c listen(). Debe llamarse después de @ref amarra. /// /// @param enchufe Enchufe previamente amarrado. /// @param len Tamaño de la cola de conexiones pendientes. void escucha(Enchufe enchufe, size_t len); /// Acepta una conexión entrante y devuelve el socket de la nueva conexión. /// /// Llama a @c accept(). Bloquea hasta que haya un cliente. Termina el proceso /// si falla. /// /// @param enchufe Enchufe en modo escucha (después de @ref escucha). /// @return Nuevo Enchufe asociado al cliente aceptado. Enchufe acepta(Enchufe enchufe); /// Envía todos los bytes del buffer a través del socket. /// /// Llama a @c write(). Termina el proceso si falla. /// /// @param enchufe Socket de destino. /// @param in_buf Datos a enviar. void zumba(Enchufe enchufe, Buffer in_buf); /// Lee bytes del socket en el buffer proporcionado. /// /// Llama a @c read(). Termina el proceso si falla. Devuelve 0 cuando el par /// ha cerrado la conexión. /// /// @param enchufe Socket del que leer. /// @param out_buf Buffer de destino; se lee como máximo @c out_buf.len bytes. /// @return Número de bytes leídos; 0 indica EOF (conexión cerrada). size_t recibe(Enchufe enchufe, Buffer out_buf); /// Cierra el socket. /// /// Llama a @c close(). Después de esta llamada el Enchufe no debe usarse. /// /// @param enchufe Socket a cerrar. void desenchufa(Enchufe enchufe);