feat: migrate sim framework from worktree to master
Migrates all 43 sim modules and 23 test files (~7.8k LOC, 686 tests) from .worktrees/sim-framework/ into pm3py/sim/. Import fixes: - 4 files: ..protocol/..transport → ..core.protocol/..core.transport - trace_fmt.py: pm3py.hf_15 → pm3py.trace.ndef - test_sim_pm3medium.py: flat imports → core.* - test_sim_trace_fmt.py: flat imports → core.* - Added sim Cmd entries to core/protocol.py (EML_SETMEM, SIM_TABLE_*) 751 tests passing (686 sim + 65 core). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
160
tests/test_sim_access_control.py
Normal file
160
tests/test_sim_access_control.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Tests for access control I/O: credentials, Wiegand, OSDP."""
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
from pm3py.sim.access_control.credential import (
|
||||
Credential, encode_wiegand, decode_wiegand, from_uid,
|
||||
)
|
||||
from pm3py.sim.access_control.wiegand import (
|
||||
WiegandOutput, WiegandInput,
|
||||
)
|
||||
from pm3py.sim.access_control.osdp import (
|
||||
OSDPFrame, OSDPChannel, osdp_crc16,
|
||||
)
|
||||
|
||||
|
||||
def run(coro):
|
||||
return asyncio.get_event_loop().run_until_complete(coro)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Credential model
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCredential:
|
||||
def test_construction(self):
|
||||
c = Credential(facility_code=42, card_number=12345, format="H10301")
|
||||
assert c.facility_code == 42
|
||||
assert c.card_number == 12345
|
||||
assert c.format == "H10301"
|
||||
|
||||
def test_equality(self):
|
||||
c1 = Credential(42, 12345, "H10301")
|
||||
c2 = Credential(42, 12345, "H10301")
|
||||
assert c1 == c2
|
||||
|
||||
def test_from_uid_derives_credential(self):
|
||||
"""Derive a credential from a tag UID."""
|
||||
cred = from_uid(b"\x01\x02\x03\x04", format="H10301")
|
||||
assert cred.facility_code >= 0
|
||||
assert cred.card_number >= 0
|
||||
assert cred.format == "H10301"
|
||||
|
||||
|
||||
class TestWiegandEncoding:
|
||||
def test_encode_26bit(self):
|
||||
bits = encode_wiegand(Credential(42, 12345, "H10301"))
|
||||
assert len(bits) == 26
|
||||
|
||||
def test_decode_26bit(self):
|
||||
cred = Credential(42, 12345, "H10301")
|
||||
bits = encode_wiegand(cred)
|
||||
decoded = decode_wiegand(bits, "H10301")
|
||||
assert decoded.facility_code == 42
|
||||
assert decoded.card_number == 12345
|
||||
|
||||
def test_encode_37bit(self):
|
||||
bits = encode_wiegand(Credential(1000, 50000, "H10304"))
|
||||
assert len(bits) == 37
|
||||
|
||||
def test_decode_37bit_roundtrip(self):
|
||||
cred = Credential(1000, 50000, "H10304")
|
||||
bits = encode_wiegand(cred)
|
||||
decoded = decode_wiegand(bits, "H10304")
|
||||
assert decoded == cred
|
||||
|
||||
def test_26bit_parity(self):
|
||||
bits = encode_wiegand(Credential(42, 12345, "H10301"))
|
||||
assert sum(bits[:13]) % 2 == 0 # even parity first half
|
||||
assert sum(bits[13:]) % 2 == 1 # odd parity second half
|
||||
|
||||
def test_encode_35bit_corp1000(self):
|
||||
bits = encode_wiegand(Credential(100, 5000, "C1000_35"))
|
||||
assert len(bits) == 35
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wiegand I/O (software mode)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestWiegandOutput:
|
||||
def test_software_mode_encode(self):
|
||||
"""WiegandOutput in software mode stores bit stream."""
|
||||
out = WiegandOutput()
|
||||
cred = Credential(42, 12345, "H10301")
|
||||
out.send(cred)
|
||||
assert len(out.last_bits) == 26
|
||||
|
||||
def test_software_mode_timing(self):
|
||||
"""Verify pulse timing parameters exist."""
|
||||
out = WiegandOutput(pulse_width_us=50, interval_us=2000)
|
||||
assert out.pulse_width_us == 50
|
||||
assert out.interval_us == 2000
|
||||
|
||||
|
||||
class TestWiegandInput:
|
||||
def test_software_mode_decode(self):
|
||||
"""WiegandInput in software mode decodes bit stream."""
|
||||
inp = WiegandInput()
|
||||
cred = Credential(42, 12345, "H10301")
|
||||
bits = encode_wiegand(cred)
|
||||
decoded = inp.decode(bits, "H10301")
|
||||
assert decoded.facility_code == 42
|
||||
assert decoded.card_number == 12345
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OSDP protocol
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestOSDPFrame:
|
||||
def test_frame_construction(self):
|
||||
frame = OSDPFrame(address=0, command=0x60, data=b"")
|
||||
raw = frame.encode()
|
||||
assert raw[0] == 0x53 # SOM
|
||||
assert len(raw) >= 7 # SOM + ADDR + LEN(2) + CMD + CRC(2)
|
||||
|
||||
def test_frame_parse_roundtrip(self):
|
||||
frame = OSDPFrame(address=1, command=0x61, data=b"\x01\x02")
|
||||
raw = frame.encode()
|
||||
parsed = OSDPFrame.decode(raw)
|
||||
assert parsed.address == 1
|
||||
assert parsed.command == 0x61
|
||||
assert parsed.data == b"\x01\x02"
|
||||
|
||||
def test_crc16(self):
|
||||
"""OSDP uses CRC-16/AUG-CCITT."""
|
||||
crc = osdp_crc16(b"\x53\x00\x08\x00\x60")
|
||||
assert isinstance(crc, int)
|
||||
assert 0 <= crc <= 0xFFFF
|
||||
|
||||
|
||||
class TestOSDPChannel:
|
||||
def test_software_loopback(self):
|
||||
"""Channel in software mode: controller polls, reader replies."""
|
||||
channel = OSDPChannel() # software loopback
|
||||
|
||||
# Controller sends POLL
|
||||
poll = OSDPFrame(address=0, command=0x60, data=b"")
|
||||
channel.send(poll)
|
||||
|
||||
# Reader responds with ACK
|
||||
ack = OSDPFrame(address=0, command=0x40, data=b"")
|
||||
channel.send(ack)
|
||||
|
||||
# Both frames in buffer
|
||||
assert len(channel.buffer) == 2
|
||||
|
||||
def test_card_present_notification(self):
|
||||
"""Simulate card present event via OSDP."""
|
||||
channel = OSDPChannel()
|
||||
cred = Credential(42, 12345, "H10301")
|
||||
bits = encode_wiegand(cred)
|
||||
|
||||
# Reader sends RAW (card data) — cmd 0x73
|
||||
raw_data = bytes(bits) # simplified
|
||||
frame = OSDPFrame(address=0, command=0x73, data=raw_data)
|
||||
channel.send(frame)
|
||||
|
||||
last = channel.buffer[-1]
|
||||
assert last.command == 0x73
|
||||
Reference in New Issue
Block a user