Skip to content

结构化 SipParser 实装 + RFC 4475 合规驱动测试(叠加 PR #3) - #4

Draft
lixuanqun wants to merge 2 commits into
cursor/sip-rfc4475-fixtures-3c36from
cursor/sip-parser-3c36
Draft

结构化 SipParser 实装 + RFC 4475 合规驱动测试(叠加 PR #3)#4
lixuanqun wants to merge 2 commits into
cursor/sip-rfc4475-fixtures-3c36from
cursor/sip-parser-3c36

Conversation

@lixuanqun

Copy link
Copy Markdown
Owner

概述

Phase 2b 落地:把骨架里的 SipParserUnsupportedOperationException 变成真正能跑的结构化解析器,并让 RFC 4475 fixture 接入合规闸门。

🔗 本 PR 基于 PR #3(fixture 基础设施)。
PR #3 合并后 base 会自动更新。

实装内容

SipParser(手写字节级状态机)

输入byte[] / String / ByteBuffer
输出SipMessage(sealed: SipRequest | SipResponse

1. 前 4 字节判定 Request vs Response("SIP/" 前缀 → Response)
2. Start-Line:
   • Request-Line = Method SP Request-URI SP SIP-Version CRLF
   • Status-Line  = SIP-Version SP Status-Code SP Reason-Phrase CRLF
3. Header 块:按行解析,应用 RFC 3261 §7.3.1 LWS folding
   (续行以 SP/HTAB 开头 → 合并为单 SP,归并到上一头部)
4. Body framing:按 Content-Length 取字节;缺失则取剩余字节(UDP)

特点

  • 零正则、零外部依赖、零分配(除字符串)
  • 头值保持为 RawHeader(字符串),typed 解析留给后续 PR 增量做
  • Request-URI 暂用 OpaqueUri(scheme, schemeSpecificPart) 无损保留,未来加 SipUriParser 时切到 SipUricall site 不变

SipCodecException 增强

public class SipCodecException extends RuntimeException {
    public String category();   // 稳定 kebab-case 分类(与 fixture manifest 同步)
    public int offset();        // 失败字节偏移,便于诊断
}

Category 常量(与 fixture manifest 的 error.category 完全对齐):

  • malformed-start-line
  • malformed-header
  • unknown-version
  • bad-content-length
  • truncated
  • unsupported-uri-scheme
  • encode-failure

Uri.asWire()

sealed Uri 接口新增 asWire() 方法,用于无损回写到 Request-Line。

  • OpaqueUri.asWire() = scheme + ":" + schemeSpecificPart
  • SipUri.asWire() = 完整重建 sip:[user[:pass]@]host[:port][;params][?headers]

这样测试可以断言:解析→回写后 URI 字符串完全等于源 fixture 声明的 URI。

ParserConformanceTest(合规驱动)

@TestFactory
Iterable<DynamicTest> everyAcceptFixtureIsParsed() { ... }

@TestFactory
Iterable<DynamicTest> everyStructuralRejectFixtureIsRejectedWithDeclaredCategory() { ... }

每个 accept fixture 自动验证:

  • 版本字面量
  • body 长度 = manifest 声明的 body.length = 实际帧字节数
  • request: method、request-URI(asWire() 回写值)
  • response: status、reason
  • header count

每个 structural reject fixture 自动验证:

  • SipParser.parse() 必须抛 SipCodecException
  • exception.category() 必须等于 manifest 的 error.category

Manifest schema 扩展:parser.phase

FixtureExpectation.Reject 增加 phase 字段(默认 structural)。
用于标注哪个解析层负责拒绝。

phase 含义 当前是否在 gate
structural 结构化解析器拒绝 ✅ 现在就跑
typed-header typed Via/From/To/CSeq 解析器拒绝 ⏳ 等 typed 层
transaction 事务层拒绝(如缺 Call-ID) ⏳ 等事务层
dialog 对话层拒绝 ⏳ 等对话层

badinv01 因此被标为 typed-header(结构化解析器看不出 Via 值里 ;;,;,, 的语义错),等 typed header 解析落地后自动并入 gate。

新增 RFC 4475 fixtures

  • 3.1.1.10-transports — 五种 Via transport(UDP/SCTP/TLS/UNKNOWN/TCP),验证多 Via 保序解析
  • 3.1.2.3-ncl — Content-Length: -999,结构化 bad-content-length 必拒

测试矩阵

$ mvn -B -ntp verify
[INFO] SIP :: Message Model ............................... SUCCESS  10 tests
[INFO] SIP :: Codec ....................................... SUCCESS   8 tests
[INFO] SIP :: Compliance Tests ............................ SUCCESS  21 tests
[INFO] BUILD SUCCESS — 39 tests, 0 failures

合规模块分布:

  • FixtureRepositoryTest(harness self-test):1 + 9 + 6 + 3 = 19 dynamic tests
  • ParserConformanceTest(端到端 parser):6 accept + 2 structural reject = 8 dynamic tests
  • = 27 conformance dynamic tests

Accept fixtures 现已全部 byte-exact 通过 parser:

Fixture 验证点
self-test/simple-options OPTIONS 请求基线
self-test/simple-200-ok 200 OK 响应基线
self-test/invite-with-sdp 162 字节 SDP body framing
rfc4475/3.1.1.6-lwsdisp display-name 与 < 之间无 LWS
rfc4475/3.1.1.9-semiuri URI 内分号参数 + LWS folding
rfc4475/3.1.1.10-transports 5 个 Via 头多种 transport

Reject fixtures 在 structural phase 通过:

Fixture category
self-test/missing-sip-version malformed-start-line
rfc4475/3.1.2.3-ncl bad-content-length

下一步(Phase 2c+)

依此 PR 之上可继续叠加:

  • Typed header parsing:Via、From、To、CSeq、Contact、Route — badinv01 自动加入 gate
  • 增量纳入剩余 ~25 个 RFC 4475 fixtures
  • SipEncoder 实装 + roundtrip 测试
  • SipUriParser 切换 OpaqueUriSipUri
  • 然后才是 transaction 层(FSM + 时间轮)

合并顺序:#2#3 → 本 PR。

Open in Web Open in Cursor 

cursoragent and others added 2 commits May 11, 2026 15:17
SipParser
- 手写字节级状态机,零正则、零分配优化(除字符串)
- 自动识别 Request / Response(前 4 字节 'SIP/' 判定)
- Request-Line: Method SP Request-URI SP SIP-Version CRLF
- Status-Line:  SIP-Version SP Status-Code SP Reason-Phrase CRLF
- Header 块:RFC 3261 §7.3.1 LWS folding(SP/HTAB 续行合并为单 SP)
- Body framing:按 Content-Length 取字节;缺失则取剩余字节(UDP)
- 输出:SipMessage(headers 为 RawHeader,URI 暂用 OpaqueUri 保真)

SipCodecException 增强
- 新增 category(kebab-case 稳定分类,匹配 fixture manifest)
- 新增 offset(字节偏移,便于诊断)
- Category 常量:malformed-start-line / malformed-header /
  unknown-version / bad-content-length / truncated /
  unsupported-uri-scheme / encode-failure

Uri.asWire()
- sealed Uri 接口新增 asWire() 用于无损回写
- OpaqueUri.asWire():scheme + ':' + schemeSpecificPart
- SipUri.asWire():完整重建 sip:[user[:pass]@]host[:port][;params][?headers]

SipParserTest(8 个单测)
- 覆盖:基本 OPTIONS 请求、200 OK 响应、LWS folding、
  Content-Length body framing、缺 SIP-Version、未知版本、
  Content-Length 超出、截断输入

替换旧的 SipParserContractTest(断言 UnsupportedOperationException)。

Co-authored-by: li xuanqun <793005378@qq.com>
ParserConformanceTest(在 sip-compliance-tests)
- 每个 accept fixture 喂给 SipParser.parse(byte[]),断言:
  · 版本、body length、header count
  · request 的 method 与 request-URI(asWire 回写)
  · response 的 status 与 reason
- 每个 structural reject fixture 必须抛 SipCodecException,
  且 category 与 manifest 声明一致

manifest schema 扩展
- FixtureExpectation.Reject 新增 phase 字段(默认 'structural')
- 用于标注哪个解析层负责拒绝。typed-header / transaction / dialog
  阶段的 reject fixture 在当前结构化测试中被过滤

新增 fixtures
- rfc4475/3.1.1.10-transports:多种 Via transport(含 UNKNOWN)
- rfc4475/3.1.2.3-ncl:负 Content-Length(structural 必拒)

调整:badinv01 的 phase 标为 typed-header(结构化解析器
检测不到 Via 值里的额外分隔符;该 fixture 会随 typed header
parsing 落地后自动加入 gate)

torture/README.md 更新 parser.phase 字段说明。

完整 mvn verify:9 模块 SUCCESS,35 tests 全绿
(sip-message 10 + sip-codec 8 + compliance 17 + parser-conformance 9 - 1 skipped)

Co-authored-by: li xuanqun <793005378@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants