Skip to content

Commit f4ca45c

Browse files
committed
refactor(codec): introduce shared BufferedEncoder and simplify streaming implementations
重构:引入共享 BufferedEncoder,简化流式实现 This major refactoring introduces deep modules for streaming encoding, reducing code duplication across all algorithm implementations. Changes: - Add BufferedEncoder in Go and Rust that encapsulates state machine logic - Add shared error conversion utilities (io_error_to_codec_error) - Add BufferedBitWriter for bit-level operations in Rust - Simplify all streaming implementations to use the shared infrastructure - Update lifecycle tests to cover new buffered encoder behavior - Add contract test documentation for streaming API patterns Impact: - ~1700 lines of duplicate code removed across all algorithms - Consistent state machine behavior guaranteed across implementations - Easier to add new algorithms - just implement EncodeFunc/DecodeFunc - Better separation of concerns between streaming logic and algorithm logic
1 parent c96d8b2 commit f4ca45c

30 files changed

Lines changed: 1571 additions & 1733 deletions

CONTEXT.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# CompressKit 领域词汇表
2+
3+
本文档定义 CompressKit 项目的核心领域概念,为架构讨论提供统一语言。
4+
5+
## 核心实体
6+
7+
### Magic Number(魔数)
8+
9+
二进制格式的标识符,出现在编码文件的开头,用于识别编码算法类型。
10+
11+
| 算法 | Magic Number | 说明 |
12+
|------|-------------|------|
13+
| Huffman | `HFMN` | 4 字节 ASCII |
14+
| Arithmetic | `AENC` | 4 字节 ASCII |
15+
| Range Coder | `RCNC` | 4 字节 ASCII |
16+
| RLE | `RLE\x00` | 4 字节,含空终止符 |
17+
18+
**设计决策:** Magic Number 不可更改,是跨语言二进制兼容性的基础。
19+
20+
### Frequency Table(频率表)
21+
22+
静态模型算法(Huffman、Arithmetic、Range)使用的符号频率数据结构。
23+
24+
- **格式:** 257 个 uint32 LE 值(256 字节值 + 1 EOF 标记)
25+
- **总大小:** 4 字节(符号计数)+ 257 × 4 字节 = 1032 字节
26+
- **编码顺序:** 符号 0-255(字节值),符号 256(EOF)
27+
28+
**约束:** 频率表格式跨语言统一,不可更改。
29+
30+
### Symbol Limit(符号限制)
31+
32+
编码器处理的符号数量上限。
33+
34+
- **值:** 257(256 字节值 + 1 EOF 符号)
35+
- **EOF 符号索引:** 256
36+
37+
### Streaming Layer(流式层)
38+
39+
提供增量处理能力的编码/解码接口,通过生命周期状态机管理。
40+
41+
**生命周期:**
42+
```
43+
READY → STREAMING → FLUSHING → FINISHED
44+
45+
ERROR(任意状态可转入)
46+
```
47+
48+
**核心接口:**
49+
- `Process(in []byte, out []byte)` - 增量处理输入
50+
- `Flush(out []byte)` - 刷新缓冲输出
51+
- `Finish(out []byte)` - 完成处理并写入结束标记
52+
- `Reset()` - 重置到 READY 状态
53+
- `State()` - 查询当前状态
54+
55+
**语义保证:**
56+
- `Process()` 在编码器中缓冲输入,在解码器中增量输出
57+
- `Flush()` 对 Huffman/Arithmetic/Range 是无操作(需要完整输入)
58+
- `Finish()` 触发最终编码/解码并写入输出
59+
60+
### Buffer Layer(缓冲层)
61+
62+
提供一次性处理完整数据的便捷 API。
63+
64+
**接口:**
65+
- `EncodeBuffer(input []byte) ([]byte, error)`
66+
- `DecodeBuffer(input []byte) ([]byte, error)`
67+
68+
**语义:** 等价于 `new encoder → process(input) → finish()`
69+
70+
### State Machine(状态机)
71+
72+
Streaming Layer 的核心控制逻辑,定义有效的状态转换。
73+
74+
**状态:**
75+
- `StateReady` - 初始状态,准备接收输入
76+
- `StateStreaming` - 正在处理输入
77+
- `StateFlushing` - 已刷新,等待完成
78+
- `StateFinished` - 处理完成,不可再接收输入
79+
- `StateError` - 错误状态,需要 Reset
80+
81+
**转换规则:**
82+
| 操作 | 有效前置状态 | 结果状态 |
83+
|------|-------------|---------|
84+
| Process | READY, STREAMING, FLUSHING | STREAMING |
85+
| Flush | READY, STREAMING, FLUSHING | FLUSHING |
86+
| Finish | READY, STREAMING, FLUSHING | FINISHED |
87+
| Reset | 任意 | READY |
88+
89+
## 安全边界
90+
91+
### Input Size Limit(输入大小限制)
92+
93+
- **上限:** 4 GiB
94+
- **目的:** 防止频率溢出和解压缩炸弹攻击
95+
96+
### Output Size Limit(输出大小限制)
97+
98+
- **上限:** 1 GiB(仅解码)
99+
- **目的:** 防止解压缩炸弹攻击
100+
101+
## 错误类型
102+
103+
### 标准错误码
104+
105+
| 错误 | 语义 | 恢复方式 |
106+
|------|------|---------|
107+
| `ErrBufTooSmall` | 输出缓冲区不足 | 使用更大缓冲区重试 |
108+
| `ErrTruncated` | 输入流过早结束 | 检查输入完整性 |
109+
| `ErrCorrupt` | 数据损坏或校验失败 | 输入数据无效 |
110+
| `ErrInvalidState` | 当前状态不支持此操作 | Reset 后重试 |
111+
| `ErrSizeLimit` | 超过安全限制 | 输入/输出过大 |
112+
| `ErrVersionUnsupported` | 不支持的版本 | 检查版本兼容性 |
113+
| `ErrUnknownAlgo` | 未知的算法标识 | 检查 Magic Number |
114+
115+
### 事务性保证
116+
117+
`ErrBufTooSmall` 返回时,内部状态保持不变,调用者可以使用更大的缓冲区重试。
118+
119+
## 算法特性
120+
121+
### Huffman 编码
122+
123+
- **类型:** 基于前缀码的熵编码
124+
- **模型:** 静态(需要完整输入构建频率表)
125+
- **输出:** 位流(需要位对齐处理)
126+
- **限制:** 需要完整输入才能编码
127+
128+
### Arithmetic 编码
129+
130+
- **类型:** 区间编码
131+
- **模型:** 静态
132+
- **精度:** 区间缩放到 2^24
133+
- **限制:** 需要完整输入才能编码
134+
135+
### Range Coder
136+
137+
- **类型:** 区间编码
138+
- **模型:** 静态
139+
- **已知问题:** 大文件(>500KB)性能下降,推荐限制在 100KB 以内
140+
141+
### RLE(Run-Length Encoding)
142+
143+
- **类型:** 游程编码
144+
- **格式:** `(count: uint32 LE, value: byte)` 对序列
145+
- **特点:** 不需要频率表,可增量处理
146+
147+
## 架构分层
148+
149+
```
150+
┌─────────────────────────────────────┐
151+
│ CLI Entry Point │ 命令行接口
152+
├─────────────────────────────────────┤
153+
│ Buffer Layer │ 便捷 API
154+
├─────────────────────────────────────┤
155+
│ Streaming Layer │ 生命周期管理
156+
├─────────────────────────────────────┤
157+
│ Algorithm Core │ 编码/解码逻辑
158+
└─────────────────────────────────────┘
159+
```
160+
161+
**调用流向:** CLI → Buffer Layer → Streaming Layer → Algorithm Core
162+
163+
## 跨语言一致性
164+
165+
### 必须一致的部分
166+
167+
1. **Magic Number** - 二进制格式标识
168+
2. **Frequency Table 格式** - 字节序、顺序、大小
169+
3. **状态机语义** - 状态转换规则
170+
4. **错误码语义** - 错误类型和恢复方式
171+
5. **安全限制** - 输入/输出大小上限
172+
173+
### 可不一致的部分
174+
175+
1. **内部实现** - 数据结构、算法优化
176+
2. **API 命名风格** - 遵循各语言惯例
177+
3. **错误消息文本** - 不影响错误码语义
178+
179+
## 文档层次
180+
181+
| 文档 | 用途 | 受众 |
182+
|------|------|------|
183+
| README.md | 项目入口 | 新用户 |
184+
| VitePress 文档 | 产品门户 | 最终用户 |
185+
| OpenSpec | 需求来源 | 开发者 |
186+
| CHANGELOG.md | 用户可见变更 | 用户 |
187+
| CONTEXT.md | 领域词汇 | 贡献者/AI |
188+
189+
## 参考资料
190+
191+
- `openspec/specs/core-architecture/spec.md` - 核心架构规范
192+
- `openspec/specs/encoding-project/spec.md` - 项目需求规范
193+
- `openspec/specs/cross-language-testing/spec.md` - 测试规范

