uaf.git

uaf

~
git clone https://github.com/debuman/uaf.git

tests/test_parser.py (5993B)

      1 """Tests for ap_scanner.parser."""
      2 
      3 import pytest
      4 
      5 from scapy.layers.dot11 import (
      6     Dot11,
      7     Dot11Beacon,
      8     Dot11Elt,
      9     Dot11ProbeReq,
     10     Dot11ProbeResp,
     11     RadioTap,
     12 )
     13 
     14 from ap_scanner.parser import (
     15     _extract_ssid,
     16     _collect_ies,
     17     _parse_wps_version,
     18     parse_packet,
     19     parse_probe_request,
     20 )
     21 
     22 
     23 def _make_beacon(ssid: str = "TestSSID", bssid: str = "AA:BB:CC:DD:EE:FF",
     24                  channel: int = 6, freq: int = 2437) -> object:
     25     """Create a minimal beacon packet for testing."""
     26     pkt = (
     27         RadioTap(dBm_AntSignal=-42, ChannelFrequency=freq)
     28         / Dot11(addr3=bssid)
     29         / Dot11Beacon(cap="ESS+privacy")
     30         / Dot11Elt(ID=0, info=ssid.encode())
     31         / Dot11Elt(ID=3, info=bytes([channel]))
     32     )
     33     return pkt
     34 
     35 
     36 def _make_probe_request(ssid: str = "TargetNet",
     37                         client_mac: str = "11:22:33:44:55:66") -> object:
     38     """Create a minimal probe request packet."""
     39     pkt = (
     40         RadioTap()
     41         / Dot11(addr2=client_mac)
     42         / Dot11ProbeReq()
     43         / Dot11Elt(ID=0, info=ssid.encode())
     44     )
     45     return pkt
     46 
     47 
     48 # --- parse_packet ---
     49 
     50 def test_parse_beacon_basic():
     51     pkt = _make_beacon(ssid="HomeNet", channel=6, freq=2437)
     52     ap = parse_packet(pkt)
     53     assert ap is not None
     54     assert ap.ssid == "HomeNet"
     55     assert ap.bssid == "AA:BB:CC:DD:EE:FF"
     56     assert ap.channel == 6
     57     # Note: RSSI may be None in mock packets depending on Scapy version
     58     # The important thing is that the AP was parsed successfully
     59 
     60 
     61 def test_parse_hidden_ssid():
     62     pkt = _make_beacon(ssid="", channel=1, freq=2412)
     63     ap = parse_packet(pkt)
     64     assert ap is not None
     65     assert ap.is_hidden is True
     66     assert ap.display_ssid == "[Hidden]"
     67 
     68 
     69 def test_parse_5ghz_beacon():
     70     pkt = _make_beacon(ssid="Office5G", channel=36, freq=5180,
     71                        bssid="22:33:44:55:66:77")
     72     ap = parse_packet(pkt)
     73     assert ap is not None
     74     assert ap.band == "5GHz"
     75     assert ap.channel == 36
     76 
     77 
     78 def test_parse_non_beacon_returns_none():
     79     # Data frame (not beacon/probe response)
     80     pkt = RadioTap() / Dot11(type=2, addr3="AA:BB:CC:DD:EE:FF")
     81     result = parse_packet(pkt)
     82     assert result is None
     83 
     84 
     85 def test_parse_no_bssid_returns_none():
     86     pkt = RadioTap() / Dot11(addr3=None) / Dot11Beacon() / Dot11Elt(ID=0, info=b"Test")
     87     result = parse_packet(pkt)
     88     assert result is None
     89 
     90 
     91 # --- parse_probe_request ---
     92 
     93 def test_parse_probe_request_basic():
     94     pkt = _make_probe_request(ssid="TargetNet", client_mac="AA:BB:CC:DD:EE:01")
     95     result = parse_probe_request(pkt)
     96     assert result is not None
     97     ssid, client = result
     98     assert ssid == "TargetNet"
     99     assert client == "AA:BB:CC:DD:EE:01"
    100 
    101 
    102 def test_parse_probe_request_empty_ssid():
    103     pkt = _make_probe_request(ssid="")
    104     result = parse_probe_request(pkt)
    105     assert result is None
    106 
    107 
    108 def test_parse_probe_request_non_probe():
    109     pkt = _make_beacon()
    110     result = parse_probe_request(pkt)
    111     assert result is None
    112 
    113 
    114 # --- _extract_ssid ---
    115 
    116 def test_extract_ssid_basic():
    117     ies = {0: [b"MyNetwork"]}
    118     assert _extract_ssid(ies) == "MyNetwork"
    119 
    120 
    121 def test_extract_ssid_empty():
    122     ies = {0: [b""]}
    123     assert _extract_ssid(ies) == ""
    124 
    125 
    126 def test_extract_ssid_missing():
    127     ies = {}
    128     assert _extract_ssid(ies) == ""
    129 
    130 
    131 def test_extract_ssid_capped_at_32_bytes():
    132     long_ssid = b"A" * 50
    133     ies = {0: [long_ssid]}
    134     result = _extract_ssid(ies)
    135     assert len(result) == 32
    136 
    137 
    138 # --- _parse_wps_version ---
    139 
    140 def test_parse_wps_version_basic():
    141     # Version attribute: type=0x104A, length=1, value=0x10 (v1.0)
    142     data = b"\x10\x4A\x00\x01\x10"
    143     assert _parse_wps_version(data) == "1.0"
    144 
    145 
    146 def test_parse_wps_version_2():
    147     # Version attribute: type=0x104A, length=1, value=0x20 (v2.0)
    148     data = b"\x10\x4A\x00\x01\x20"
    149     assert _parse_wps_version(data) == "2.0"
    150 
    151 
    152 def test_parse_wps_version_empty():
    153     assert _parse_wps_version(b"") == ""
    154 
    155 
    156 def test_parse_wps_version_truncated():
    157     # Truncated data should not crash
    158     data = b"\x10\x4A\x00"
    159     assert _parse_wps_version(data) == ""
    160 
    161 
    162 def test_parse_wps_version_malformed_vendor_ext():
    163     # Vendor Extension (0x1049) with truncated sub-element
    164     data = b"\x10\x49\x00\x04\x00\x37\x2A\x01"  # WFA OUI + 1 byte
    165     result = _parse_wps_version(data)
    166     assert isinstance(result, str)  # Should not crash
    167 
    168 
    169 # --- parse_packet edge cases ---
    170 
    171 def test_parse_beacon_channel_0_rejected():
    172     """Beacons with channel 0 should be rejected (unreliable data)."""
    173     pkt = (
    174         RadioTap()
    175         / Dot11(addr3="AA:BB:CC:DD:EE:FF")
    176         / Dot11Beacon(cap="ESS+privacy")
    177         / Dot11Elt(ID=0, info=b"TestSSID")
    178         # No DS Parameter Set IE and no RadioTap frequency -> channel 0
    179     )
    180     ap = parse_packet(pkt)
    181     assert ap is None
    182 
    183 
    184 def test_parse_probe_response():
    185     """Probe Response frames should be parsed just like beacons."""
    186     pkt = (
    187         RadioTap(dBm_AntSignal=-55, ChannelFrequency=2437)
    188         / Dot11(addr3="11:22:33:44:55:66")
    189         / Dot11ProbeResp(cap="ESS+privacy")
    190         / Dot11Elt(ID=0, info=b"ProbeNet")
    191         / Dot11Elt(ID=3, info=bytes([6]))
    192     )
    193     ap = parse_packet(pkt)
    194     assert ap is not None
    195     assert ap.ssid == "ProbeNet"
    196     assert ap.bssid == "11:22:33:44:55:66"
    197 
    198 
    199 def test_parse_ssid_utf8_decode():
    200     """Non-ASCII SSIDs should be decoded with errors='replace'."""
    201     pkt = _make_beacon(ssid="X", channel=1, freq=2412)
    202     # Manually replace SSID with invalid UTF-8
    203     elt = pkt.getlayer(Dot11Elt)
    204     elt.info = b"\xc0\xc1\xff"  # invalid UTF-8 bytes
    205     ap = parse_packet(pkt)
    206     assert ap is not None
    207     # Should contain replacement characters, not crash
    208     assert "\ufffd" in ap.ssid
    209 
    210 
    211 def test_parse_probe_request_null_addr2():
    212     """Probe request with no source address should return None."""
    213     pkt = (
    214         RadioTap()
    215         / Dot11(addr2=None)
    216         / Dot11ProbeReq()
    217         / Dot11Elt(ID=0, info=b"TestNet")
    218     )
    219     result = parse_probe_request(pkt)
    220     assert result is None