Skip to content

firmware fingerprinting: order brand requests #23311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
Jul 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
71dcc34
eliminate brands based on ECUs that respond to tester present
gregjhogan Dec 26, 2021
43bc6a6
make it work
gregjhogan Dec 26, 2021
5d0a470
Add type hint for can message
sshane Jun 1, 2022
ad40d47
Only query for addresses in fingerprints, and account for different b…
sshane Jun 1, 2022
55b837a
These need to be addresses, not response addresses
sshane Jun 1, 2022
8e54436
We need to listen to response addresses, not query addresses
sshane Jun 1, 2022
e981add
add to files_common
sshane Jun 1, 2022
03ec08c
Unused Optional
sshane Jun 1, 2022
10d68a0
add logging
sshane Jun 1, 2022
250a89c
only query essential ecus
sshane Jun 2, 2022
6fea1b1
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jun 4, 2022
2ee55ae
simplify get_brand_candidates(), keep track of multiple request varia…
sshane Jun 6, 2022
5793a1e
fixes
sshane Jun 6, 2022
c4ffd70
(addr, subaddr, bus) can be common across brands, add a match to each…
sshane Jun 6, 2022
c445b98
fix length
sshane Jun 6, 2022
48e9651
query subaddrs in sequence
sshane Jun 7, 2022
a240774
fix
sshane Jun 7, 2022
9cad5c2
candidate if a platform is a subset of responding ecu addresses
sshane Jun 7, 2022
08d2058
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jun 16, 2022
5a77da3
do logging for shadow mode
sshane Jun 16, 2022
e539787
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jun 20, 2022
947f9de
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 5, 2022
dee85a4
query fw using most likely brands first, break when match
sshane Jul 5, 2022
82f6012
fix crash
sshane Jul 6, 2022
ff782b1
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 7, 2022
0e09112
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 8, 2022
9abf7f9
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 8, 2022
7a1a237
clean up
sshane Jul 8, 2022
09d0c7f
add brand filtering for debug script
sshane Jul 8, 2022
ac97188
no cache
sshane Jul 8, 2022
f87b809
fix that
sshane Jul 8, 2022
4a96ad3
fixes
sshane Jul 8, 2022
3a2b635
same name
sshane Jul 8, 2022
0ea9749
this likely isn't needed, we vin and get tester present responses bef…
sshane Jul 8, 2022
b53be8b
cache
sshane Jul 8, 2022
a742300
fix type annotation
sshane Jul 8, 2022
345bef9
no enumerate
sshane Jul 8, 2022
a969049
fix
sshane Jul 8, 2022
e2c5109
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 8, 2022
420e173
make sure matching function checks for multiple matches across brands
sshane Jul 8, 2022
15c57a3
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 8, 2022
428581c
this can be the same again
sshane Jul 8, 2022
3976923
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 9, 2022
4651349
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 9, 2022
ab03234
Merge remote-tracking branch 'upstream/master' into fast-fw-fp
sshane Jul 9, 2022
b87c3a0
clean up some functions/names
sshane Jul 9, 2022
af9ac2e
self explanetory with rework
sshane Jul 9, 2022
572f1b7
names
sshane Jul 9, 2022
f6936cc
better ordering of func vars, fix typing
sshane Jul 9, 2022
2b311cc
better yet
sshane Jul 9, 2022
27eeb6e
unused dict
sshane Jul 9, 2022
3720950
more accurate comment
sshane Jul 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions selfdrive/car/car_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from selfdrive.car.vin import get_vin, VIN_UNKNOWN
from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car, get_present_ecus
from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus
from system.swaglog import cloudlog
import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint
Expand Down Expand Up @@ -99,7 +99,7 @@ def fingerprint(logcan, sendcan):
cloudlog.warning("Getting VIN & FW versions")
_, vin = get_vin(logcan, sendcan, bus)
ecu_rx_addrs = get_present_ecus(logcan, sendcan)
car_fw = get_fw_versions(logcan, sendcan)
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs)

exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
else:
Expand Down
75 changes: 60 additions & 15 deletions selfdrive/car/fw_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ def build_fw_dict(fw_versions, filter_brand=None):
return fw_versions_dict


def get_brand_addrs():
versions = get_interface_attr('FW_VERSIONS', ignore_none=True)
brand_addrs = defaultdict(set)
for brand, cars in versions.items():
for fw in cars.values():
brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()}
return brand_addrs


