// Muestra que send puede escribir menos bytes de los pedidos. Conecta un socket
// a un receptor que nunca lee, hasta que se llenan los buffers.
#define _POSIX_C_SOURCE 200809L

#include "io_util.h"

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#define REQUEST (8 * 1024 * 1024)

// Una conexión por loopback; el otro extremo queda en la cola de accept.
static int connect_to_self(int listener) {
    struct sockaddr_in addr;
    socklen_t len = sizeof addr;
    if (getsockname(listener, (struct sockaddr *)&addr, &len) == -1) {
        perror("getsockname");
        return -1;
    }
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");
        return -1;
    }
    if (connect(fd, (struct sockaddr *)&addr, len) == -1) {
        perror("connect");
        close_checked(fd);
        return -1;
    }
    return fd;
}

static void report(const char *mode, ssize_t sent) {
    if (sent == -1) {
        printf("%s: send devuelve -1, errno = %s\n", mode, strerror(errno));
    } else {
        printf("%s: se pidieron %d bytes y send devuelve %zd\n", mode, REQUEST, sent);
    }
}

int main(void) {
    char *buf = calloc(REQUEST, 1);
    if (buf == NULL) {
        perror("calloc");
        return EXIT_FAILURE;
    }
    int listener = tcp_listen(0);
    if (listener == -1) {
        return EXIT_FAILURE;
    }

    // Caso 1: socket no bloqueante. send copia lo que cabe y regresa.
    int fd = connect_to_self(listener);
    if (fd == -1) {
        return EXIT_FAILURE;
    }
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl");
        return EXIT_FAILURE;
    }
    report("no bloqueante, 1.ª llamada", send(fd, buf, REQUEST, MSG_NOSIGNAL));
    report("no bloqueante, 2.ª llamada", send(fd, buf, REQUEST, MSG_NOSIGNAL));
    close_checked(fd);

    // Caso 2: socket bloqueante con plazo. Se agota el plazo a la mitad.
    fd = connect_to_self(listener);
    if (fd == -1) {
        return EXIT_FAILURE;
    }
    struct timeval limit = {.tv_sec = 0, .tv_usec = 200000};
    if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &limit, sizeof limit) == -1) {
        perror("setsockopt");
        return EXIT_FAILURE;
    }
    report("bloqueante con SO_SNDTIMEO", send(fd, buf, REQUEST, MSG_NOSIGNAL));
    close_checked(fd);

    close_checked(listener);
    free(buf);
    return EXIT_SUCCESS;
}
