Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rumpelsepp committed Jun 27, 2022
1 parent a86d67b commit 5c32849
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 111 deletions.
3 changes: 0 additions & 3 deletions docs/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ isotp://can0?src_addr=0x6f4&dst_addr=0x654&rx_ext_address=0xf4&ext_address=0x54&

## can-raw

* `src_addr` (required): The ISOTP source address as int.
* `dst_addr` (required): The ISOTP destination address as int.
* `is_extended` (optional): Use extended CAN identifiers.
* `is_fd` (optional): Use CAN-FD frames.

Example:
Expand Down
127 changes: 49 additions & 78 deletions src/gallia/transports/can.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,20 +265,12 @@ def unpack(cls, frame: bytes) -> CANMessage:
_CAN_RAW_SPEC_TYPE = TypedDict(
"_CAN_RAW_SPEC_TYPE",
{
"src_addr": Optional[int],
"dst_addr": Optional[int],
"is_extended": bool,
"is_fd": bool,
"bind": bool,
},
)

spec_can_raw = {
"src_addr": (_int_spec(None), False),
"dst_addr": (_int_spec(None), False),
"is_extended": (_bool_spec(False), False),
"is_fd": (_bool_spec(False), False),
"bind": (_bool_spec(False), False),
}


Expand All @@ -289,13 +281,10 @@ class RawCANTransport(BaseTransport, scheme="can-raw", spec=spec_can_raw):
def __init__(self, target: TargetURI) -> None:
super().__init__(target)
self.args = cast(_CAN_RAW_SPEC_TYPE, self._args)
self.connected = False

assert target.hostname is not None, "empty interface"
self.interface = target.hostname
self._sock: s.socket
self.src_addr: int
self.dst_addr: int

async def connect(self, timeout: Optional[float] = None) -> None:
self._sock = s.socket(s.PF_CAN, s.SOCK_RAW, s.CAN_RAW)
Expand All @@ -304,75 +293,25 @@ async def connect(self, timeout: Optional[float] = None) -> None:
self._sock.setsockopt(s.SOL_CAN_RAW, s.CAN_RAW_FD_FRAMES, 1)
self._sock.setblocking(False)

if self.args["bind"]:
self.bind()

def set_filter(self, can_ids: list[int], inv_filter: bool = False) -> None:
if not can_ids:
def set_filter(self, can_ids: list[dict], inv_filter: bool = False) -> None:
if len(can_ids) == 0:
return
filter_mask = s.CAN_EFF_MASK if self.args["is_extended"] else s.CAN_SFF_MASK
data = b""
for can_id in can_ids:
id_ = can_id["arbitration_id"]
filter_mask = s.CAN_EFF_MASK if can_id["is_extended_id"] else s.CAN_SFF_MASK
if inv_filter:
can_id |= self.CAN_INV_FILTER
data += struct.pack("@II", can_id, filter_mask)
id_ |= self.CAN_INV_FILTER
data += struct.pack("@II", id_, filter_mask)
self._sock.setsockopt(s.SOL_CAN_RAW, s.CAN_RAW_FILTER, data)
if inv_filter:
self._sock.setsockopt(s.SOL_CAN_RAW, s.CAN_RAW_JOIN_FILTERS, 1)

def bind(self) -> None:
if self.args["src_addr"] is None or self.args["dst_addr"] is None:
raise RuntimeError("no src_addr/dst_addr set")

self.set_filter([self.args["src_addr"]])
self.src_addr = self.args["src_addr"]
self.dst_addr = self.args["dst_addr"]
self.connected = True

async def read(
self, timeout: Optional[float] = None, tags: Optional[list[str]] = None
) -> bytes:
if not self.connected or not self.src_addr:
raise RuntimeError("transport is not connected; set bind=true")
_, data = await self.recvfrom(timeout, tags)
return data

