"""Le um traffic.log do hook e devolve os pacotes ja recortados.

O hook escreve  [HH:MM:SS.mmm] RECV sock=0x... len=N  e a seguir o payload em hex.
Um bloco desses pode trazer VARIOS pacotes colados, porque o jogo le do socket em
rajadas -- por isso e preciso re-partir pelo prefixo de 4 bytes big-endian.
"""
import io, re

CAB = re.compile(r'^\[(\d{1,2}):(\d\d):(\d\d)\.(\d+)\]\s+(SEND|RECV)\s+sock=(\S+)\s+len=(\d+)')


def blocos(caminho):
    """(segundos_do_dia, direcao, sock, bytes) por bloco do log."""
    dir_ = sock = None
    t = None
    buf = []
    with io.open(caminho, 'r', encoding='latin-1', errors='replace') as fp:
        for linha in fp:
            linha = linha.rstrip('\r\n')
            m = CAB.match(linha)
            if m:
                if dir_ is not None and buf:
                    yield t, dir_, sock, bytes(buf)
                h, mi, s, ms, dir_, sock, _n = m.groups()
                t = int(h)*3600 + int(mi)*60 + int(s) + int(ms)/1000.0
                buf = []
                continue
            if dir_ is None:
                continue
            pedaco = linha.strip()
            if not pedaco or not all(c in '0123456789ABCDEFabcdef ' for c in pedaco):
                continue
            try:
                buf.extend(bytes.fromhex(pedaco.replace(' ', '')))
            except ValueError:
                pass
    if dir_ is not None and buf:
        yield t, dir_, sock, bytes(buf)


def pacotes(caminho, direcao='RECV'):
    """(segundos, opcode_2bytes, corpo_sem_prefixo) por pacote.

    COSE OS BLOCOS, e isso nao e detalhe. Um pacote grande atravessa varias chamadas ao
    socket: o inventario do oficial declara 4275 bytes e chegam 2490 no primeiro bloco. A
    versao anterior parava na fronteira e deitava fora o resto -- 9,8 por cento dos bytes de
    uma captura, em silencio. Com isso o 06 05 do oficial simplesmente NAO EXISTIA para
    quem lesse o log, e cheguei a concluir por escrito que ele nao mandava inventario.

    E a segunda vez que o mesmo descuido custa uma conclusao errada: antes tinha sido ler so
    o primeiro pacote de cada bloco. A sobra guarda-se POR SOCKET, porque ha mais do que uma
    ligacao no mesmo log e misturar as sobras corrompe as duas.
    """
    sobra = {}
    for t, d, sock, dados in blocos(caminho):
        if d != direcao:
            continue
        buf = sobra.pop(sock, b'') + dados
        i = 0
        n = len(buf)
        while i + 4 <= n:
            tam = int.from_bytes(buf[i:i+4], 'big')
            if tam <= 0 or tam > 1 << 20:
                i += 1                     # dessincronizado: anda um byte e tenta de novo
                continue
            if i + 4 + tam > n:
                break                      # falta corpo: fica para o bloco seguinte
            corpo = buf[i+4:i+4+tam]
            if len(corpo) >= 2:
                yield t, corpo[:2], corpo
            i += 4 + tam
        if i < n:
            sobra[sock] = buf[i:]
