chronaxis.execution.can_block_codec
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""普通 CAN 有界数据块;只恢复固定记录,绝不加载任意 Python 对象。23CND1 头为 4 字节版本和网络序 uint32 主体长度。主体先存字符串表,再按4固定字段依次存块、来源和原帧。字符串只在本块共享;原数值保留位型和整数精度。5"""6from decimal import Decimal7from pathlib import Path8import struct910LIMIT = 52428811ROWS = 6412_HEADER = struct.Struct('!4sI')13_U32 = struct.Struct('!I')14_I64 = struct.Struct('!q')15_F64 = struct.Struct('!d')16_STATUSES = ('decoded', 'unknown', 'unselected', 'non_data')17_FLAGS = ('is_extended_id', 'is_remote_frame', 'is_error_frame',18 'is_fd', 'bitrate_switch', 'error_state_indicator')192021class CANBlockError(ValueError):22 """固定 CAN 编码结构不完整或不合法。"""232425class _Writer:26 """单块临时编码空间;所有增长先核原有包长上限。"""2728 def __init__(self):29 """建立单块临时空间。3031 参数: 无。32 返回: 新编码器。33 异常: 内存分配错误。34 """35 self.body = bytearray()36 self.strings = []37 self.indices = {}38 self.string_bytes = 43940 def add(self, data):41 """追加已编码字节并先核包长。4243 参数: data为bytes类片段。44 返回: None。45 异常: 超过512KiB时CANBlockError。46 """47 if len(self.body) + self.string_bytes + len(data) + 8 > LIMIT:48 raise CANBlockError('CAN数据块超过512KiB')49 self.body.extend(data)5051 def byte(self, value):52 """写一个uint8。5354 参数: value为0到255整数。55 返回: None。56 异常: 范围或包长错误。57 """58 self.add(bytes((value,)))5960 def uint(self, value):61 """写网络序uint32。6263 参数: value为0到2**32-1整数。64 返回: None。65 异常: 范围或包长错误。66 """67 self.add(_U32.pack(value))6869 def text(self, value):70 """写本块字符串表索引。7172 参数: value为原生str。73 返回: None,首次出现时登记UTF8/surrogatepass内容。74 异常: 类型或包长错误。75 """76 if type(value) is not str:77 raise CANBlockError('CAN文本字段必须是str')78 index = self.indices.get(value)79 if index is None:80 encoded = value.encode('utf-8', 'surrogatepass')81 if len(self.body) + self.string_bytes + len(encoded) + 16 > LIMIT:82 raise CANBlockError('CAN字符串表超过512KiB')83 index = len(self.strings)84 self.indices[value] = index85 self.strings.append(encoded)86 self.string_bytes += 4 + len(encoded)87 self.uint(index)8889 def scalar(self, value):90 # bool 单独编码;大整数不经过 float,也不把非有限原值替换成 None。91 """无损写有限种类原值。9293 参数: value为None/bool/int/float,原float允许NaN/Inf。94 返回: None。95 异常: 未支持类型或包长错误。96 """97 kind = type(value)98 if value is None:99 self.byte(0)100 elif kind is bool:101 self.byte(2 if value else 1)102 elif kind is int:103 self.byte(3 if value >= 0 else 4)104 width = max(1, (abs(value).bit_length() + 7) // 8)105 if width > LIMIT:106 raise CANBlockError('CAN整数超过块长')107 self.uint(width)108 self.add(abs(value).to_bytes(width, 'big'))109 elif kind is float:110 self.byte(5)111 self.add(_F64.pack(value))112 else:113 raise CANBlockError('CAN原值只接受int/float/bool/None')114115 def optional_text(self, value):116 """写可空文本。117118 参数: value为str或None。119 返回: None。120 异常: 类型或包长错误。121 """122 self.byte(value is not None)123 if value is not None:124 self.text(value)125126 def pairs(self, values):127 """写字段值对。128129 参数: values为已经校验的名称和值对序列。130 返回: None。131 异常: 字段类型或包长错误。132 """133 self.uint(len(values))134 for name, value in values:135 self.text(name)136 self.scalar(value)137138 def decimal(self, value):139 """写精确比较时间事实。140141 参数: value为有限Decimal或None。142 返回: None,不按指数展开文本。143 异常: 类型、非有限值或包长错误。144 """145 self.byte(value is not None)146 if value is not None:147 if type(value) is not Decimal or not value.is_finite():148 raise CANBlockError('CAN精确时间事实需要有限Decimal')149 sign, digits, exponent = value.as_tuple()150 self.byte(sign)151 self.scalar(exponent)152 self.uint(len(digits))153 self.add(bytes(digits))154155 def finish(self, magic=b'CND1'):156 """拼接单一头、字符串表和主体。157158 参数: magic为内部写定的CND1或CMT1版本。159 返回: 对应固定格式的完整bytes。160 异常: 超过512KiB时CANBlockError。161 """162 table = bytearray(_U32.pack(len(self.strings)))163 for value in self.strings:164 table.extend(_U32.pack(len(value)))165 table.extend(value)166 size = len(table) + len(self.body)167 if size + 8 > LIMIT:168 raise CANBlockError('CAN数据块超过512KiB')169 table.extend(self.body)170 if magic not in (b'CND1',b'CMT1'):171 raise CANBlockError('内部CAN格式未知')172 return _HEADER.pack(magic, size) + table173174175class _Reader:176 """只按固定顺序取字段;长度检查在切片或容器分配之前执行。"""177178 def __init__(self, packet, expected=b'CND1', *, on_expand=None):179 """核固定头并读取本块字符串表。180181 参数: packet为完整bytes,expected为本固定入口写定的版本;on_expand在模型新增分配前核预约。182 返回: 新读取器,拥有游标及局部展开计数。183 异常: 版本、长度、编码或展开上限错误。184 """185 if type(packet) is not bytes or not 12 <= len(packet) <= LIMIT:186 raise CANBlockError('CAN数据块长度非法')187 magic, size = _HEADER.unpack_from(packet)188 if expected not in (b'CND1',b'CMT1') or magic != expected or size != len(packet) - 8:189 raise CANBlockError('CAN数据块版本或声明长度不符')190 self.data = memoryview(packet)191 # 数据块仍用原6MiB模型部分。READY固定图最多4096字段、8195文本、192 # 每字段3个整数;4*包长 + 128*8195 + (1024+3*64)*4096 < 8MiB。193 # READY的原生图和冻结图并存另由父on_expand在分配前预约,8MiB不是其总峰值。194 self.expansion_limit = (8 if expected==b'CMT1' else 6)*1048576195 self.on_expand = on_expand196 self.expansion = 0197 self.position = 8198 self.strings = []199 value = None200 try:201 count = self.uint()202 if count > self.remaining // 4 or expected==b'CMT1' and count>8195:203 raise CANBlockError('CAN字符串表计数超出剩余包')204 self.charge(count * 128)205 for _ in range(count):206 size = self.uint()207 self.charge(size * 4)208 try:209 value = bytes(self.take(size)).decode('utf-8', 'surrogatepass')210 except UnicodeDecodeError as error:211 raise CANBlockError('CAN字符串不是有效内部UTF8') from error212 self.strings.append(value)213 except BaseException:214 self.close()215 value = packet = None216 raise217218 def close(self):219 """清除仅属解码器的视图与字符串表引用。220221 参数: 无。返回: None;返回模型继续持有其实际使用的不可变文本。222 异常: 无,重复调用无副作用。223 """224 if self.data is not None:225 self.data.release()226 self.data = None227 self.strings.clear()228 self.on_expand = None229230 def charge(self, amount):231 """累计本块模型展开上界。232233 参数: amount为本次新增保守字节数。234 返回: None,不改变ResourceBudget账本。235 异常: 超过本固定图边界时CANBlockError;父预约不足或取消原样传播。236 """237 candidate = self.expansion + amount238 if candidate > self.expansion_limit:239 raise CANBlockError('CAN解码展开超过固定图上界')240 if self.on_expand is not None:241 self.on_expand(candidate)242 self.expansion = candidate243244 @property245 def remaining(self):246 """取得未消费字节数。247248 参数: 无。249 返回: 非负int。250 异常: 无。251 """252 return len(self.data) - self.position253254 def take(self, size):255 """取得有界字节视图并移动游标。256257 参数: size为非负字节数。258 返回: memoryview,不复制包主体。259 异常: 长度越界或截断时CANBlockError。260 """261 if size < 0 or size > self.remaining:262 raise CANBlockError('CAN数据块截断')263 offset = self.position264 self.position += size265 return self.data[offset:self.position]266267 def byte(self):268 """读一个uint8。269270 参数: 无。271 返回: int。272 异常: 截断时CANBlockError。273 """274 position = self.position275 if position >= len(self.data):276 raise CANBlockError('CAN数据块截断')277 value = self.data[position]278 self.position = position + 1279 return value280281 def uint(self):282 """读网络序uint32。283284 参数: 无。285 返回: int。286 异常: 截断时CANBlockError。287 """288 position = self.position289 if position + 4 > len(self.data):290 raise CANBlockError('CAN数据块截断')291 value = _U32.unpack_from(self.data, position)[0]292 self.position = position + 4293 return value294295 def boolean(self):296 """读严格布尔位。297298 参数: 无。299 返回: bool。300 异常: 非0/1或截断时CANBlockError。301 """302 value = self.byte()303 if value not in (0, 1):304 raise CANBlockError('CAN布尔字段非法')305 return bool(value)306307 def text(self):308 """取本块已有字符串。309310 参数: 无。311 返回: str,不复制共享文本。312 异常: 索引越界或截断时CANBlockError。313 """314 index = self.uint()315 if index >= len(self.strings):316 raise CANBlockError('CAN字符串索引越界')317 return self.strings[index]318319 def scalar(self):320 """读固定种类数值。321322 参数: 无。323 返回: None/bool/int/float,保留原位型。324 异常: 未知标记、非最短整数、展开或截断错误。325 """326 tag = self.byte()327 if tag == 0:328 return None329 if tag in (1, 2):330 return tag == 2331 if tag in (3, 4):332 width = self.uint()333 if not 1 <= width <= self.remaining:334 raise CANBlockError('CAN整数长度非法')335 self.charge(width * 2 + 64)336 raw = self.take(width)337 value = None338 try:339 if width > 1 and raw[0] == 0:340 raise CANBlockError('CAN整数不能使用非最短表示')341 value = int.from_bytes(raw, 'big')342 if tag == 4 and value == 0:343 raise CANBlockError('CAN整数不能使用负零表示')344 return value if tag == 3 else -value345 finally:346 # 内层格式错误的异常栈不能通过切片视图继续持有整包。347 raw.release()348 raw = value = None349 if tag == 5:350 return _F64.unpack(self.take(8))[0]351 raise CANBlockError('CAN原值标记未知')352353 def nonnegative(self):354 """读原生非负序号。355356 参数: 无。357 返回: int。358 异常: 负数、bool、其他类型或截断错误。359 """360 value = self.scalar()361 if type(value) is not int or value < 0:362 raise CANBlockError('CAN序号需要非负整数')363 return value364365 def optional_text(self):366 """读可空文本。367368 参数: 无。369 返回: str或None。370 异常: 存在位、索引或截断错误。371 """372 return self.text() if self.boolean() else None373374 def pairs(self, *, maximum=4096):375 """读有限字段对。376377 参数: maximum为条数上限,默认4096。378 返回: tuple名称值对。379 异常: 计数、展开、标记或截断错误。380 """381 count = self.uint()382 if count > maximum or count > self.remaining // 5:383 raise CANBlockError('CAN字段对计数超限')384 self.charge(count * 256)385 return tuple((self.text(), self.scalar()) for _ in range(count))386387 def decimal(self):388 """读精确比较时间事实。389390 参数: 无。391 返回: 有限Decimal或None。392 异常: 符号、数字、指数、展开或截断错误。393 """394 if not self.boolean():395 return None396 sign = self.byte()397 exponent = self.scalar()398 count = self.uint()399 if sign not in (0, 1) or type(exponent) is not int or not count:400 raise CANBlockError('CAN Decimal结构非法')401 self.charge(count * 16 + 256)402 digits = self.take(count)403 value = None404 try:405 if any(value > 9 for value in digits):406 raise CANBlockError('CAN Decimal数字非法')407 try:408 value = Decimal((sign, tuple(digits), exponent))409 except (ValueError, OverflowError) as error:410 raise CANBlockError('CAN Decimal指数非法') from error411 if not value.is_finite():412 raise CANBlockError('CAN Decimal必须有限')413 return value414 finally:415 digits.release()416 digits = value = exponent = None417418419def _write_frame(writer, frame):420 """写原帧固定公开字段。421422 参数: writer块编码器,frame为已知原帧。423 返回: None。424 异常: 字段或包长错误。425 """426 writer.add(_F64.pack(frame.timestamp))427 writer.uint(frame.arbitration_id)428 writer.byte(sum(int(getattr(frame, name)) << index for index, name in enumerate(_FLAGS)))429 writer.byte(2 if frame.is_rx is None else int(frame.is_rx))430 writer.byte(type(frame.channel) is str)431 if type(frame.channel) is str:432 writer.text(frame.channel)433 else:434 writer.scalar(frame.channel)435 writer.scalar(frame.dlc)436 writer.uint(len(frame.data))437 writer.add(frame.data)438439440def _read_frame(reader, timed):441 """恢复已知原帧类型。442443 参数: reader块读取器,timed指是否为有时间RawCANRecord。444 返回: LegacyRawFrame或已知can.Message。445 异常: 固定字段或原模型校验错误,无时间模式不增加时间有限性检查。446 """447 timestamp = _F64.unpack(reader.take(8))[0]448 frame_id, flags, direction = reader.uint(), reader.byte(), reader.byte()449 if flags & ~63 or direction not in (0, 1, 2):450 raise CANBlockError('CAN原帧标志非法')451 channel = reader.text() if reader.boolean() else reader.scalar()452 if channel is not None and type(channel) not in (int, str):453 raise CANBlockError('CAN通道字段非法')454 dlc = reader.scalar()455 size = reader.uint()456 if size > 64:457 raise CANBlockError('CAN载荷超过64字节')458 arguments = dict(timestamp=timestamp, arbitration_id=frame_id, channel=channel,459 dlc=dlc, data=bytes(reader.take(size)),460 is_rx=None if direction == 2 else bool(direction),461 **{name: bool(flags & (1 << index)) for index, name in enumerate(_FLAGS)})462 if timed:463 from ..legacy.records import LegacyRawFrame464 return LegacyRawFrame(**arguments)465 # 没有时间消费者的 BLF 不额外执行有限时间检查;只能恢复这个已知帧类型。466 from ..can_dependencies import import_can_dependency467 return import_can_dependency('can').Message(**arguments, check=False)468469470def encode_block(number, records, eof, facts):471 """编码一个已校验CAN块。472473 参数: number非负块号,records最多64条,eof真实结束,facts六项文件事实。474 返回: CND1 bytes。475 异常: 数量、字段或原512KiB包长错误。476 """477 from ..sources.raw_can import RawCANRecord478 if type(number) is not int or number < 0 or type(eof) is not bool or len(records) > ROWS:479 raise CANBlockError('CAN块号、EOF或条数非法')480 writer = _Writer()481 writer.scalar(number)482 writer.byte(eof)483 writer.uint(len(records))484 for record in records:485 writer.text(record.source)486 writer.scalar(record.input_index)487 writer.scalar(record.sequence)488 writer.byte(_STATUSES.index(record.status))489 raw = record.raw490 timed = type(raw) is RawCANRecord491 writer.byte(timed)492 _write_frame(writer, raw.frame)493 writer.text(str(raw.path))494 writer.scalar(raw.sequence)495 if timed:496 writer.add(_I64.pack(raw.time_ns))497 writer.scalar(raw.line_number)498 writer.optional_text(raw.raw_time)499 writer.text(raw.parser_version)500 writer.scalar(raw.dlc_code)501 writer.byte(raw.time_quantized)502 writer.scalar(raw.source_time_ns)503 writer.pairs(record.values)504 writer.pairs(record.frame_values)505 writer.pairs(record.frame_layout)506 expected = {'count', 'first_ns', 'last_ns', 'decode_calls', 'first_order_time', 'last_order_time'}507 if set(facts) != expected:508 raise CANBlockError('CAN文件事实字段不符')509 for name in ('count', 'first_ns', 'last_ns', 'decode_calls'):510 writer.scalar(facts[name])511 for name in ('first_order_time', 'last_order_time'):512 writer.decimal(facts[name])513 return writer.finish()514515516def decode_block(packet):517 """恢复一个固定CAN块;失败只保留原诊断,不保留已经解码的整批记录。518519 参数: packet为完整CND1 bytes。520 返回: 块号、records列表、EOF和facts字典;成功时记录所有权交给调用方。521 异常: 结构、模型、展开、截断或尾随字节错误;保留原异常与原因链。522 """523 from ..sources.raw_can import RawCANRecord524 from ..sources.blf import _UntimedBLFRecord525 from ..sources.text_can_decode import DecodedCANRecord526 from ..sources.can_spec import can_field_from_key527 reader = None528 records = []529 frame = raw = facts = source = path = None530 known_fields = values = frame_values = frame_layout = pair = key = None531 try:532 reader = _Reader(packet)533 number, eof, count = reader.nonnegative(), reader.boolean(), reader.uint()534 if count > ROWS:535 raise CANBlockError('CAN记录数超过64')536 reader.charge(count * 2048)537 if count:538 # 集合只引用本块已有字符串;基础空间及扩容峰值在实际分配前计入6MiB。539 reader.charge(512)540 known_fields = set()541 for _ in range(count):542 source, input_index, sequence = reader.text(), reader.nonnegative(), reader.nonnegative()543 status = reader.byte()544 if status >= len(_STATUSES):545 raise CANBlockError('CAN状态标记未知')546 timed = reader.boolean()547 frame = _read_frame(reader, timed)548 path, raw_sequence = Path(reader.text()), reader.nonnegative()549 if timed:550 raw = RawCANRecord(frame, _I64.unpack(reader.take(8))[0], path,551 reader.scalar(), raw_sequence, reader.optional_text(), reader.text(),552 reader.scalar(), reader.boolean(), reader.scalar())553 else:554 raw = _UntimedBLFRecord(frame, path, raw_sequence)555 values, frame_values, frame_layout = reader.pairs(), reader.pairs(), reader.pairs()556 # 只有values中的规范CAN键需要完整解析;原物理字段名称不套用这条规则。557 # 私有坏包先核实际出现的键,不承诺与原pickle恢复有相同的首个错误。558 for pair in values:559 key = pair[0]560 if key not in known_fields:561 can_field_from_key(key)562 reader.charge(192)563 known_fields.add(key)564 records.append(DecodedCANRecord._from_bound_values(source, input_index, sequence, raw,565 values, _STATUSES[status], frame_values, frame_layout, known_fields))566 facts = {name: reader.scalar() for name in ('count', 'first_ns', 'last_ns', 'decode_calls')}567 if any(type(facts[name]) is not int or facts[name] < 0 for name in ('count', 'decode_calls')):568 raise CANBlockError('CAN文件计数非法')569 if any(value is not None and (type(value) is not int or not -(2**63) <= value < 2**63)570 for value in (facts['first_ns'], facts['last_ns'])):571 raise CANBlockError('CAN文件纳秒事实非法')572 for name in ('first_order_time', 'last_order_time'):573 facts[name] = reader.decimal()574 if reader.remaining:575 raise CANBlockError('CAN数据块含未消费字节')576 return number, records, eof, facts577 except BaseException:578 # 当前模型的构造诊断可保留该条记录;已经完成但尚未交付的其他记录不属于诊断。579 records.clear()580 raise581 finally:582 if known_fields is not None:583 known_fields.clear()584 if reader is not None:585 reader.close()586 # 成功的返回值已经持有所需模型;这里只撤销当前调用的引用,不修改交付列表。587 packet = records = frame = raw = facts = source = path = None588 known_fields = values = frame_values = frame_layout = pair = key = None589590591_METADATA_KEYS = {'profile','dbc_path','dbc_sha256','fields','decode_choices','allow_truncated',592 'selected_length_check','decode_scope','cantools_version','record_check_scope','time_order_check_scope'}593594595def _metadata(value):596 """核当前CAN读取器字段事实。597598 参数: value为固定元数据映射。599 返回: 原生dict。600 异常: 缺项、未知字段或类型不符。601 """602 item=hint=None603 try:604 from ..legacy.records import field_from_dict605 # 固定字段已经单独检查;只读校验不再复制整份元数据容器。606 if type(value) is not dict or set(value) != _METADATA_KEYS:607 raise CANBlockError('CAN元数据根字段不符')608 if (value['profile'] != 'can-text-v1' or value['decode_choices'] is not False609 or value['allow_truncated'] is not True or value['decode_scope'] != 'requested'610 or value['selected_length_check'] not in ('legacy','partial','exact')611 or value['record_check_scope'] not in ('source','selected_ids')612 or value['time_order_check_scope'] not in ('source','selected_ids','none')613 or type(value['fields']) not in (list,tuple) or len(value['fields']) > 4096):614 raise CANBlockError('CAN元数据政策或字段数量非法')615 for name in ('dbc_path','cantools_version'):616 if type(value[name]) is not str:617 raise CANBlockError('CAN元数据文本非法')618 digest = value['dbc_sha256']619 if digest is not None and (type(digest) is not str or len(digest)!=64620 or any(c not in '0123456789abcdef' for c in digest)):621 raise CANBlockError('CAN元数据摘要非法')622 for item in value['fields']:623 allowed = {'locator','unit','dbc_frames','frame_format_compatibility','cycle_ns'}624 if type(item) is not dict or not {'locator','unit'} <= set(item) or not set(item)<=allowed:625 raise CANBlockError('CAN字段事实结构非法')626 field_from_dict(item['locator'])627 if item['unit'] is not None and type(item['unit']) is not str:628 raise CANBlockError('CAN单位事实非法')629 if 'cycle_ns' in item and (type(item['cycle_ns']) is not int or not 0<item['cycle_ns']<2**63):630 raise CANBlockError('CAN周期事实非法')631 if 'dbc_frames' in item:632 if type(item['dbc_frames']) not in (list,tuple) or any(type(pair) not in (list,tuple) or len(pair)!=2633 or any(type(flag) is not bool for flag in pair) for pair in item['dbc_frames']):634 raise CANBlockError('CAN DBC帧型事实非法')635 if 'frame_format_compatibility' in item:636 hint=item['frame_format_compatibility']637 if (type(hint) is not dict or set(hint)!={'code','dbc_is_fd','requested_is_fd'}638 or hint['code']!='dbc_fd_classic_selector' or hint['dbc_is_fd'] is not True639 or hint['requested_is_fd'] is not False):640 raise CANBlockError('CAN帧型兼容事实非法')641 return value642 finally:643 value=item=hint=None644645646def encode_metadata(value):647 """编码READY事实,并复用单块字符串表避免宽字段的重复JSON键膨胀。648649 参数: value为当前固定元数据。650 返回: CMT1 bytes,仍受原512KiB上限。651 异常: 字段、类型或包长错误。652 """653 value=_metadata(value)654 writer=_Writer()655 writer.text(value['dbc_path'])656 writer.optional_text(value['dbc_sha256'])657 writer.text(value['cantools_version'])658 writer.byte(('legacy','partial','exact').index(value['selected_length_check']))659 writer.byte(('source','selected_ids').index(value['record_check_scope']))660 writer.byte(('source','selected_ids','none').index(value['time_order_check_scope']))661 writer.uint(len(value['fields']))662 for item in value['fields']:663 locator=item['locator']664 writer.scalar(locator['channel'])665 writer.text(locator['signal'])666 writer.scalar(locator['frame_id'])667 for key in ('is_extended','is_fd'):668 writer.byte(2 if locator.get(key) is None else int(locator[key]))669 writer.optional_text(item['unit'])670 flags=sum(bit for name,bit in (('dbc_frames',1),('frame_format_compatibility',2),('cycle_ns',4)) if name in item)671 writer.byte(flags)672 if flags&1:673 frames=item['dbc_frames']674 if len(frames)>4:675 raise CANBlockError('DBC帧型组合超过四种')676 writer.byte(len(frames))677 for extended,fd in frames:678 writer.byte(int(extended)|(int(fd)<<1))679 if flags&4:680 writer.scalar(item['cycle_ns'])681 return writer.finish(b'CMT1')682683684def decode_metadata(packet, *, on_expand=None):685 """恢复固定READY事实;只按明确字段解释CMT1,不支持旧pickle。686687 参数: packet为完整CMT1 bytes;on_expand在新增固定模型分配前核父端实际额度。688 返回: 固定元数据dict,随后由既有模型冻结。689 异常: 版本、字段、包长、索引、展开或截断错误。690 """691 reader=None692 value=fields=item=locator=frames=None693 try:694 reader=_Reader(packet,b'CMT1',on_expand=on_expand)695 value=dict(profile='can-text-v1',dbc_path=reader.text(),dbc_sha256=reader.optional_text(),696 cantools_version=reader.text(),decode_choices=False,allow_truncated=True,decode_scope='requested')697 for name,choices in (698 ('selected_length_check',('legacy','partial','exact')),699 ('record_check_scope',('source','selected_ids')),700 ('time_order_check_scope',('source','selected_ids','none'))):701 index=reader.byte()702 if index>=len(choices):703 raise CANBlockError('CAN元数据策略索引非法')704 value[name]=choices[index]705 count=reader.uint()706 if count>4096 or count>reader.remaining//10:707 raise CANBlockError('CAN元数据字段数量非法')708 reader.charge(count*1024)709 fields=[]710 for _ in range(count):711 locator=dict(channel=reader.nonnegative(),signal=reader.text(),frame_id=reader.nonnegative())712 for key in ('is_extended','is_fd'):713 flag=reader.byte()714 if flag not in (0,1,2):715 raise CANBlockError('CAN定位帧型标志非法')716 if flag!=2:717 locator[key]=bool(flag)718 item=dict(locator=locator,unit=reader.optional_text())719 flags=reader.byte()720 if flags&~7:721 raise CANBlockError('CAN字段事实标志未知')722 if flags&1:723 combinations=reader.byte()724 if combinations>4:725 raise CANBlockError('DBC帧型组合超过四种')726 frames=[]727 for _ in range(combinations):728 pair=reader.byte()729 if pair>3:730 raise CANBlockError('DBC帧型组合非法')731 frames.append([bool(pair&1),bool(pair&2)])732 item['dbc_frames']=frames733 if flags&2:734 item['frame_format_compatibility']=dict(code='dbc_fd_classic_selector',dbc_is_fd=True,requested_is_fd=False)735 if flags&4:736 item['cycle_ns']=reader.nonnegative()737 fields.append(item)738 value['fields']=fields739 if reader.remaining:740 raise CANBlockError('CAN元数据含未消费字节')741 return _metadata(value)742 finally:743 if reader is not None:744 reader.close()745 packet=value=fields=item=locator=frames=None746