algorithms/arithmetic/go/arithmetic.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,14 @@ func WriteFrequencies(w io.Writer, freq []uint32) error {
348348
func ReadFrequencies(r io.Reader) ([]uint32, error) {
349349
var count uint32
350350
if err := binary.Read(r, binary.LittleEndian, &count); err != nil {
351-
return nil, fmt.Errorf("failed to read frequency table: %w", err)
351+
return nil, codec.WrapError(codec.KindTruncated, "failed to read frequency table", err)
352352
}
353353
if count != uint32(SymbolLimit) {
354-
return nil, fmt.Errorf("invalid frequency table size: %d", count)
354+
return nil, codec.NewError(codec.KindCorrupt, fmt.Sprintf("invalid frequency table size: %d", count))
355355
}
356356
freq := make([]uint32, count)
357357
if err := binary.Read(r, binary.LittleEndian, freq); err != nil {
358-
return nil, fmt.Errorf("failed to read frequency table: %w", err)
358+
return nil, codec.WrapError(codec.KindTruncated, "failed to read frequency table", err)
359359
}
360360
return freq, nil
361361
}
@@ -405,7 +405,7 @@ func Decode(r io.Reader, w io.Writer) error {
405405

406406
magic := make([]byte, 4)
407407
if _, err := io.ReadFull(br, magic); err != nil || string(magic) != "AENC" {
408-
return fmt.Errorf("invalid input file format")
408+
return codec.NewError(codec.KindCorrupt, "invalid input file format")
409409
}
410410

411411
freq, err := ReadFrequencies(br)
@@ -426,7 +426,7 @@ func Decode(r io.Reader, w io.Writer) error {
426426
}
427427
totalWritten++
428428
if totalWritten > codec.MaxOutputSize {
429-
return fmt.Errorf("output size limit exceeded")
429+
return codec.NewError(codec.KindSizeLimit, "output size limit exceeded")
430430
}
431431
if err := bw.WriteByte(byte(sym)); err != nil {
432432
return err

0 commit comments

Comments
 (0)