171 lines
6.2 KiB
Python
171 lines
6.2 KiB
Python
# SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
import subprocess
|
|
import time
|
|
import os
|
|
import pytest
|
|
import signal
|
|
|
|
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
|
|
|
def test_isotp_usage(bin_path):
|
|
"""
|
|
Test usage/help output for isotpsend and isotprecv.
|
|
isotpsend returns error on -h.
|
|
isotprecv returns usage on missing args.
|
|
"""
|
|
isotpsend = os.path.join(bin_path, "isotpsend")
|
|
isotprecv = os.path.join(bin_path, "isotprecv")
|
|
|
|
if os.path.exists(isotpsend):
|
|
# isotpsend: invalid option -h
|
|
# Note: The tool might exit with 0 even on invalid options in some builds.
|
|
# We rely on checking the output text.
|
|
res_send = subprocess.run(
|
|
[isotpsend, "-h"],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
# We do not assert returncode != 0 here because it was observed to be 0
|
|
assert "Usage: isotpsend" in res_send.stderr or "Usage: isotpsend" in res_send.stdout
|
|
|
|
if os.path.exists(isotprecv):
|
|
# isotprecv: no args -> usage -> exit code != 0 (typically) or 0?
|
|
# Based on your output, it prints Usage. We check valid stderr/stdout output.
|
|
res_recv = subprocess.run(
|
|
[isotprecv],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
# We assume non-zero because usually tools requiring args exit with error
|
|
# checking the output text is the most important part.
|
|
assert "Usage: isotprecv" in res_recv.stderr or "Usage: isotprecv" in res_recv.stdout
|
|
|
|
def check_isotp_support(bin_path, interface):
|
|
"""
|
|
Helper to check if ISO-TP kernel module is loaded/available.
|
|
Tries to run isotpsend for a split second.
|
|
"""
|
|
isotpsend = os.path.join(bin_path, "isotpsend")
|
|
# Try to send a dummy frame. If socket creation fails, it exits with error.
|
|
# We use a timeout to ensure it doesn't hang.
|
|
try:
|
|
# -s 123 -d 321 are dummy IDs
|
|
proc = subprocess.Popen(
|
|
[isotpsend, "-s", "123", "-d", "321", interface],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
# Send empty or minimal data to close quickly if successful or fail if socket error
|
|
try:
|
|
outs, errs = proc.communicate(input="00", timeout=0.5)
|
|
except subprocess.TimeoutExpired:
|
|
proc.kill()
|
|
outs, errs = proc.communicate()
|
|
|
|
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
|
return False
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
@pytest.mark.parametrize("payload_hex", [
|
|
"AA BB CC", # Single Frame (< 8 bytes)
|
|
"00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE" # Multi Frame (> 8 bytes, triggers segmentation)
|
|
])
|
|
def test_isotp_transmission(bin_path, can_interface, payload_hex):
|
|
"""
|
|
Test ISO-TP transmission between isotpsend and isotprecv.
|
|
|
|
Description:
|
|
1. Start isotprecv (Receiver) in background.
|
|
It listens on RX_ID (e.g., 321) and sends Flow Control on TX_ID (e.g., 123).
|
|
2. Run isotpsend (Sender).
|
|
It sends on TX_ID (e.g., 123) and listens for Flow Control on RX_ID (e.g., 321).
|
|
3. Verify that isotprecv outputs exactly the data sent by isotpsend.
|
|
|
|
This verifies:
|
|
- Kernel ISO-TP stack (socket creation).
|
|
- Addressing (Source/Dest IDs).
|
|
- Segmentation/Reassembly (for long payloads).
|
|
"""
|
|
isotpsend = os.path.join(bin_path, "isotpsend")
|
|
isotprecv = os.path.join(bin_path, "isotprecv")
|
|
|
|
if not os.path.exists(isotpsend) or not os.path.exists(isotprecv):
|
|
pytest.skip("isotpsend or isotprecv binary not found.")
|
|
|
|
if not check_isotp_support(bin_path, can_interface):
|
|
pytest.skip("ISO-TP kernel support missing (socket failed). Load can-isotp module.")
|
|
|
|
# IDs for the connection
|
|
# Sender transmits with ID_A, Receiver listens to ID_A
|
|
ID_A = "123"
|
|
ID_B = "321"
|
|
|
|
# 1. Start Receiver (isotprecv)
|
|
# -s <src> -d <dst>
|
|
# In isotprecv context: -s is usually the ID it sends FC on, -d is ID it listens to?
|
|
# Actually, can-utils convention is often:
|
|
# isotprecv -s <tx_id> -d <rx_id>
|
|
# To listen to what isotpsend (-s 123 -d 321) sends:
|
|
# We need a socket that listens on 123 and writes on 321.
|
|
# So isotprecv should be: -s 321 -d 123
|
|
recv_cmd = [isotprecv, "-s", ID_B, "-d", ID_A, can_interface]
|
|
|
|
recv_proc = subprocess.Popen(
|
|
recv_cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
|
|
try:
|
|
time.sleep(0.5) # Wait for receiver to bind socket
|
|
|
|
# 2. Run Sender (isotpsend)
|
|
# -s 123 -d 321
|
|
send_cmd = [isotpsend, "-s", ID_A, "-d", ID_B, can_interface]
|
|
|
|
# Pass payload via stdin
|
|
send_proc = subprocess.run(
|
|
send_cmd,
|
|
input=payload_hex,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
timeout=2
|
|
)
|
|
|
|
assert send_proc.returncode == 0, f"isotpsend failed: {send_proc.stderr}"
|
|
|
|
# 3. Verify Receiver Output
|
|
# isotprecv prints the received PDU. It might not exit automatically unless we kill it
|
|
# OR if we used -l (loop). Without -l, it typically exits after one PDU.
|
|
# We wait briefly for it to finish.
|
|
try:
|
|
recv_stdout, recv_stderr = recv_proc.communicate(timeout=2)
|
|
except subprocess.TimeoutExpired:
|
|
recv_proc.terminate()
|
|
recv_stdout, recv_stderr = recv_proc.communicate()
|
|
|
|
# Check if the payload is in the output
|
|
# Output format is usually "AA BB CC ..."
|
|
# We normalize spaces for comparison
|
|
expected = " ".join(payload_hex.split())
|
|
actual = " ".join(recv_stdout.split())
|
|
|
|
print(f"--- Sender Stderr: ---\n{send_proc.stderr}")
|
|
print(f"--- Receiver Stdout: ---\n{recv_stdout}")
|
|
|
|
assert expected in actual, f"Receiver did not receive correct data. Expected '{expected}', got '{actual}'"
|
|
|
|
finally:
|
|
if recv_proc.poll() is None:
|
|
recv_proc.terminate()
|
|
recv_proc.wait()
|