Skip to main content

flare_core/common/compression/
algorithms.rs

1//! 内置压缩算法实现
2//!
3//! 提供常用的压缩算法实现
4
5use super::traits::Compressor;
6use crate::common::error::{FlareError, Result};
7
8/// 压缩算法类型枚举
9///
10/// 支持内置算法和自定义算法扩展
11#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub enum CompressionAlgorithm {
13    /// 不使用压缩
14    None,
15    /// Gzip 压缩
16    Gzip,
17    /// Zstd 压缩(待实现)
18    Zstd,
19    /// 自定义压缩算法(通过字符串标识符)
20    ///
21    /// 使用此变体可以注册和使用自定义压缩算法
22    /// 自定义算法必须通过 `CompressionUtil::register_custom` 注册
23    ///
24    /// # 示例
25    /// ```rust,ignore
26    /// use flare_core::common::compression::{CompressionAlgorithm, CompressionUtil, Compressor};
27    /// use std::sync::Arc;
28    ///
29    /// // 注册自定义压缩器
30    /// struct MyCustomCompressor;
31    /// impl Compressor for MyCustomCompressor { /* ... */ }
32    ///
33    /// CompressionUtil::register_custom(Arc::new(MyCustomCompressor));
34    ///
35    /// // 使用自定义算法
36    /// let algo = CompressionAlgorithm::Custom("my_custom".to_string());
37    /// ```
38    Custom(String),
39}
40
41impl CompressionAlgorithm {
42    /// 从字符串转换为压缩算法
43    ///
44    /// 如果字符串匹配内置算法,返回对应的枚举值
45    /// 否则返回 `Custom(String)` 变体
46    ///
47    /// # 示例
48    /// ```rust
49    /// use flare_core::common::compression::CompressionAlgorithm;
50    ///
51    /// assert_eq!(CompressionAlgorithm::from_str("none"), Some(CompressionAlgorithm::None));
52    /// assert_eq!(CompressionAlgorithm::from_str("gzip"), Some(CompressionAlgorithm::Gzip));
53    /// assert_eq!(
54    ///     CompressionAlgorithm::from_str("my_custom"),
55    ///     Some(CompressionAlgorithm::Custom("my_custom".to_string()))
56    /// );
57    /// ```
58    #[allow(clippy::should_implement_trait)]
59    pub fn from_str(s: &str) -> Option<Self> {
60        match s.to_lowercase().as_str() {
61            "none" | "" => Some(CompressionAlgorithm::None),
62            "gzip" => Some(CompressionAlgorithm::Gzip),
63            "zstd" => Some(CompressionAlgorithm::Zstd),
64            custom => Some(CompressionAlgorithm::Custom(custom.to_string())),
65        }
66    }
67
68    /// 转换为字符串标识符
69    ///
70    /// 返回算法的字符串表示,可用于注册表查找
71    ///
72    /// # 示例
73    /// ```rust
74    /// use flare_core::common::compression::CompressionAlgorithm;
75    ///
76    /// assert_eq!(CompressionAlgorithm::None.as_str(), "none");
77    /// assert_eq!(CompressionAlgorithm::Gzip.as_str(), "gzip");
78    /// assert_eq!(CompressionAlgorithm::Custom("my_custom".to_string()).as_str(), "my_custom");
79    /// ```
80    pub fn as_str(&self) -> String {
81        match self {
82            CompressionAlgorithm::None => "none".to_string(),
83            CompressionAlgorithm::Gzip => "gzip".to_string(),
84            CompressionAlgorithm::Zstd => "zstd".to_string(),
85            CompressionAlgorithm::Custom(name) => name.clone(),
86        }
87    }
88
89    /// 检查是否是自定义算法
90    pub fn is_custom(&self) -> bool {
91        matches!(self, CompressionAlgorithm::Custom(_))
92    }
93
94    /// 获取自定义算法名称(如果是自定义算法)
95    pub fn custom_name(&self) -> Option<&str> {
96        match self {
97            CompressionAlgorithm::Custom(name) => Some(name),
98            _ => None,
99        }
100    }
101}
102
103/// 无压缩器(直通)
104pub struct NoCompressor;
105
106impl Compressor for NoCompressor {
107    fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
108        Ok(data.to_vec())
109    }
110
111    fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
112        Ok(data.to_vec())
113    }
114
115    fn algorithm(&self) -> CompressionAlgorithm {
116        CompressionAlgorithm::None
117    }
118
119    fn name(&self) -> &'static str {
120        "none"
121    }
122
123    fn can_detect(&self, _data: &[u8]) -> bool {
124        // 无压缩不检测,总是作为后备选项
125        false
126    }
127}
128
129/// 解压输出上限:防 gzip 解压炸弹(恶意小包膨胀致 OOM)。与传输帧上限同量级。
130#[cfg(feature = "compression-gzip")]
131const MAX_GZIP_DECOMPRESSED_LEN: u64 = 16 * 1024 * 1024;
132
133/// Gzip 压缩器
134pub struct GzipCompressor;
135
136impl Compressor for GzipCompressor {
137    fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
138        #[cfg(feature = "compression-gzip")]
139        {
140            use flate2::Compression;
141            use flate2::write::GzEncoder;
142            use std::io::Write;
143
144            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
145            encoder.write_all(data).map_err(|e| {
146                FlareError::encoding_error(format!("Gzip compression failed: {}", e))
147            })?;
148            encoder.finish().map_err(|e| {
149                FlareError::encoding_error(format!("Gzip compression finish failed: {}", e))
150            })
151        }
152
153        #[cfg(not(feature = "compression-gzip"))]
154        {
155            let _ = data;
156            Err(FlareError::operation_not_supported(
157                "gzip compression feature is disabled",
158            ))
159        }
160    }
161
162    fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
163        #[cfg(feature = "compression-gzip")]
164        {
165            use flate2::read::GzDecoder;
166            use std::io::Read;
167
168            // 防解压炸弹:限制解压输出上限,避免恶意小包膨胀致 OOM(客户端与服务端共用此路径)。
169            let mut decoder = GzDecoder::new(data).take(MAX_GZIP_DECOMPRESSED_LEN + 1);
170            let mut decompressed = Vec::new();
171            decoder.read_to_end(&mut decompressed).map_err(|e| {
172                FlareError::encoding_error(format!("Gzip decompression failed: {}", e))
173            })?;
174            if decompressed.len() as u64 > MAX_GZIP_DECOMPRESSED_LEN {
175                return Err(FlareError::encoding_error(format!(
176                    "Gzip decompressed payload exceeds limit ({MAX_GZIP_DECOMPRESSED_LEN} bytes)"
177                )));
178            }
179            Ok(decompressed)
180        }
181
182        #[cfg(not(feature = "compression-gzip"))]
183        {
184            let _ = data;
185            Err(FlareError::operation_not_supported(
186                "gzip compression feature is disabled",
187            ))
188        }
189    }
190
191    fn algorithm(&self) -> CompressionAlgorithm {
192        CompressionAlgorithm::Gzip
193    }
194
195    fn name(&self) -> &'static str {
196        "gzip"
197    }
198
199    fn can_detect(&self, data: &[u8]) -> bool {
200        // Gzip 魔数: 0x1f 0x8b
201        data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b
202    }
203}
204
205#[cfg(all(test, feature = "compression-gzip"))]
206mod gzip_decompress_limit_tests {
207    use super::GzipCompressor;
208    use crate::common::compression::traits::Compressor;
209
210    #[test]
211    fn roundtrip_under_limit_is_unchanged() {
212        let c = GzipCompressor;
213        let data = vec![7u8; 1024 * 1024]; // 1MB,正常负载
214        let compressed = c.compress(&data).unwrap();
215        let restored = c.decompress(&compressed).unwrap();
216        assert_eq!(restored, data);
217    }
218
219    #[test]
220    fn decompress_rejects_zip_bomb_over_limit() {
221        let c = GzipCompressor;
222        // 17MB 高可压缩数据 → 解压超过 16MB 上限,必须被拒,避免 OOM。
223        let data = vec![0u8; 17 * 1024 * 1024];
224        let compressed = c.compress(&data).unwrap();
225        assert!(
226            compressed.len() < 1024 * 1024,
227            "bomb payload should compress small"
228        );
229        assert!(
230            c.decompress(&compressed).is_err(),
231            "decompressed payload exceeding the limit must be rejected"
232        );
233    }
234}