def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
"""Do a fuzzy FW match. This function will return a match, and the number of firmware version
that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars
Expand All @@ -236,7 +245,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
# time and only one is in our database.
exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug]

# Build lookup table from (addr, subaddr, fw) to list of candidate cars
# Build lookup table from (addr, sub_addr, fw) to list of candidate cars
all_fw_versions = defaultdict(list)
for candidate, fw_by_addr in FW_VERSIONS.items():
if candidate == exclude:
Expand Down Expand Up @@ -361,24 +370,59 @@ def get_present_ecus(logcan, sendcan):
return ecu_responses


def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progress=False):
ecu_types = {}
def get_brand_ecu_matches(ecu_rx_addrs):
"""Returns dictionary of brands and matches with ECUs in their FW versions"""

# Extract ECU addresses to query from fingerprints
# ECUs using a subaddress need be queried one by one, the rest can be done in parallel
addrs = []
parallel_addrs = []
brand_addrs = get_brand_addrs()
brand_matches = {r.brand: set() for r in REQUESTS}

brand_rx_offsets = set((r.brand, r.rx_offset) for r in REQUESTS)
for addr, sub_addr, _ in ecu_rx_addrs:
# Since we can't know what request an ecu responded to, add matches for all possible rx offsets
for brand, rx_offset in brand_rx_offsets:
a = (uds.get_rx_addr_for_tx_addr(addr, -rx_offset), sub_addr)
if a in brand_addrs[brand]:
brand_matches[brand].add(a)

return brand_matches


def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=False, progress=False):
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""

all_car_fw = []
brand_matches = get_brand_ecu_matches(ecu_rx_addrs)

for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True):
car_fw = get_fw_versions(logcan, sendcan, brand=brand, timeout=timeout, debug=debug, progress=progress)
all_car_fw.extend(car_fw)
matches = match_fw_to_car_exact(build_fw_dict(car_fw))
if len(matches) == 1:
break

return all_car_fw


def get_fw_versions(logcan, sendcan, brand=None, extra=None, timeout=0.1, debug=False, progress=False):
versions = get_interface_attr('FW_VERSIONS', ignore_none=True)
if brand is not None:
versions = {brand: versions[brand]}

if extra is not None:
versions.update(extra)

# Extract ECU addresses to query from fingerprints
# ECUs using a subaddress need be queried one by one, the rest can be done in parallel
addrs = []
parallel_addrs = []
ecu_types = {}

for brand, brand_versions in versions.items():
for c in brand_versions.values():
for ecu_type, addr, sub_addr in c.keys():
a = (brand, addr, sub_addr)
if a not in ecu_types:
ecu_types[(addr, sub_addr)] = ecu_type
ecu_types[a] = ecu_type

if sub_addr is None:
if a not in parallel_addrs:
Expand All @@ -390,17 +434,17 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
addrs.insert(0, parallel_addrs)

fw_versions = {}
for i, addr in enumerate(tqdm(addrs, disable=not progress)):
requests = [r for r in REQUESTS if brand is None or r.brand == brand]
for addr in tqdm(addrs, disable=not progress):
for addr_chunk in chunks(addr):
for r in REQUESTS:
for r in requests:
try:
addrs = [(a, s) for (b, a, s) in addr_chunk if b in (r.brand, 'any') and
(len(r.whitelist_ecus) == 0 or ecu_types[(a, s)] in r.whitelist_ecus)]
(len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)]

if addrs:
query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug)
t = 2 * timeout if i == 0 else timeout
fw_versions.update({(r.brand, addr): (version, r) for addr, version in query.get_data(t).items()})
fw_versions.update({(r.brand, addr): (version, r) for addr, version in query.get_data(timeout).items()})
except Exception:
cloudlog.warning(f"FW query exception: {traceback.format_exc()}")

Expand All @@ -409,7 +453,7 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
for (brand, addr), (version, request) in fw_versions.items():
f = car.CarParams.CarFw.new_message()

f.ecu = ecu_types[addr]
f.ecu = ecu_types[(brand, addr[0], addr[1])]
f.fwVersion = version
f.address = addr[0]
f.responseAddress = uds.get_rx_addr_for_tx_addr(addr[0], request.rx_offset)
Expand All @@ -433,6 +477,7 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
parser = argparse.ArgumentParser(description='Get firmware version of ECUs')
parser.add_argument('--scan', action='store_true')
parser.add_argument('--debug', action='store_true')
parser.add_argument('--brand', help='Only query addresses/with requests for this brand')
args = parser.parse_args()

logcan = messaging.sub_sock('can')
Expand All @@ -458,7 +503,7 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
print()

t = time.time()
fw_vers = get_fw_versions(logcan, sendcan, extra=extra, debug=args.debug, progress=True)
fw_vers = get_fw_versions(logcan, sendcan, brand=args.brand, extra=extra, debug=args.debug, progress=True)
_, candidates = match_fw_to_car(fw_vers)

print()
Expand Down