#define _POSIX_C_SOURCE 200809L
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define PAYLOAD 1024
#define HEADER 7
#define DATA 1
#define ACK 2
#define END 3
#define ATTEMPTS 5
#define TIMEOUT 1000

static int64_t
now_ms(void)
{
    struct timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1;
    return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

static int
wait_packet(int fd, int64_t deadline)
{
    struct pollfd pfd = {fd, POLLIN, 0};
    for (;;) {
        int64_t now = now_ms();
        int result;
        if (now < 0) return -1;
        if (now >= deadline) return 0;
        result = poll(&pfd, 1, (int)(deadline - now));
        if (result < 0 && errno == EINTR) continue;
        if (result <= 0) return result;
        if (pfd.revents & POLLIN) return 1;
        errno = EIO;
        return -1;
    }
}

static int
send_packet(int fd, const unsigned char *packet, size_t size, uint32_t seq)
{
    int attempt;
    for (attempt = 0; attempt < ATTEMPTS; ++attempt) {
        int64_t deadline;
        ssize_t n;
        do {
            n = send(fd, packet, size, 0);
        } while (n < 0 && errno == EINTR);
        if (n < 0) return -1;
        if ((size_t)n != size) { errno = EIO; return -1; }
        deadline = now_ms();
        if (deadline < 0) return -1;
        deadline += TIMEOUT;
        for (;;) {
            unsigned char reply[HEADER + 1];
            uint32_t received;
            int result = wait_packet(fd, deadline);
            if (result < 0) return -1;
            if (result == 0) break;
            do {
                n = recv(fd, reply, sizeof(reply), 0);
            } while (n < 0 && errno == EINTR);
            if (n < 0) return -1;
            if (n != HEADER || reply[0] != ACK || reply[5] || reply[6])
                continue;
            memcpy(&received, reply + 1, sizeof(received));
            if (ntohl(received) == seq) return 0;
        }
    }
    errno = ETIMEDOUT;
    return -1;
}

static int
connect_receiver(const char *host, const char *port)
{
    struct addrinfo hints = {0}, *addresses, *addr;
    int fd = -1, result, saved = EHOSTUNREACH;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_NUMERICSERV;
    result = getaddrinfo(host, port, &hints, &addresses);
    if (result != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
        return -1;
    }
    for (addr = addresses; addr != NULL; addr = addr->ai_next) {
        do {
            fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
            if (fd < 0) { saved = errno; break; }
            if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) break;
            saved = errno;
            close(fd);
            fd = -1;
        } while (saved == EINTR);
        if (fd >= 0) break;
    }
    freeaddrinfo(addresses);
    if (fd < 0) { errno = saved; perror("connect"); }
    return fd;
}

int
main(int argc, char **argv)
{
    unsigned char packet[HEADER + PAYLOAD];
    uint32_t seq = 0;
    char *end;
    long port;
    int input, fd, status = 1;

    if (argc != 4) {
        fprintf(stderr, "usage: %s host port input\n", argv[0]);
        return 1;
    }
    errno = 0;
    port = strtol(argv[2], &end, 10);
    if (errno || !*argv[2] || *end || port < 1 || port > 65535) {
        fprintf(stderr, "invalid port: %s\n", argv[2]);
        return 1;
    }
    input = open(argv[3], O_RDONLY);
    if (input < 0) { perror(argv[3]); return 1; }
    fd = connect_receiver(argv[1], argv[2]);
    if (fd < 0) { close(input); return 1; }
    for (;;) {
        size_t used = 0;
        uint32_t wire_seq = htonl(seq);
        uint16_t wire_len;
        while (used < PAYLOAD) {
            ssize_t n = read(input, packet + HEADER + used, PAYLOAD - used);
            if (n < 0 && errno == EINTR) continue;
            if (n < 0) { perror("read"); goto done; }
            if (n == 0) break;
            used += (size_t)n;
        }
        if (used > 0 && seq == UINT32_MAX) {
            fprintf(stderr, "file exceeds sequence number limit\n");
            break;
        }
        packet[0] = used == 0 ? END : DATA;
        wire_len = htons((uint16_t)used);
        memcpy(packet + 1, &wire_seq, sizeof(wire_seq));
        memcpy(packet + 5, &wire_len, sizeof(wire_len));
        if (send_packet(fd, packet, HEADER + used, seq) < 0) {
            perror("transfer");
            break;
        }
        if (used == 0) { status = 0; break; }
        ++seq;
    }
done:
    close(fd);
    if (close(input) < 0) { perror("close input"); status = 1; }
    return status;
}
