|  |  |  | @ -359,16 +359,62 @@ def parse_platform_code(fw_version: bytes) -> Tuple[Optional[bytes], Optional[by | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def hyundai_fuzzy(fw_versions_dict) -> Set[str]: | 
			
		
	
		
			
				
					|  |  |  |  |   all_platform_codes = defaultdict(set) | 
			
		
	
		
			
				
					|  |  |  |  |   """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 | 
			
		
	
		
			
				
					|  |  |  |  |     the match is rejected.""" | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # Build lookup table from (addr, sub_addr, fw) to list of candidate cars | 
			
		
	
		
			
				
					|  |  |  |  |   all_fw_versions = defaultdict(list) | 
			
		
	
		
			
				
					|  |  |  |  |   # Platform codes are brand-specific unique identifiers for each platform, less specific than a FW version | 
			
		
	
		
			
				
					|  |  |  |  |   all_platform_codes = defaultdict(list) | 
			
		
	
		
			
				
					|  |  |  |  |   for candidate, fw_by_addr in FW_VERSIONS.items(): | 
			
		
	
		
			
				
					|  |  |  |  |     if candidate == exclude: | 
			
		
	
		
			
				
					|  |  |  |  |       continue | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     for addr, fws in fw_by_addr.items(): | 
			
		
	
		
			
				
					|  |  |  |  |       if addr[0] not in FW_QUERY_CONFIG.platform_code_ecus: | 
			
		
	
		
			
				
					|  |  |  |  |         continue | 
			
		
	
		
			
				
					|  |  |  |  |       # These ECUs are known to be shared between models (EPS only between hybrid/ICE version) | 
			
		
	
		
			
				
					|  |  |  |  |       # Getting this exactly right isn't crucial, but excluding camera and radar makes it almost | 
			
		
	
		
			
				
					|  |  |  |  |       # impossible to get 3 matching versions, even if two models with shared parts are released at the same | 
			
		
	
		
			
				
					|  |  |  |  |       # time and only one is in our database. | 
			
		
	
		
			
				
					|  |  |  |  |       if addr[0] not in FUZZY_EXCLUDE_ECUS: | 
			
		
	
		
			
				
					|  |  |  |  |         for f in fws: | 
			
		
	
		
			
				
					|  |  |  |  |           all_fw_versions[(addr[1], addr[2], f)].append(candidate) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       # Add platform codes to lookup dict if config specifies a function | 
			
		
	
		
			
				
					|  |  |  |  |       if addr[0] in config.platform_code_ecus and config.fuzzy_get_platform_codes is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         for platform_code in config.fuzzy_get_platform_codes(fws): | 
			
		
	
		
			
				
					|  |  |  |  |           all_platform_codes[(addr[1], addr[2], platform_code)].append(candidate) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   matched_ecus = set() | 
			
		
	
		
			
				
					|  |  |  |  |   candidate = None | 
			
		
	
		
			
				
					|  |  |  |  |   for addr, versions in fw_versions_dict.items(): | 
			
		
	
		
			
				
					|  |  |  |  |     ecu_key = (addr[0], addr[1]) | 
			
		
	
		
			
				
					|  |  |  |  |     for version in versions: | 
			
		
	
		
			
				
					|  |  |  |  |       # All cars that have this FW response on the specified address | 
			
		
	
		
			
				
					|  |  |  |  |       candidates = all_fw_versions[(*ecu_key, version)] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       # If not one candidate for this ECU and version, try platform codes + dates | 
			
		
	
		
			
				
					|  |  |  |  |       if len(candidates) != 1 and config.fuzzy_get_platform_codes is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         # Returns one or none, all cars that have this platform code | 
			
		
	
		
			
				
					|  |  |  |  |         for platform_code in config.fuzzy_get_platform_codes([version]): | 
			
		
	
		
			
				
					|  |  |  |  |           candidates = all_platform_codes[(*ecu_key, platform_code)] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       if len(candidates) == 1: | 
			
		
	
		
			
				
					|  |  |  |  |         matched_ecus.add(ecu_key) | 
			
		
	
		
			
				
					|  |  |  |  |         if candidate is None: | 
			
		
	
		
			
				
					|  |  |  |  |           candidate = candidates[0] | 
			
		
	
		
			
				
					|  |  |  |  |         # We uniquely matched two different cars. No fuzzy match possible | 
			
		
	
		
			
				
					|  |  |  |  |         elif candidate != candidates[0]: | 
			
		
	
		
			
				
					|  |  |  |  |           return set() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       for fw in fws: | 
			
		
	
		
			
				
					|  |  |  |  |         code, date = parse_platform_code(fw) | 
			
		
	
		
			
				
					|  |  |  |  |         if code is not None: | 
			
		
	
		
			
				
					|  |  |  |  |           all_platform_codes[(addr[1], addr[2], code)].add(candidate) | 
			
		
	
		
			
				
					|  |  |  |  |   # Note that it is possible to match to a candidate without all its ECUs being present | 
			
		
	
		
			
				
					|  |  |  |  |   # if there are enough matches. FIXME: parameterize this or require all ECUs to exist like exact matching | 
			
		
	
		
			
				
					|  |  |  |  |   if len(matched_ecus) >= 2: | 
			
		
	
		
			
				
					|  |  |  |  |     if log: | 
			
		
	
		
			
				
					|  |  |  |  |       cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {len(matched_ecus)} matching ECUs") | 
			
		
	
		
			
				
					|  |  |  |  |     return {candidate} | 
			
		
	
		
			
				
					|  |  |  |  |   else: | 
			
		
	
		
			
				
					|  |  |  |  |     return set() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | 
 |