dropfield waited on a reply the firmware never sends (bare hf_field_off();break;) -> 2s stall + raise; use send_ng_no_response. hf.sniff under-sent the 10-byte {u32,u32,u8,u8} struct and mis-parsed the uint16 reply. hw.standalone sent an empty payload for a {arg,mlen,mode[10]} struct and blocked; pack it + fire-and-forget. Pre-existing, independent of rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
4.7 KiB
Python
116 lines
4.7 KiB
Python
import struct
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
from pm3py.core.protocol import Cmd
|
|
from pm3py.core.transport import PM3Response
|
|
from pm3py.core.hw import HWCommands
|
|
|
|
def make_ng_response(cmd: int, status: int, payload: bytes) -> PM3Response:
|
|
return PM3Response(cmd=cmd, status=status, reason=0, ng=True, data=payload)
|
|
|
|
def make_mock_transport():
|
|
transport = AsyncMock()
|
|
return transport
|
|
|
|
def _make_caps_response(is_rdv4=False):
|
|
"""Build a capabilities response with is_rdv4 flag."""
|
|
# version(1) + baudrate(4) + bigbuf_size(4) + 4 bytes of flags
|
|
flags = bytearray(4)
|
|
flags[0] = 0x80 # compiled_with_lf
|
|
if is_rdv4:
|
|
flags[3] = 0x02 # is_rdv4 at byte 12 bit 1
|
|
return make_ng_response(Cmd.CAPABILITIES, 0, struct.pack("<BII", 7, 115200, 40000) + bytes(flags))
|
|
|
|
def test_ping():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
ping_data = bytes(range(32))
|
|
transport.send_ng.return_value = make_ng_response(Cmd.PING, 0, ping_data)
|
|
result = asyncio.get_event_loop().run_until_complete(hw.ping(32))
|
|
assert result["success"] is True
|
|
assert result["length"] == 32
|
|
|
|
def test_version():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
# Build version response: id(4) + section_size(4) + versionstr_len(4) + versionstr
|
|
vstr = b"Proxmark3 OS\x00"
|
|
payload = struct.pack("<III", 0x270B0A40, 512*1024, len(vstr)) + vstr
|
|
transport.send_ng.return_value = make_ng_response(Cmd.VERSION, 0, payload)
|
|
result = asyncio.get_event_loop().run_until_complete(hw.version())
|
|
assert "chip_id" in result
|
|
assert "version_string" in result
|
|
|
|
def test_status():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
transport.send_ng.return_value = make_ng_response(Cmd.STATUS, 0, b"")
|
|
result = asyncio.get_event_loop().run_until_complete(hw.status())
|
|
assert result["status"] == 0
|
|
|
|
def test_led_on_by_name():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
hw._is_rdv4 = False # Easy: green=A=0x01
|
|
transport.send_ng.return_value = make_ng_response(Cmd.LED_CONTROL, 0, b"")
|
|
result = asyncio.get_event_loop().run_until_complete(hw.led("green", on=True))
|
|
payload = transport.send_ng.call_args[0][1]
|
|
led_mask = payload[0]
|
|
assert led_mask == 0x01 # green on Easy = A
|
|
|
|
def test_led_pulse():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
# pulse on "red" (C=0x04) — not PWM-capable on Easy, so pre-set as RDV4
|
|
hw._is_rdv4 = True # skip capabilities fetch; RDV4 has PWM on A,D only... but red=C
|
|
# Actually red=C is not PWM on either platform. Use "orange" (A) which is PWM on both.
|
|
transport.send_ng.return_value = make_ng_response(Cmd.LED_CONTROL, 0, b"")
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
hw.led("orange", pulse=True, speed=300, count=3))
|
|
# Verify the payload (last call to send_ng)
|
|
call_args = transport.send_ng.call_args
|
|
payload = call_args[0][1]
|
|
led_mask, action, brightness, speed, count = struct.unpack("<BBBHH", payload)
|
|
assert led_mask == 0x01 # orange = A
|
|
assert action == 4 # pulse
|
|
assert speed == 300
|
|
assert count == 3
|
|
|
|
def test_led_brightness():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
# Pre-cache as Easy — PWM capable on A,B only
|
|
hw._is_rdv4 = False
|
|
transport.send_ng.return_value = make_ng_response(Cmd.LED_CONTROL, 0, b"")
|
|
result = asyncio.get_event_loop().run_until_complete(hw.led("green", brightness=50))
|
|
payload = transport.send_ng.call_args[0][1]
|
|
led_mask, action, bright, _, _ = struct.unpack("<BBBHH", payload)
|
|
assert led_mask == 0x01 # green on Easy = A
|
|
assert action == 3 # pwm
|
|
assert bright == 50
|
|
|
|
def test_led_pwm_non_capable_raises():
|
|
"""PWM on a non-PWM LED should raise PM3Error."""
|
|
from pm3py.core.transport import PM3Error
|
|
import pytest
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
hw._is_rdv4 = False # Easy: only A,B support PWM
|
|
with pytest.raises(PM3Error, match="do not support"):
|
|
asyncio.get_event_loop().run_until_complete(
|
|
hw.led("blue", brightness=50)) # blue=D, not PWM on Easy
|
|
|
|
|
|
def test_standalone():
|
|
transport = make_mock_transport()
|
|
hw = HWCommands(transport)
|
|
result = asyncio.get_event_loop().run_until_complete(hw.standalone(arg=3, mode="14a"))
|
|
assert result["status"] == 0
|
|
# Firmware RunMod() never replies -> fire-and-forget
|
|
transport.send_ng_no_response.assert_awaited_once()
|
|
cmd, payload = transport.send_ng_no_response.call_args.args
|
|
assert cmd == Cmd.STANDALONE
|
|
# PACKED {uint8 arg; uint8 mlen; uint8 mode[10]} = 12 bytes
|
|
assert payload == struct.pack("<BB", 3, 3) + b"14a" + bytes(7)
|
|
transport.send_ng.assert_not_called()
|