async def write(
async def read_frame(
self,
data: bytes,
timeout: Optional[float] = None,
tags: Optional[list[str]] = None,
) -> int:
if not self.connected or not self.dst_addr:
raise RuntimeError("transport is not connected; set bind=true")
return await self.sendto(data, self.dst_addr, timeout=timeout, tags=tags)

async def sendto(
self,
data: bytes,
dst: int,
timeout: Optional[float] = None,
tags: Optional[list[str]] = None,
) -> int:
msg = CANMessage(
arbitration_id=dst,
data=data,
is_extended_id=self.args["is_extended"],
is_fd=self.args["is_fd"],
check=True,
)
if self.args["is_extended"]:
self.logger.log_write(f"{dst:08x}#{data.hex()}", tags=tags)
else:
self.logger.log_write(f"{dst:03x}#{data.hex()}", tags=tags)

loop = asyncio.get_running_loop()
await asyncio.wait_for(loop.sock_sendall(self._sock, msg.pack()), timeout)
return len(data)

async def recvfrom(
self, timeout: Optional[float] = None, tags: Optional[list[str]] = None
) -> tuple[int, bytes]:
) -> CANMessage:
loop = asyncio.get_running_loop()
can_frame = await asyncio.wait_for(
loop.sock_recv(self._sock, self.BUFSIZE), timeout
Expand All @@ -387,28 +326,60 @@ async def recvfrom(
self.logger.log_read(
f"{msg.arbitration_id:03x}#{msg.data.hex()}", tags=tags
)
return msg.arbitration_id, msg.data
return msg

async def write_frame(
self,
msg: CANMessage,
timeout: Optional[float] = None,
tags: Optional[list[str]] = None,
) -> None:
if msg.is_extended_id:
self.logger.log_write(f"{msg.arbitration_id:08x}#{msg.data.hex()}", tags=tags)
else:
self.logger.log_write(f"{msg.arbitration_id:03x}#{msg.data.hex()}", tags=tags)

loop = asyncio.get_running_loop()
await asyncio.wait_for(loop.sock_sendall(self._sock, msg.pack()), timeout)

async def read(
self,
timeout: Optional[float] = None,
tags: Optional[list[str]] = None,
) -> bytes:
pass

async def write(
self,
data: bytes,
timeout: Optional[float] = None,
tags: Optional[list[str]] = None,
) -> int:
pass

async def close(self) -> None:
pass

async def reconnect(self, timeout: Optional[float] = None) -> None:
pass

async def get_idle_traffic(self, sniff_time: float) -> list[int]:
async def get_idle_traffic(self, sniff_time: float) -> list[dict]:
"""Listen to traffic on the bus and return list of IDs
which are seen in the specified period of time.
The output of this function can be used as input to set_filter.
"""
addr_idle: list[int] = list()
idle_msgs: list[dict] = []
t1 = time.time()
while time.time() - t1 < sniff_time:
try:
addr, _ = await self.recvfrom(timeout=1)
if addr not in addr_idle:
self.logger.log_info(f"Received a message from {addr:03x}")
addr_idle.append(addr)
msg = await self.read_frame(timeout=1)
msg_dict = {
"is_extended_id": msg.is_extended_id,
"arbitration_id": msg.arbitration_id,
}
if msg_dict not in idle_msgs:
self.logger.log_info(f"Received a message {msg.arbitration_id}#{msg.data.hex()}")
idle_msgs.append(msg_dict)
except asyncio.TimeoutError:
continue
addr_idle.sort()
return addr_idle
return idle_msgs
46 changes: 27 additions & 19 deletions src/gallia/udscan/scanner/find_can_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Optional

from gallia.transports.base import TargetURI
from gallia.transports.can import ISOTPTransport, RawCANTransport
from gallia.transports.can import ISOTPTransport, RawCANTransport, CANMessage
from gallia.uds.core.service import UDSRequest, NegativeResponse
from gallia.uds.core.client import UDSClient
from gallia.udscan.core import DiscoveryScanner
Expand Down Expand Up @@ -51,6 +51,11 @@ def add_parser(self) -> None:
default=0.01,
help="set sleeptime between loop iterations",
)
self.parser.add_argument(
"--extended-ids",
action="store_true",
help="use extended can identifiers",
)
self.parser.add_argument(
"--extended-addr",
action="store_true",
Expand Down Expand Up @@ -106,7 +111,7 @@ async def query_description(self, target_list: list[TargetURI], did: int) -> Non
if isinstance(resp, NegativeResponse):
self.logger.log_summary(f"could not read did: {resp}")
else:
self.logger.log_summary(f"response was: {resp}")
self.logger.log_summary(f"response was: {resp.data_records[0]}")
except Exception as e:
self.logger.log_summary(f"reading description failed: {g_repr(e)}")

Expand Down Expand Up @@ -151,28 +156,33 @@ async def main(self, args: Namespace) -> None:

sniff_time: int = args.sniff_time
self.logger.log_summary(f"Recording idle bus communication for {sniff_time}s")
addr_idle = await transport.get_idle_traffic(sniff_time)
idle_msgs = await transport.get_idle_traffic(sniff_time)

self.logger.log_summary(f"Found {len(addr_idle)} CAN Addresses on idle Bus")
transport.set_filter(addr_idle, inv_filter=True)
self.logger.log_summary(f"Found {len(idle_msgs)} CAN Messages on idle Bus")
transport.set_filter(idle_msgs, inv_filter=True)

req = UDSRequest.parse_dynamic(args.pdu)
pdu = self.build_isotp_frame(req, padding=args.padding)

for ID in range(args.start, args.stop + 1):
await asyncio.sleep(args.sleep)

dst_addr = args.tester_addr if args.extended_addr else ID
arbitration_id = args.tester_addr if args.extended_addr else ID
if args.extended_addr:
pdu = self.build_isotp_frame(req, ID, padding=args.padding)

self.logger.log_info(f"Testing ID {can_id_repr(ID)}")
is_broadcast = False

await transport.sendto(pdu, timeout=0.1, dst=dst_addr)
msg = CANMessage(
arbitration_id=arbitration_id,
data=pdu,
is_extended_id=args.extended_ids,
)
await transport.write_frame(msg, timeout=0.1)
try:
addr, _ = await transport.recvfrom(timeout=0.1)
if addr == ID:
msg = await transport.read_frame(timeout=0.1)
if msg.arbitration_id == ID:
self.logger.log_info(
f"The same CAN ID {can_id_repr(ID)} answered. Skipping…"
)
Expand All @@ -184,12 +194,12 @@ async def main(self, args: Namespace) -> None:
# The recv buffer needs to be flushed to avoid
# wrong results...
try:
new_addr, _ = await transport.recvfrom(timeout=0.1)
if new_addr != addr:
new_msg = await transport.read_frame(timeout=0.1)
if new_msg.arbitration_id != msg.arbitration_id:
is_broadcast = True
self.logger.log_summary(
f"seems that broadcast was triggered on CAN ID {can_id_repr(ID)}, "
f"got answer from {can_id_repr(new_addr)}"
f"got answer from {can_id_repr(new_msg.arbitration_id)}"
)
else:
self.logger.log_info(
Expand All @@ -199,26 +209,24 @@ async def main(self, args: Namespace) -> None:
if is_broadcast:
self.logger.log_summary(
f"seems that broadcast was triggered on CAN ID {can_id_repr(ID)}, "
f"got answer from {can_id_repr(addr)}"
f"got answer from {can_id_repr(new_msg.arbitration_id)}"
)
else:
self.logger.log_summary(
f"found endpoint on CAN ID [src:dst]: {can_id_repr(ID)}:{can_id_repr(addr)}"
f"found endpoint [src:dst]: {can_id_repr(ID)}:{can_id_repr(msg.arbitration_id)}"
)
target_args = {}
target_args["is_fd"] = str(transport.args["is_fd"]).lower()
target_args["is_extended"] = str(
transport.args["is_extended"]
).lower()
target_args["is_extended"] = str(msg.is_extended_id).lower()

if args.extended_addr:
target_args["ext_address"] = hex(ID)
target_args["rx_ext_address"] = args.tester_addr & 0xFF
target_args["src_addr"] = args.tester_addr
target_args["dst_addr"] = hex(addr)
target_args["dst_addr"] = hex(msg.arbitration_id)
else:
target_args["src_addr"] = hex(ID)
target_args["dst_addr"] = hex(addr)
target_args["dst_addr"] = hex(msg.arbitration_id)

if args.padding is not None:
target_args["tx_padding"] = f"{args.padding}"
Expand Down
22 changes: 11 additions & 11 deletions src/gallia/udscan/scanner/find_xcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from gallia.penlog import Logger
from gallia.transports.base import TargetURI
from gallia.transports.can import RawCANTransport
from gallia.transports.can import RawCANTransport, CANMessage
from gallia.utils import bytes_repr, can_id_repr, g_repr


Expand Down Expand Up @@ -232,22 +232,22 @@ async def test_can(self, args: Namespace) -> None:
for can_id in range(0x800):
self.logger.log_info(f"Testing CAN ID: {can_id_repr(can_id)}")
pdu = bytes([0xFF, 0x00])
await transport.sendto(pdu, can_id, timeout=0.1)
msg = CANMessage(arbitration_id=can_id, data=pdu)
await transport.write_frame(msg, timeout=0.1)

try:
while True:
master, data = await transport.recvfrom(timeout=0.1)
if data[0] == 0xFF:
msg = (
f"Found XCP endpoint [master:slave]: CAN: {can_id_repr(master)}:{can_id_repr(can_id)} "
f"data: {bytes_repr(data)}"
msg = await transport.read_frame(timeout=0.1)
if msg.data[0] == 0xFF:
self.logger.log_summary(
f"Found endpoint [master:slave]: {can_id_repr(msg.arbitration_id)}:{can_id_repr(can_id)} "
f"data: {bytes_repr(msg.data)}"
)
self.logger.log_summary(msg)
endpoints.append((can_id, master))
endpoints.append((can_id, msg))
else:
self.logger.log_info(
f"Received non XCP answer for CAN-ID {can_id_repr(can_id)}: {can_id_repr(master)}:"
f"{bytes_repr(data)}"
f"Received non XCP answer on {can_id_repr(can_id)}: {can_id_repr(msg.arbitration_id)}:"
f"{bytes_repr(msg.data)}"
)
except asyncio.TimeoutError:
pass
Expand Down

0 comments on commit 5c32849

Please sign in to comment.