路段标识与切片:route/-1 与 route—1 为什么结果不同

作者:

在

结论:路段标识不是单一校验器,而是正则族、构造器与切片函数三套契约的叠加。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 或回放。换言之,「字符串能匹配」与「文件真实存在且属于该路段」是两回事;服务端是否实际返回旧权限下的数据、请求库默认超时与网络资源行为,均不由探针证明。

对自制硬件与插件化的要求

  1. 把「语法标识」「已授权数据集」「实际文件内容」分成三层:规范设备/日志/片段 ID、来源与选择器、固定文件哈希及 schema 分别记录。
  2. 远端返回每个文件 URL 时,先核对其中的设备 ID、日志 ID 和片段号属于所请求的路段。
  3. 离线回放不应为负索引或开放切片隐式访问 API;先解析成明确有序片段清单,再逐段验证日志/视频/相机同步。
  4. 不能用标识字符串长度或 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 或时间字段;而且正则与构造器的接受集不同。标识只表示语法形状,不构成可信身份认证。

延伸阅读

参考资料

  • 项目 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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注