all repos — rastro @ main

BitTorrent tracker!

tests/unit/test_bencode_udp.py (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
"""Bencode and UDP packet unit tests."""

from __future__ import annotations

import struct

import pytest
from app.probes.bencode import BencodeError, bdecode, validate_peers_then_discard
from app.probes.udp import (
    create_announce_request,
    create_connect_request,
    parse_announce_response,
    parse_connect_response,
)


def test_bencode_valid_and_invalid():
    # d8:intervali1800e5:peers6:\x01\x02\x03\x04\x00Pe
    peers = b"\x01\x02\x03\x04\x00\x50"
    payload = b"d8:intervali1800e5:peers6:" + peers + b"e"
    decoded = bdecode(payload)
    assert decoded["interval"] == 1800
    assert isinstance(decoded["peers"], list)
    assert validate_peers_then_discard(decoded) is True
    assert decoded["peers"] is None

    with pytest.raises(BencodeError):
        bdecode(b"")
    with pytest.raises(BencodeError):
        bdecode(b"i123")  # incomplete
    with pytest.raises(BencodeError):
        bdecode(b"4:spam")  # not a dict root for tracker helper — actually bdecode requires dict
        # 4:spam is a string root
    with pytest.raises(BencodeError):
        bdecode(b"l4:spame")


def test_udp_connect_and_announce_roundtrip_fields():
    req, tid = create_connect_request()
    assert len(req) == 16
    # forge response
    conn_id = 0x1122334455667788
    resp = struct.pack("!iiq", 0, tid, conn_id)
    assert parse_connect_response(resp, tid) == conn_id

    info = b"a" * 20
    peer = b"b" * 20
    ann, atid = create_announce_request(conn_id, info, peer, num_want=0)
    assert len(ann) == 98
    # announce response: action, tid, interval, leechers, seeders + fake peers
    aresp = struct.pack("!iiiii", 1, atid, 1800, 2, 5) + b"\x00" * 6
    interval, leechers, seeders = parse_announce_response(aresp, atid)
    assert interval == 1800
    assert leechers == 2
    assert seeders == 5


def test_udp_transaction_mismatch():
    resp = struct.pack("!iiq", 0, 123, 99)
    with pytest.raises(RuntimeError, match="transaction"):
        parse_connect_response(resp, 456)
    aresp = struct.pack("!iiiii", 1, 1, 10, 0, 0)
    with pytest.raises(RuntimeError, match="transaction"):
        parse_announce_response(aresp, 2)