结论:路段标识不是单一校验器,而是正则族、构造器与切片函数三套契约的叠加。RouteName 只用 assert 检查两段长度 16/20;SegmentRange.seg_idxs 对有界正索引遵循 Python 的右开区间(1:4 返回片段 1、2、3),而负索引与开放区间会隐式查询远端 maxqlog。因此 route/-1 与 route---1 走的是不同分支:前者可被接受为「没有片段号」,后者在路段长度断言处失败。
标识与切片函数
| 入口 | 实现语义 | 边界 |
|---|---|---|
RouteName.__init__() |
选择先出现的 | 或 / 分隔设备 ID 与日志 ID,并转为 device|log 规范名。 |
只用 assert 检查两段长度 16/20,未验证十六进制设备 ID 或时间字段格式;不是可信身份认证。 |
SegmentName.__init__() |
从三种分隔形式中提取片段号,可保留本地数据目录;allow_route_name=True 用 -1 表示没有片段号。 |
斜杠分段的 int() 可接受负片段号;-- 加负号形成三个连字符时,rsplit('--', 1) 留下多余 - 并导致路段长度断言失败。 |
SegmentRange.__init__() |
用 RE.SEGMENT_RANGE 对整个字符串匹配,保存正则组。 |
selector 仅允许 q/r/a,但「匹配成功」不保证所选日志实际存在。 |
SegmentRange.seg_idxs |
单个非负片段直接返回;有界正索引切片用 range(end+1)[slice];负索引或无上界切片先查询最大片段号。 |
整条路线、负索引和开放区间依赖远端元数据;单个过大的正片段号不会在此处查存在性。 |
有界切片遵循右开区间,例如 1:4 返回片段 1、2、3;该函数先构造完整 list 再切片,不是按需生成,大的上界会增加内存。对于单个负索引,先加 maxqlog + 1,没有进一步验证结果仍非负。隔离探针把 maxqlog 固定为 3,确认 -1 → [3]、-10 → [-6],而单个正数 50 → [50] 也不在此处检查真实上界。
共享正则与解析器边界
RE 是共享的文本模式集合,不是统一的路段身份验证器。同一个字符串经由不同入口,结论可能相反。
匹配方式本身就是差异来源:RE.EXPLORER_FILE 要求本地文件名主体与扩展均为小写,大写 RLOG.zst 不匹配;Bootlog.__init__() 用 re.search 可从前后带其他文本的字符串提取路段,而 Athena createClip() 用 re.fullmatch。搜索匹配不能充当整串授权或来源证明,导入器也必须明确如何处理非预期命名。
| 模式或消费者 | 实测契约 | 后续设计含义 |
|---|---|---|
RE.TIMESTAMP |
2023-99-99--25-61-61 符合字符形状;模式不检查日历值。 |
区分日志 ID 语法和有效日期。 |
RE.ROUTE_NAME 与 RouteName |
正则允许下划线分隔,但构造器只识别 | 或 /;反过来,构造器接受 16 个 Z 和 20 个 x,正则拒绝。 |
不同入口不能互相代替校验结果;应定义唯一规范格式。 |
RE.SEGMENT_NAME 与 SegmentName |
模式片段号只接受非负数字;构造器接受 route/-1,但 route---1 不能正常解析。 |
切片哨兵值与真实文件/目录名应分开类型。 |
RE.BOOTLOG_NAME |
它只是 ROUTE_NAME 别名;Bootlog.__init__() 的 re.search 可从前后带其他文本的字符串提取路段,Athena createClip() 则用 re.fullmatch。 |
搜索匹配不能充当整串授权或来源证明。 |
RE.EXPLORER_FILE |
本地文件名需小写字母主体及小写字母数字扩展;大写 RLOG.zst 不匹配。 |
导入器要明确如何处理非预期命名。 |
本地与远端路段发现
Route.__init__() 按是否给 data_dir 选择本地或远端,再取排序后最后一个片段号作为 max_seg_number;若远端返回空片段列表,取 [-1] 会失败。_get_segments_remote() 请求 v1/route/<canonical>/files,按 URL path 末四级拼出片段名;_get_segments_local() 识别 explorer 单文件、device|route--segment 目录和 device|route/segment 目录三种形态,本地缺段保留为 None。
一个值得注意的边界:远端 API 若为请求路段返回另一设备 ID 的合法 URL,该 URL 会被解析成另一路段的 Segment,但仍进入当前 Route.log_paths();本层不核对返回 URL 的 dongle_id/log_id 是否与请求相同。此外 Segment.url 的 @cache 仅按路段名缓存,不把令牌/会话放进键,也没有 TTL;Segment.events 再 GET <url>/events.json,调用 raise_for_status(),但没有显式超时。
归类与缓存还有几处覆盖规则:_get_segments_remote() 把 URL path 末四级拼成片段名并按文件名归类 rlog、qlog 和三路相机,同类别多 URL 时后出现的值覆盖先前值,没有 .zst 与 .bz2 的显式优先级;本地则以目录遍历顺序决定同类重名格式的首选,Route 对路径的整理不打开日志,也不检查压缩格式、schema 或文件哈希。隔离探针还确认:本地 0、2 段会保留第 1 段的 None 空位;远端同一段的 rlog.zst、rlog.bz2 由后者覆盖;空远端文件列表在读取最大段号时抛 IndexError。上述结论由十六项隔离探针核对,但未联网查询、未运行真实 LogReader 或回放。换言之,「字符串能匹配」与「文件真实存在且属于该路段」是两回事;服务端是否实际返回旧权限下的数据、请求库默认超时与网络资源行为,均不由探针证明。
对自制硬件与插件化的要求
- 把「语法标识」「已授权数据集」「实际文件内容」分成三层:规范设备/日志/片段 ID、来源与选择器、固定文件哈希及 schema 分别记录。
- 远端返回每个文件 URL 时,先核对其中的设备 ID、日志 ID 和片段号属于所请求的路段。
- 离线回放不应为负索引或开放切片隐式访问 API;先解析成明确有序片段清单,再逐段验证日志/视频/相机同步。
- 不能用标识字符串长度或 API 返回 URL 作为真实性证明;跨来源补段、qlog/rlog 选择和文件格式须在报告中显式呈现。
常见问题
route/-1 和 route---1 为什么结果不同?
route/-1 走斜杠分段,int() 可接受负片段号;route---1 是 -- 加负号形成三个连字符,rsplit('--', 1) 会留下多余 -,导致路段长度断言失败。两者与真实路段是否存在无关。
有界切片 1:4 会选中哪些片段?
遵循 Python 右开区间,返回片段 1、2、3。该函数先构造完整 range 列表再切片,不是按需生成,大的上界会增加内存。
为什么负索引会访问网络?
因为 SegmentRange.seg_idxs 对负索引或无上界切片会先调用 get_max_seg_number_cached() 查询远端 maxqlog。整条路线、负索引和开放区间都不是纯离线解析,依赖远端元数据。
路段标识能当身份认证吗?
不能。RouteName.__init__() 只用 assert 检查两段长度 16/20,未验证十六进制设备 ID 或时间字段;而且正则与构造器的接受集不同。标识只表示语法形状,不构成可信身份认证。
延伸阅读
- 离线与实时两条数据管线:日志回放和上车运行的差别 —— 承接本文离线回放为何不应隐式访问 API。
- 路段下载的鉴权:令牌、会话与缓存各自管什么 —— 承接本文 URL 来源核对与缓存键问题。
- process replay 校验:为什么「能回放」不等于「行为等价」 —— 承接片段选择与回放输入的可靠性讨论。
- 赛道日数据复盘:用 42 条赛道分段定位每一秒失分 —— 承接按片段组织数据的实用视角。
- GNSS UBX 二进制解析:帧头、长度与校验怎么校验 —— 对照「格式形状不等于语义有效」的解析边界。
参考资料
- 项目 02 ——
openpilot-research/ROUTE_SEGMENT_IDENTITY_SLICE_DEEP_DIVE.md(取用了标识与切片函数、共享正则与解析器边界、本地与远端路段发现,以及插件化要求)。 - 项目 02 ——
openpilot-research/REPLAY_INPUT_IO_DEEP_DIVE.md(仅用于确认回放输入与实际文件读取的衔接位置)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复