// layers.c: abre una captura y muestra el tamaño de cada capa de cada paquete.
//
//   ./layers <captura.pcap>
//
// Lee el formato clásico de libpcap byte a byte, sin copiar structs desde el
// archivo: los encabezados de red tienen un orden de bytes y un formato definidos,
// y una struct de C no garantiza ninguno de los dos. Entiende capturas de Ethernet
// y de Linux cooked capture (SLL y SLL2), que es lo que produce tcpdump -i any.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define PCAP_HEADER_SIZE 24
#define RECORD_HEADER_SIZE 16
#define LINKTYPE_ETHERNET 1
#define LINKTYPE_SLL 113
#define LINKTYPE_SLL2 276
#define ETHERTYPE_IPV4 0x0800
#define IP_PROTO_ICMP 1
#define IP_PROTO_TCP 6
#define IP_PROTO_UDP 17
#define TCP_MIN_CAPTURED 14 // hasta el byte de flags
#define UDP_HEADER_SIZE 8
#define ICMP_HEADER_SIZE 8

// Los campos de red van con el byte más significativo primero.
static uint32_t read_be16(const uint8_t *p) {
    return (uint32_t)p[0] << 8 | p[1];
}

// El encabezado de la captura usa el orden de bytes de quien la escribió.
static uint32_t read_u32(const uint8_t *p, int big_endian) {
    if (big_endian) {
        return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3];
    }
    return (uint32_t)p[3] << 24 | (uint32_t)p[2] << 16 | (uint32_t)p[1] << 8 | p[0];
}

// Tamaño del encabezado de enlace y posición del campo que dice qué lleva dentro.
static int link_layout(uint32_t linktype, size_t *size, size_t *ethertype_at) {
    switch (linktype) {
    case LINKTYPE_ETHERNET:
        *size = 14;
        *ethertype_at = 12;
        return 0;
    case LINKTYPE_SLL:
        *size = 16;
        *ethertype_at = 14;
        return 0;
    case LINKTYPE_SLL2:
        *size = 20;
        *ethertype_at = 0;
        return 0;
    default:
        return -1;
    }
}

// Imprime los flags de TCP más comunes, en una sola palabra.
static void print_tcp_flags(uint8_t flags) {
    printf(" [");
    if (flags & 0x02) putchar('S');
    if (flags & 0x01) putchar('F');
    if (flags & 0x04) putchar('R');
    if (flags & 0x08) putchar('P');
    if (flags & 0x10) putchar('A');
    putchar(']');
}

// Muestra el contenido de un paquete IPv4: `ip` apunta a su primer byte.
static void print_ipv4(const uint8_t *ip, size_t available) {
    if (available < 20) {
        printf(" paquete IPv4 recortado\n");
        return;
    }
    size_t ip_size = (size_t)(ip[0] & 0x0f) * 4;
    size_t total = read_be16(ip + 2);
    uint8_t protocol = ip[9];
    if (ip_size < 20 || total < ip_size || available < ip_size) {
        printf(" ip=%zu mal formado\n", ip_size);
        return;
    }
    const uint8_t *transport = ip + ip_size;
    size_t transport_available = available - ip_size;
    size_t transport_size;
    const char *name;
    switch (protocol) {
    case IP_PROTO_TCP:
        if (transport_available < TCP_MIN_CAPTURED) {
            printf(" ip=%zu tcp recortado\n", ip_size);
            return;
        }
        name = "tcp";
        transport_size = (size_t)(transport[12] >> 4) * 4;
        break;
    case IP_PROTO_UDP:
        name = "udp";
        transport_size = UDP_HEADER_SIZE;
        break;
    case IP_PROTO_ICMP:
        name = "icmp";
        transport_size = ICMP_HEADER_SIZE;
        break;
    default:
        printf(" ip=%zu protocolo=%u\n", ip_size, protocol);
        return;
    }
    // El largo de los datos sale de IP, no de la captura: un frame puede traer
    // relleno al final, y ese relleno no es de la aplicación.
    if (total < ip_size + transport_size) {
        printf(" ip=%zu %s mal formado\n", ip_size, name);
        return;
    }
    printf(" ip=%zu %s=%zu app=%zu ttl=%u %u.%u.%u.%u > %u.%u.%u.%u", ip_size, name, transport_size,
           total - ip_size - transport_size, ip[8], ip[12], ip[13], ip[14], ip[15], ip[16], ip[17],
           ip[18], ip[19]);
    if (protocol == IP_PROTO_TCP) {
        print_tcp_flags(transport[13]);
    }
    putchar('\n');
}

