#!/usr/bin/env python3
"""
Decode the flag from the timing-based ICMP channel in public/task.pcapng.
Requires scapy: pip install scapy
"""
from scapy.all import rdpcap, IP, ICMP  # type: ignore
import sys
from typing import Iterable, List


TARGET_IP = "192.168.13.37"
ZERO_DELAY = 1.2
ONE_DELAY = 1.6
TOLERANCE = 0.08  # Acceptable drift around each delay marker


def load_icmp_times(pcap_path: str) -> List[float]:
    packets = rdpcap(pcap_path)
    icmp_flow = [
        pkt
        for pkt in packets
        if IP in pkt
        and ICMP in pkt
        and (pkt[IP].src == TARGET_IP or pkt[IP].dst == TARGET_IP)
    ]
    icmp_flow.sort(key=lambda p: float(p.time))
    return [float(pkt.time) for pkt in icmp_flow]


def as_deltas(times: List[float]) -> List[float]:
    return [cur - prev for prev, cur in zip(times, times[1:])]


def decode_bits(deltas: Iterable[float]) -> str:
    bits = []
    for dt in deltas:
        if abs(dt - ZERO_DELAY) <= TOLERANCE:
            bits.append("0")
        elif abs(dt - ONE_DELAY) <= TOLERANCE:
            bits.append("1")
    return "".join(bits)


def bits_to_ascii(bits: str) -> str:
    # Drop trailing bits if not a whole byte
    usable_len = len(bits) - (len(bits) % 8)
    out = []
    for i in range(0, usable_len, 8):
        out.append(chr(int(bits[i : i + 8], 2)))
    return "".join(out)


def main() -> None:
    pcap_path = sys.argv[1] if len(sys.argv) > 1 else "../public/task.pcapng"
    times = load_icmp_times(pcap_path)
    bitstring = decode_bits(as_deltas(times))
    plaintext = bits_to_ascii(bitstring)
    print(plaintext)


if __name__ == "__main__":
    main()
