Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 128 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 路线图](#路线图)。
[![JDK](https://img.shields.io/badge/JDK-21%2B-blue)]()
[![License](https://img.shields.io/badge/License-MIT-yellow)]()

---

## 设计原则

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` + 虚拟线程<br/>详见 [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 文件

## 许可证

Expand Down
162 changes: 162 additions & 0 deletions docs/adr/0002-transaction-layer-design.md
Original file line number Diff line number Diff line change
@@ -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<State>` + 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<Void> 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<State>` 改造为事件溯源

---

## 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 | 项目维护者 |
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Loading