// Lee exactamente n bytes. Devuelve 1 si pudo, 0 si el archivo terminó justo
// antes, y termina el programa si quedó cortado a la mitad o hubo un error.
static int read_exact(FILE *file, void *buffer, size_t n) {
    size_t got = fread(buffer, 1, n, file);
    if (got == n) {
        return 1;
    }
    if (ferror(file)) {
        perror("layers: fread");
        exit(EXIT_FAILURE);
    }
    if (got == 0) {
        return 0;
    }
    fprintf(stderr, "layers: la captura termina a la mitad de un registro\n");
    exit(EXIT_FAILURE);
}

// Abre el archivo y lee su encabezado. Deja el tipo de enlace y el orden de bytes.
static FILE *open_capture(const char *path, uint32_t *linktype, int *big_endian) {
    FILE *file = fopen(path, "rb");
    if (file == NULL) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    uint8_t header[PCAP_HEADER_SIZE];
    if (!read_exact(file, header, sizeof header)) {
        fprintf(stderr, "layers: %s está vacío\n", path);
        exit(EXIT_FAILURE);
    }
    uint32_t magic = read_u32(header, 0);
    if (magic == 0xa1b2c3d4 || magic == 0xa1b23c4d) {
        *big_endian = 0;
    } else if (magic == 0xd4c3b2a1 || magic == 0x4d3cb2a1) {
        *big_endian = 1;
    } else {
        fprintf(stderr, "layers: %s no es una captura pcap clásica\n", path);
        exit(EXIT_FAILURE);
    }
    *linktype = read_u32(header + 20, *big_endian);
    return file;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "uso: %s <captura.pcap>\n", argv[0]);
        return EXIT_FAILURE;
    }
    uint32_t linktype;
    int big_endian;
    FILE *file = open_capture(argv[1], &linktype, &big_endian);

    size_t link_size, ethertype_at;
    if (link_layout(linktype, &link_size, &ethertype_at) != 0) {
        fprintf(stderr, "layers: tipo de enlace %u no soportado\n", linktype);
        fclose(file);
        return EXIT_FAILURE;
    }

    uint8_t record[RECORD_HEADER_SIZE];
    uint8_t *packet = NULL;
    size_t capacity = 0;
    long number = 0;
    while (read_exact(file, record, sizeof record)) {
        size_t captured = read_u32(record + 8, big_endian);
        size_t on_wire = read_u32(record + 12, big_endian);
        if (captured > capacity) {
            uint8_t *bigger = realloc(packet, captured);
            if (bigger == NULL) {
                perror("layers: realloc");
                free(packet);
                fclose(file);
                return EXIT_FAILURE;
            }
            packet = bigger;
            capacity = captured;
        }
        if (captured > 0 && !read_exact(file, packet, captured)) {
            fprintf(stderr, "layers: la captura termina a la mitad de un paquete\n");
            free(packet);
            fclose(file);
            return EXIT_FAILURE;
        }
        number++;
        printf("%4ld total=%-5zu enlace=%zu", number, on_wire, link_size);
        if (captured < link_size) {
            printf(" recortado\n");
        } else if (read_be16(packet + ethertype_at) == ETHERTYPE_IPV4) {
            print_ipv4(packet + link_size, captured - link_size);
        } else {
            printf(" no es IPv4 (ethertype 0x%04x)\n", read_be16(packet + ethertype_at));
        }
    }
    free(packet);
    if (fclose(file) != 0) {
        perror("layers: fclose");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
