diff --git a/README.md b/README.md
index a11b930..7121b87 100644
--- a/README.md
+++ b/README.md
@@ -1,82 +1,161 @@
# SIP Stack
-> 一个现代化、模块化、基于 JDK 21 虚拟线程的 Java SIP 协议栈。
+> 现代化、模块化、基于 **JDK 21 虚拟线程** 的 Java SIP 协议栈。
> **纯库实现**:协议核心零运行时依赖,框架适配可选。
-> **第一阶段目标**:GB28181(B 场景)。**最终目标**:通用 SIP 协议栈(A 场景)。
+> **覆盖范围**:RFC 3261 核心 + RFC 7616 Digest + GB/T 28181-2016 国标应用层。
-⚠ 项目处于**重启初期**,当前仓库只有骨架。详见 [#1 路线图](#路线图)。
+[]()
+[]()
---
## 设计原则
-1. **协议核心不感知框架与网络库**。`sip-message`、`sip-codec` 模块零运行时依赖。
-2. **分层强制隔离**。通过 Maven 多模块 + JPMS `module-info.java` 在编译期阻断脏依赖。
+1. **协议核心不感知框架与网络库**。`sip-message`、`sip-codec` 零运行时依赖。
+2. **分层强制隔离**。Maven 多模块 + JPMS `module-info.java` 在编译期阻断脏依赖。
3. **拥抱 JDK 21**。虚拟线程让事务/对话 FSM 用同步代码即可表达,无需回调/事件循环。
-4. **RFC 4475 驱动开发**。第一行测试代码就是 torture tests,逼出真正合规的解析器。
-5. **可插拔传输**。默认 `sip-transport-nio`,未来可加 `sip-transport-netty` 等不破坏 SPI。
+4. **RFC 4475 驱动开发**。Torture-test fixtures 是合规闸门,每个新解析特性都先有 fixture。
+5. **可插拔传输**。默认 `sip-transport-nio`(NIO + 虚拟线程),未来可加 `sip-transport-netty` 等不破坏 SPI。
## 模块结构
```
-sip-stack-parent POM 父模块
-├── sip-message 不可变消息模型(sealed + record) [零依赖]
-├── sip-codec wire-format 解析/编码(手写,RFC 3261 §25) [零依赖+sip-message]
-├── sip-transport-api 传输 SPI 纯接口 [+sip-message]
-├── sip-transport-nio JDK 21 NIO + 虚拟线程默认实现 [+slf4j]
-├── sip-transaction 事务层 FSM + 时间轮 [+slf4j]
-├── sip-dialog 对话层 [+slf4j]
-├── sip-ua 高级 UA API [+slf4j]
-└── sip-compliance-tests RFC 4475 / RFC 5118 合规测试 [test-only]
+sip-stack-parent
+├── sip-message 不可变消息模型(sealed + record) [零依赖]
+├── sip-codec wire-format 解析/编码 + typed headers [+sip-message]
+├── sip-transport-api 传输 SPI 纯接口 [+sip-message]
+├── sip-transport-nio JDK 21 NIO + 虚拟线程默认实现(UDP + TCP) [+codec, +slf4j]
+├── sip-transaction RFC 3261 §17 四个事务 FSM + 时间轮 [+transport-api]
+├── sip-dialog RFC 3261 §12 对话层(Route Set / Target Refresh)[+transaction]
+├── sip-ua 高级 UA API(REGISTER / OPTIONS / Digest 鉴权) [+dialog]
+├── sip-gb28181 GB/T 28181-2016 应用层(MANSCDP XML) [+ua]
+└── sip-compliance-tests RFC 4475 / RFC 5118 合规测试 [test-only]
```
## 关键决策
-| 维度 | 选择 | 说明 |
-| ----------- | ----------------------------------- | ---- |
-| 项目定位 | B → A | 先做 GB28181 信令网关,最终做通用栈 |
-| JDK | **21 LTS** | 虚拟线程、Pattern Matching、Records、Sealed |
-| 网络方案 | **JDK NIO + 虚拟线程**(不引入 Netty) | UDP 用 `DatagramChannel`,TCP/TLS 用 `Socket` + 虚拟线程
详见 [ADR-0001](docs/adr/0001-transport-layer-selection.md) |
-| 协议核心依赖 | 仅 SLF4J(且仅在需要日志的模块) | `sip-message` / `sip-codec` 连 SLF4J 都不引入 |
-| 框架适配 | 暂不做 | 先打磨纯库;Spring Boot starter 是后续可选模块 |
-| 解析器 | 手写递归下降 | 不使用 ANTLR / 正则 / parser combinator |
-| 消息模型 | 不可变 record + sealed | 模式匹配即穷尽;后续可加更多 `permits` |
+所有重要架构决策记录在 [`docs/adr/`](docs/adr/) 目录,遵循 ADR 格式。
-> 所有重要架构决策记录在 [`docs/adr/`](docs/adr/) 目录下,遵循 ADR (Architecture Decision Record) 格式。
+- [ADR-0001](docs/adr/0001-transport-layer-selection.md) — 传输层:**JDK 21 虚拟线程 + NIO**(不引入 Netty)
+- [ADR-0002](docs/adr/0002-transaction-layer-design.md) — 事务层:四 FSM + 时间轮 + 虚拟线程
-## 构建
+| 维度 | 选择 |
+| --- | --- |
+| 项目定位 | B(GB28181)→ A(通用 SIP 栈) |
+| JDK | 21 LTS |
+| 网络方案 | JDK NIO + 虚拟线程 |
+| 解析器 | 手写递归下降 |
+| 消息模型 | 不可变 `record` + `sealed interface` |
+| 模块隔离 | Maven 多模块 + JPMS `module-info.java` |
+| 鉴权 | RFC 7616(SHA-256 / SHA-512-256)+ MD5 兼容 |
+| 框架适配 | 暂不做(纯库优先) |
-```bash
+## 快速上手
+
+### 1. 解析 / 编码
+
+```java
+SipMessage msg = SipParser.parse(bytes);
+byte[] wire = SipEncoder.encode(msg);
+```
+
+### 2. 创建 UDP 传输
+
+```java
+NioUdpTransport transport = new NioUdpTransport(
+ new InetSocketAddress("0.0.0.0", 5060));
+transport.start().get();
+transport.listener(in -> {
+ SipMessage m = in.message();
+ // 处理 m
+});
+```
+
+### 3. 注册 + Digest 鉴权
+
+```java
+TimerScheduler timers = TimerScheduler.create();
+RegistrationClient client = new RegistrationClient(transport::send, timers);
+
+SipResponse response = client.register(
+ SipUri.parse("sip:registrar.example.com"),
+ SipUri.parse("sip:alice@example.com"),
+ SipUri.parse("sip:alice@pc.example.com"),
+ 3600,
+ new DigestCredentials("alice", "secret"),
+ registrarEndpoint
+).get();
+```
+
+### 4. 发起 OPTIONS 探测
+
+```java
+OptionsProbe probe = new OptionsProbe(transport::send, timers);
+SipResponse rsp = probe.probe(targetUri, localAor, peerEndpoint).get();
+```
+
+### 5. GB28181 Keepalive
+
+```java
+String xml = GbMessages.keepaliveXml(
+ GbMessages.nextSerialNumber(),
+ "34020000001320000001",
+ "OK");
+SipRequest message = GbMessages.manscdpMessage(
+ platformUri, deviceUri, platformUri,
+ fromTag, callId, cseq,
+ xml, "127.0.0.1:5060");
+transport.send(message, platformEndpoint);
+```
+
+## 路线图
+
+- [x] **Phase 0** — 骨架 + ADR-0001
+- [x] **Phase 1** — typed headers(Via / NameAddr / CSeq / Contact / Route / 简单整数头 / Call-ID)
+- [x] **Phase 2** — 解析器 + 编码器(含 SipUriParser)+ RFC 4475 闸门
+- [x] **Phase 3** — `sip-transport-nio`(UDP + TCP)
+- [x] **Phase 4** — 事务层四个 FSM + 时间轮(ADR-0002)
+- [x] **Phase 5** — Dialog + REGISTER + OPTIONS + Digest 鉴权(RFC 7616)
+- [x] **Phase 7** — GB28181 应用层(MANSCDP XML)
+- [ ] **Phase 8** — TLS / WebSocket 传输
+- [ ] **Phase 9** — 互操作测试(Kamailio / Asterisk + SIPp)
+- [ ] **Phase 10** — Spring Boot starter(可选)
+
+## 测试
+
+```
mvn -B verify
```
要求:**JDK 21+**、Maven 3.8+。
-## 路线图
+当前规模:**10 modules、169 个测试**(含 34 个合规动态测试)。
+
+### 合规闸门
-- [x] **Phase 0**:决策定型、骨架搭建(当前 PR)
-- [ ] **Phase 1**:`sip-message` 补齐 typed headers(Via/From/To/CSeq/Contact/Route)
-- [ ] **Phase 2**:`sip-codec` 手写解析器,**RFC 4475 fixture 全部通过**
-- [ ] **Phase 3**:`sip-codec` 编码器,roundtrip 测试
-- [ ] **Phase 4**:`sip-transport-nio` UDP 完整实现 + 集成测试
-- [ ] **Phase 5**:`sip-transaction` 四个 FSM + 时间轮
-- [ ] **Phase 6**:`sip-dialog` + REGISTER + Digest 鉴权
-- [ ] **Phase 7**:GB28181 适配(catalog、ptz、报警等扩展消息体)
-- [ ] **Phase 8**:`sip-transport-nio` TCP/TLS
-- [ ] **Phase 9**:互操作测试(Kamailio / Asterisk via Testcontainers + SIPp)
+- **RFC 4475** torture test fixtures 驻留在 `sip-compliance-tests/src/test/resources/torture/rfc4475/`
+- 每个新 fixture 通过 `tools/write_fixture.py` 从 `.fixture` 源转码到 byte-exact `.raw`,杜绝 CRLF 被无声破坏
+- 合规测试在 parser、parse→encode roundtrip、typed-header 解析三个层面分别验证
## 参考规范
-- RFC 3261 SIP 核心
-- RFC 3263 服务器定位(DNS NAPTR/SRV)
-- RFC 3264 Offer/Answer 模型
-- RFC 3581 rport
-- RFC 4566 / 8866 SDP
-- RFC 4475 / 5118 Torture Tests
-- RFC 5626 NAT outbound
-- RFC 6665 SUBSCRIBE/NOTIFY
-- RFC 7616 Digest Auth
-- GB/T 28181-2016 安防监控国标
+- **RFC 3261** SIP 核心
+- **RFC 3263** DNS 定位
+- **RFC 3264** Offer/Answer
+- **RFC 3581** rport (NAT)
+- **RFC 4475 / 5118** Torture Tests
+- **RFC 5626** NAT outbound
+- **RFC 5923** TCP 连接复用
+- **RFC 7616** Digest 鉴权(SHA-256)
+- **GB/T 28181-2016** 安防监控国标
+
+## 工程化
+
+- `.gitattributes` 强制 `*.raw` binary
+- `maven-enforcer-plugin` 强制 JDK 21、Maven 3.8+
+- `-Werror`、`-Xlint:all`、`-parameters`
+- GitHub Actions CI:`mvn -B verify` on JDK 21
+- 所有架构决策有对应 ADR 文件
## 许可证
diff --git a/docs/adr/0002-transaction-layer-design.md b/docs/adr/0002-transaction-layer-design.md
new file mode 100644
index 0000000..7c5e105
--- /dev/null
+++ b/docs/adr/0002-transaction-layer-design.md
@@ -0,0 +1,162 @@
+# ADR-0002:事务层设计 — 四 FSM + 时间轮 + 虚拟线程
+
+- **Status**:Accepted
+- **Date**:2026-05-11
+- **Deciders**:项目维护者
+- **Tags**:transaction, fsm, timing, virtual-threads
+
+---
+
+## 1. Context
+
+RFC 3261 §17 定义了 SIP 协议栈的核心机制 — 事务层。每个事务是一个有限状态机(FSM),跟踪一个请求-响应对的完整生命周期。RFC 同时定义了 11 个定时器(A 到 K),用于驱动重传、超时和"等待残留消息"。
+
+设计选择决定了:
+- 协议核心代码的**可读性**(同步 vs 异步表达)
+- **正确性**(状态迁移和定时器交互必须严格符合 RFC)
+- **可测试性**(无需真实 transport / 真实时间就能跑完所有迁移)
+- **可观测性**(FSM 状态、定时器、计数器对运维至关重要)
+
+---
+
+## 2. Decision
+
+### 2.1 四个独立 FSM 而非通用状态机
+
+**实装**:
+- `NonInviteClientTransaction` (§17.1.2)
+- `NonInviteServerTransaction` (§17.2.2)
+- `InviteClientTransaction` (§17.1.1)
+- `InviteServerTransaction` (§17.2.1)
+
+**理由**:
+- 这四个 FSM 在 RFC 中是**独立定义**的,状态名虽有重叠但语义和迁移不同
+- 强行"统一通用 FSM"会导致大量条件分支和 if-else,反而失去清晰度
+- 共享代码(如定时器调度、终止流程)通过 helper 提取,**不是 base class**
+
+### 2.2 时间轮:JDK `ScheduledExecutorService` + 虚拟线程派发
+
+**实装**:
+```java
+public interface TimerScheduler extends AutoCloseable {
+ Handle schedule(Runnable task, long delayMs);
+ interface Handle { void cancel(); boolean isCancelled(); }
+}
+```
+默认实现 = 单线程 `ScheduledExecutorService`(入队)+ 虚拟线程池(回调派发)。
+
+**理由**:
+- 即便繁忙服务器,活跃事务数也在几万量级,`ScheduledThreadPoolExecutor` 完全够用
+- 把回调切到虚拟线程,**永远不阻塞 timer wheel**
+- 接口很窄,将来真的撞上 PPS 瓶颈,可以替换为 `HashedWheelTimer`(Netty 风格),**调用方一行不改**
+
+### 2.3 事务键:`(branch, sent-by, method)`
+
+**实装**:
+```java
+public record TransactionKey(String branch, String sentBy, SipMethod method) { }
+```
+
+强制要求 `branch` 以 RFC 3261 magic cookie `z9hG4bK` 开头。
+
+**特殊处理**:ACK 匹配规则(§17.1.3 末段)
+- 2xx 的 ACK 是**新事务**,不应匹配现有
+- 非 2xx 的 ACK 必须命中原 INVITE 服务端事务
+- 通过 `TransactionKey.of(request, SipMethod.INVITE)` 显式让调用方表达意图
+
+**理由**:
+- 不支持 RFC 2543 兼容性 —— 现代 SIP 设备都用 RFC 3261 cookie
+- 三元组比纯 branch 更鲁棒(理论上 sent-by 可以是同一 branch 的"伪装防御")
+
+### 2.4 同步代码风格的 FSM(拥抱虚拟线程)
+
+**实装**:FSM 的状态迁移用 `AtomicReference` + CAS 表达,**没有事件循环、没有回调队列**。
+
+定时器触发回调跑在虚拟线程上,可以直接调用 `state.compareAndSet(...)` 而不必担心阻塞。
+
+**理由**:
+- SIP FSM 的状态迁移本质上是"事件→新状态"的映射,CAS 比 actor / state-machine 框架更简单
+- 调用 listener 是直接函数调用 —— 调用方可以在 listener 里同步处理 `SipResponse`,包括用 `CompletableFuture.get()` 等待下游
+- 这是 ADR-0001(虚拟线程)的直接收益
+
+### 2.5 TU 通过 listener 接口而非订阅
+
+**实装**:
+- `ClientTransactionListener.onProvisional / onFinal / onTimeout / onTransportError`
+- `ServerTransactionListener.onRequest / onRetransmit / onTerminate`
+
+**理由**:
+- 一个事务只有一个 TU 关心 — 不需要发布订阅
+- 单 listener 接口让回调签名是**编译期类型安全**的,比 EventBus / pub-sub 灵活但易错
+- 失败时 listener 抛异常被 FSM 静默捕获 + 日志,**不影响事务本身**
+
+### 2.6 与 transport 解耦:`MessageSender` SPI
+
+**实装**:
+```java
+@FunctionalInterface
+public interface MessageSender {
+ CompletableFuture send(SipMessage message, Endpoint peer);
+}
+```
+
+事务层依赖 `MessageSender`(单方法 SPI),**不依赖 `Transport`**。
+
+**理由**:
+- 单元测试用 `RecordingSender` 把所有发送捕获到 list,不需要真实 socket
+- 18 个事务测试 1.5 秒内跑完,**无任何 I/O**
+- 集成测试时把真实 `NioUdpTransport::send` 作为 lambda 传入
+
+---
+
+## 3. Consequences
+
+### 正面
+- FSM 实现紧贴 RFC §17 状态图,代码可对照 review
+- 单元测试简单(无需 mock framework,只需 RecordingSender)
+- 定时器内存开销小(每个事务约 3-5 个活跃定时器,单条目 < 200 bytes)
+- 与 transport 解耦使得未来切 Netty / 虚拟线程切回平台线程都不影响事务层
+
+### 负面
+- 4 个 FSM 类有 30-40% 共享逻辑(终止、CAS 状态迁移),用 helper 提取后还有少量重复
+- `ScheduledExecutorService` 在万级并发下定时器精度约 ±10ms(够用,但对 IMS 级运营商场景偏低)
+- 没有 transaction snapshot / replay 机制,崩溃后事务状态丢失(**生产 SIP 应用同样有这个语义**,不是 bug)
+
+### 中性
+- 不再借鉴 JAIN-SIP 的"通用 transaction state"风格,新人需要熟悉四个独立 FSM 各自的状态名
+
+---
+
+## 4. Key RFC 3261 §17 Behavior Captures
+
+| 行为 | 实装位置 |
+|------|---------|
+| INVITE 客户端收 2xx → 立即 Terminated(TU 负责 ACK) | `InviteClientTransaction.onResponse` |
+| INVITE 客户端收非 2xx → 自动构造发送 ACK(§17.1.1.3) | `InviteClientTransaction.sendAckForNonSuccess` |
+| INVITE 服务端收 ACK → Completed → Confirmed | `InviteServerTransaction.onRequest` |
+| Non-INVITE 服务端在 Completed 收 retransmit → 重发上一响应 | `NonInviteServerTransaction.onRequest` |
+| Timer A / E / G 指数退避,封顶 T2 | `Timing.timerA/E/G(attempt)` |
+| 可靠传输上 Timer D/I/J/K = 0 | `Timing.TIMER_*_MS_RELIABLE` |
+
+---
+
+## 5. When to Reconsider
+
+1. 撞上**单进程 100K+ 并发事务**且 ScheduledExecutorService 时间精度成为瓶颈 → 切换 HashedWheelTimer 实现 `TimerScheduler`
+2. 引入 **PRACK(RFC 3262)** / **UPDATE(RFC 3311)** —— 这两个会复用现有 FSM,但 PRACK 的"可靠 provisional"逻辑可能需要在 INVITE Server FSM 上扩展状态
+3. 引入**事务状态持久化**(崩溃恢复 / 集群迁移)—— 需要把 `AtomicReference` 改造为事件溯源
+
+---
+
+## 6. References
+
+- RFC 3261 §17 — Transactions
+- RFC 3261 §A — Timer values
+- RFC 3262 — PRACK (future)
+- ADR-0001 — Transport layer selection (虚拟线程基础)
+
+## 7. 变更记录
+
+| 日期 | 变更 | 作者 |
+| --- | --- | --- |
+| 2026-05-11 | 创建并记为 Accepted | 项目维护者 |
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 86fa3f7..0c04e43 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -26,3 +26,4 @@
| 编号 | 标题 | 状态 |
| ---- | ---- | ---- |
| [ADR-0001](0001-transport-layer-selection.md) | 传输层选型:JDK 21 虚拟线程 + NIO vs Netty | Accepted |
+| [ADR-0002](0002-transaction-layer-design.md) | 事务层设计:四 FSM + 时间轮 + 虚拟线程 | Accepted |