137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
# SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
import subprocess
|
|
import time
|
|
import os
|
|
import pytest
|
|
import signal
|
|
import pty
|
|
|
|
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
|
|
|
def test_j1939spy_usage(bin_path):
|
|
"""
|
|
Test usage/help output for j1939spy.
|
|
|
|
Manual Reproduction:
|
|
1. Run: ./j1939spy -h
|
|
2. Expect: Output containing "Usage: j1939spy".
|
|
"""
|
|
j1939spy = os.path.join(bin_path, "j1939spy")
|
|
if not os.path.exists(j1939spy):
|
|
pytest.skip("j1939spy binary not found")
|
|
|
|
result = subprocess.run(
|
|
[j1939spy, "-h"],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
|
|
assert "Usage: j1939spy" in result.stderr or "Usage: j1939spy" in result.stdout
|
|
|
|
def test_j1939spy_sniffing(bin_path, can_interface):
|
|
"""
|
|
Test j1939spy sniffing functionality.
|
|
|
|
Description:
|
|
1. Start j1939spy in promiscuous mode (-P) on the interface using PTY to avoid buffering.
|
|
2. Generate J1939 traffic using j1939sr (Source 0x90 -> Dest 0x80).
|
|
3. Verify that j1939spy captures and displays the traffic.
|
|
|
|
Manual Reproduction:
|
|
1. Spy: ./j1939spy -P vcan0
|
|
2. Transfer: echo "test" | ./j1939sr vcan0:90 vcan0:80
|
|
3. Check j1939spy output for "test" (hex: 74657374).
|
|
"""
|
|
j1939spy = os.path.join(bin_path, "j1939spy")
|
|
j1939sr = os.path.join(bin_path, "j1939sr")
|
|
|
|
for tool in [j1939spy, j1939sr]:
|
|
if not os.path.exists(tool):
|
|
pytest.skip(f"{tool} not found")
|
|
|
|
# 1. Start j1939spy
|
|
# Command: ./j1939spy -P vcan0
|
|
cmd_spy = [j1939spy, "-P", can_interface]
|
|
print(f"DEBUG: Starting spy: {' '.join(cmd_spy)}")
|
|
|
|
# Use PTY to force line buffering (simulates terminal behavior)
|
|
master_fd, slave_fd = pty.openpty()
|
|
|
|
spy_proc = subprocess.Popen(
|
|
cmd_spy,
|
|
stdout=slave_fd,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
os.close(slave_fd) # Close slave in parent
|
|
|
|
try:
|
|
# Allow spy to start
|
|
time.sleep(0.5)
|
|
if spy_proc.poll() is not None:
|
|
_, err = spy_proc.communicate()
|
|
pytest.fail(f"j1939spy failed to start. Stderr: {err}")
|
|
|
|
# 2. Generate Traffic
|
|
# Using j1939sr from SA 0x90 to SA 0x80
|
|
src_addr = f"{can_interface}:90"
|
|
dst_addr = f"{can_interface}:80"
|
|
payload_str = "test"
|
|
# Hex for "test" is 74 65 73 74. j1939spy usually prints hex.
|
|
|
|
cmd_sr = [j1939sr, src_addr, dst_addr]
|
|
print(f"DEBUG: Generating traffic: echo '{payload_str}' | {' '.join(cmd_sr)}")
|
|
|
|
sr_proc = subprocess.run(
|
|
cmd_sr,
|
|
input=payload_str + "\n",
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
|
|
if sr_proc.returncode != 0:
|
|
print(f"DEBUG: j1939sr stderr: {sr_proc.stderr}")
|
|
|
|
# 3. Verify capture
|
|
# Read from PTY
|
|
time.sleep(1.0)
|
|
|
|
output = ""
|
|
try:
|
|
# Read non-blocking or simple read since we know data should be there
|
|
# Using os.read on master_fd
|
|
while True:
|
|
# Basic non-blocking read approach or just one big read if buffer has data
|
|
# For test stability, just read a chunk.
|
|
chunk = os.read(master_fd, 4096).decode('utf-8', errors='replace')
|
|
if not chunk:
|
|
break
|
|
output += chunk
|
|
if "74657374" in output:
|
|
break
|
|
except OSError:
|
|
pass
|
|
|
|
print(f"DEBUG: j1939spy output:\n{output}")
|
|
|
|
# Expected output format from user description:
|
|
# vcan0:90,00000 80 !6 [5] 74657374 0a
|
|
# We look for the payload hex "74657374" (test)
|
|
expected_hex = "74657374"
|
|
|
|
assert expected_hex in output, f"j1939spy did not capture the payload hex '{expected_hex}'"
|
|
|
|
finally:
|
|
if spy_proc.poll() is None:
|
|
spy_proc.terminate()
|
|
try:
|
|
spy_proc.wait(timeout=1.0)
|
|
except subprocess.TimeoutExpired:
|
|
spy_proc.kill()
|
|
|
|
if master_fd:
|
|
os.close(master_fd)
|