|  |  |  | @ -236,10 +236,12 @@ STATIC_DSU_MSGS = [ | 
			
		
	
		
			
				
					|  |  |  |  | ] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | SHORT_FW_PATTERN = re.compile(b'(?P<platform>[A-Z0-9]{4})(?P<version>[A-Z0-9]{4})') | 
			
		
	
		
			
				
					|  |  |  |  | MED_PATTERN = re.compile(b'TODO') | 
			
		
	
		
			
				
					|  |  |  |  | FW_PATTERN = re.compile(b'(?P<part>[0-9A-Z]{4})[0-9A-Z](?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})') | 
			
		
	
		
			
				
					|  |  |  |  | MEDIUM_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<version>[A-Z0-9]{3})') | 
			
		
	
		
			
				
					|  |  |  |  | LONG_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})') | 
			
		
	
		
			
				
					|  |  |  |  | FW_LEN_CODE = re.compile(b'^[\x01-\x05]')  # 5 chunks max. max seen is 3 chunks, 16 bytes each | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import random | 
			
		
	
		
			
				
					|  |  |  |  | def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: | 
			
		
	
		
			
				
					|  |  |  |  |   # Returns unique, platform-specific identification codes for a set of versions | 
			
		
	
		
			
				
					|  |  |  |  |   codes = set()  # (code-Optional[part], date) | 
			
		
	
	
		
			
				
					|  |  |  | @ -247,44 +249,74 @@ def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[by | 
			
		
	
		
			
				
					|  |  |  |  |     # FW versions returned from UDS queries can return multiple fields/chunks of data (different ECU calibrations, different data?) | 
			
		
	
		
			
				
					|  |  |  |  |     #  and are prefixed with a byte that describes how many chunks of data there are. | 
			
		
	
		
			
				
					|  |  |  |  |     # But FW returned from KWP requires querying of each sub-data id and does not have a length prefix. | 
			
		
	
		
			
				
					|  |  |  |  |     has_n_chunks = fw[0] < 0xf  # max seen is 3 chunks, 16 bytes each | 
			
		
	
		
			
				
					|  |  |  |  |     n_chunks = 1 | 
			
		
	
		
			
				
					|  |  |  |  |     print(f'{has_n_chunks=}') | 
			
		
	
		
			
				
					|  |  |  |  |     if has_n_chunks: | 
			
		
	
		
			
				
					|  |  |  |  |       n_chunks = fw[0] | 
			
		
	
		
			
				
					|  |  |  |  |     # fw = bytearray(fw) | 
			
		
	
		
			
				
					|  |  |  |  |     # fw[0] = random.randint(0, 20) | 
			
		
	
		
			
				
					|  |  |  |  |     length_code = 1 | 
			
		
	
		
			
				
					|  |  |  |  |     length_code_match = FW_LEN_CODE.search(fw) | 
			
		
	
		
			
				
					|  |  |  |  |     # has_n_chunks = fw[0] <= 0x5  # max seen is 3 chunks, 16 bytes each | 
			
		
	
		
			
				
					|  |  |  |  |     # assert (length_code is not None) == bool(has_n_chunks), fw | 
			
		
	
		
			
				
					|  |  |  |  |     # continue | 
			
		
	
		
			
				
					|  |  |  |  |     # n_chunks = 1 | 
			
		
	
		
			
				
					|  |  |  |  |     # print(f'{has_n_chunks=}') | 
			
		
	
		
			
				
					|  |  |  |  |     if length_code_match is not None: | 
			
		
	
		
			
				
					|  |  |  |  |       # n_chunks = length_code.group()[0]  # fw[0] | 
			
		
	
		
			
				
					|  |  |  |  |       length_code = length_code_match.group()[0] | 
			
		
	
		
			
				
					|  |  |  |  |       fw = fw[1:] | 
			
		
	
		
			
				
					|  |  |  |  |       assert n_chunks * 16 == len(fw) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     chunks = [fw[16 * i:16 * i + 16] for i in range(n_chunks)] | 
			
		
	
		
			
				
					|  |  |  |  |     # chunks = [s for s in fw.split(b'\x00') if len(s)] | 
			
		
	
		
			
				
					|  |  |  |  |     # fw length should be multiple of 16 bytes (per chunk, even if no length code), skip parsing if unexpected length | 
			
		
	
		
			
				
					|  |  |  |  |     if length_code * 16 != len(fw): | 
			
		
	
		
			
				
					|  |  |  |  |       continue | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     chunks = [fw[16 * i:16 * i + 16] for i in range(length_code)] | 
			
		
	
		
			
				
					|  |  |  |  |     chunks = [c.strip(b'\x00 ') for c in chunks] | 
			
		
	
		
			
				
					|  |  |  |  |     # chunks = [s.strip(b'\x00 ') for s in fw.split(b'\x00') if len(s)] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     # Ensure not all empty bytes | 
			
		
	
		
			
				
					|  |  |  |  |     # TODO: needed since we use | 
			
		
	
		
			
				
					|  |  |  |  |     if not len(chunks): | 
			
		
	
		
			
				
					|  |  |  |  |       continue | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     # if chunks not in ([b'896634A13000', b''], [b'896634A23000', b'']): | 
			
		
	
		
			
				
					|  |  |  |  |     #   assert chunks == chunks_new, (chunks, chunks_new) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     a = list(map(len, chunks)) | 
			
		
	
		
			
				
					|  |  |  |  |     print(fw, chunks, a) | 
			
		
	
		
			
				
					|  |  |  |  |     # assert len(set(a)) == 1 | 
			
		
	
		
			
				
					|  |  |  |  |     # assert len(set(a)) == 1, (a, fw) | 
			
		
	
		
			
				
					|  |  |  |  |     # only first is considered for now since second is commonly shared (TODO: understand that) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     first_chunk = chunks[0] | 
			
		
	
		
			
				
					|  |  |  |  |     # doesn't have a part encoded in version (OBD query?) | 
			
		
	
		
			
				
					|  |  |  |  |     # short_version = sum(b > 0xf for b in first_chunk) == 8 | 
			
		
	
		
			
				
					|  |  |  |  |     short_version = len(first_chunk) == 8 | 
			
		
	
		
			
				
					|  |  |  |  |     if short_version: | 
			
		
	
		
			
				
					|  |  |  |  |     # short_version = len(first_chunk) == 8 | 
			
		
	
		
			
				
					|  |  |  |  |     if len(first_chunk) == 8: | 
			
		
	
		
			
				
					|  |  |  |  |       # TODO: some short chunks have the part number in subsequent chunks | 
			
		
	
		
			
				
					|  |  |  |  |       print('short version') | 
			
		
	
		
			
				
					|  |  |  |  |       code_match = SHORT_FW_PATTERN.search(first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       if code_match is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         code, version = code_match.groups() | 
			
		
	
		
			
				
					|  |  |  |  |         print('platform code, version', code, version) | 
			
		
	
		
			
				
					|  |  |  |  |         platform, version = code_match.groups() | 
			
		
	
		
			
				
					|  |  |  |  |         print('platform code, version', platform, version) | 
			
		
	
		
			
				
					|  |  |  |  |         codes.add((platform, version)) | 
			
		
	
		
			
				
					|  |  |  |  |     elif len(first_chunk) == 10: | 
			
		
	
		
			
				
					|  |  |  |  |       code_match = MEDIUM_FW_PATTERN.search(first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       if code_match is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         # TODO: platform is a loose term here | 
			
		
	
		
			
				
					|  |  |  |  |         part, platform, version = code_match.groups() | 
			
		
	
		
			
				
					|  |  |  |  |         codes.add((part + b'-' + platform, version)) | 
			
		
	
		
			
				
					|  |  |  |  |       print('not done', first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       # not done | 
			
		
	
		
			
				
					|  |  |  |  |       pass | 
			
		
	
		
			
				
					|  |  |  |  |     elif len(first_chunk) == 12: | 
			
		
	
		
			
				
					|  |  |  |  |       print(FW_PATTERN) | 
			
		
	
		
			
				
					|  |  |  |  |       code_match = FW_PATTERN.search(first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       print(LONG_FW_PATTERN) | 
			
		
	
		
			
				
					|  |  |  |  |       print('long, searching', first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       code_match = LONG_FW_PATTERN.search(first_chunk) | 
			
		
	
		
			
				
					|  |  |  |  |       if code_match is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         print('got long match!') | 
			
		
	
		
			
				
					|  |  |  |  |         part, platform, major_version, sub_version = code_match.groups() | 
			
		
	
		
			
				
					|  |  |  |  |         print(first_chunk, code_match, code_match.groups()) | 
			
		
	
		
			
				
					|  |  |  |  |         codes.add((part + b'-' + platform, major_version + b'-' + sub_version)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     else: | 
			
		
	
		
			
				
					|  |  |  |  |       assert False, f'invalid length: {len(first_chunk)}' | 
			
		
	
		
			
				
					|  |  |  |  |     # else: | 
			
		
	
		
			
				
					|  |  |  |  |     #   assert False, f'invalid length: {len(first_chunk)}' | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     continue | 
			
		
	
	
		
			
				
					|  |  |  | @ -405,8 +437,7 @@ FW_QUERY_CONFIG = FwQueryConfig( | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | # FW_PATTERN = re.compile(b'[0-9]{4}[0-9A-Z][0-9A-Z]') | 
			
		
	
		
			
				
					|  |  |  |  | # FW_PATTERN2 = re.compile(br'(?<=\\x[0-9]{2})[0-9A-Z]{5}|^[0-9A-Z]{5}') | 
			
		
	
		
			
				
					|  |  |  |  | FW_LEN_CODE = re.compile(b'^[\x00-\x0F]') | 
			
		
	
		
			
				
					|  |  |  |  | FW_PATTERN_V3 = re.compile(b'(?P<length>^[\x00-\x0F])?(?P<part>[0-9A-Z]{4})') | 
			
		
	
		
			
				
					|  |  |  |  | # FW_PATTERN_V3 = re.compile(b'(?P<length>^[\x00-\x0F])?(?P<part>[0-9A-Z]{4})') | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | FW_VERSIONS = { | 
			
		
	
		
			
				
					|  |  |  |  |   CAR.AVALON: { | 
			
		
	
	
		
			
				
					|  |  |  | 
 |