sp_maybe_compressed_blob/
lib.rs1use std::{
22 borrow::Cow,
23 io::{Read, Write},
24};
25
26const ZSTD_PREFIX: [u8; 8] = [82, 188, 83, 118, 70, 219, 142, 5];
31
32pub const CODE_BLOB_BOMB_LIMIT: usize = 50 * 1024 * 1024;
38
39#[derive(Debug, Clone, PartialEq, thiserror::Error)]
41pub enum Error {
42 #[error("Possible compression bomb encountered")]
44 PossibleBomb,
45 #[error("Blob had invalid format")]
47 Invalid,
48}
49
50fn read_from_decoder(
51 decoder: impl Read,
52 blob_len: usize,
53 bomb_limit: usize,
54) -> Result<Vec<u8>, Error> {
55 let mut decoder = decoder.take((bomb_limit + 1) as u64);
56
57 let mut buf = Vec::with_capacity(blob_len);
58 decoder.read_to_end(&mut buf).map_err(|_| Error::Invalid)?;
59
60 if buf.len() <= bomb_limit {
61 Ok(buf)
62 } else {
63 Err(Error::PossibleBomb)
64 }
65}
66
67fn decompress_zstd(blob: &[u8], bomb_limit: usize) -> Result<Vec<u8>, Error> {
68 let decoder = zstd::Decoder::new(blob).map_err(|_| Error::Invalid)?;
69
70 read_from_decoder(decoder, blob.len(), bomb_limit)
71}
72
73pub fn decompress(blob: &[u8], bomb_limit: usize) -> Result<Cow<'_, [u8]>, Error> {
76 if blob.starts_with(&ZSTD_PREFIX) {
77 decompress_zstd(&blob[ZSTD_PREFIX.len()..], bomb_limit).map(Into::into)
78 } else {
79 Ok(blob.into())
80 }
81}
82
83pub fn compress_weakly(blob: &[u8], bomb_limit: usize) -> Option<Vec<u8>> {
88 compress_with_level(blob, bomb_limit, 3)
89}
90
91pub fn compress_strongly(blob: &[u8], bomb_limit: usize) -> Option<Vec<u8>> {
96 compress_with_level(blob, bomb_limit, 22)
97}
98
99#[deprecated(
104 note = "Will be removed after June 2026. Use compress_strongly, compress_weakly or compress_with_level instead"
105)]
106pub fn compress(blob: &[u8], bomb_limit: usize) -> Option<Vec<u8>> {
107 compress_with_level(blob, bomb_limit, 3)
108}
109
110fn compress_with_level(blob: &[u8], bomb_limit: usize, level: i32) -> Option<Vec<u8>> {
117 if blob.len() > bomb_limit {
118 return None
119 }
120
121 let mut buf = ZSTD_PREFIX.to_vec();
122
123 {
124 let mut v = zstd::Encoder::new(&mut buf, level).ok()?.auto_finish();
125 v.write_all(blob).ok()?;
126 }
127
128 Some(buf)
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 const BOMB_LIMIT: usize = 10;
136
137 #[test]
138 fn refuse_to_encode_over_limit() {
139 let mut v = vec![0; BOMB_LIMIT + 1];
140 assert!(compress_weakly(&v, BOMB_LIMIT).is_none());
141 assert!(compress_strongly(&v, BOMB_LIMIT).is_none());
142
143 let _ = v.pop();
144 assert!(compress_weakly(&v, BOMB_LIMIT).is_some());
145 assert!(compress_strongly(&v, BOMB_LIMIT).is_some());
146 }
147
148 #[test]
149 fn compress_and_decompress() {
150 let v = vec![0; BOMB_LIMIT];
151
152 let compressed_weakly = compress_weakly(&v, BOMB_LIMIT).unwrap();
153 let compressed_strongly = compress_strongly(&v, BOMB_LIMIT).unwrap();
154
155 assert!(compressed_weakly.starts_with(&ZSTD_PREFIX));
156 assert!(compressed_strongly.starts_with(&ZSTD_PREFIX));
157
158 assert_eq!(&decompress(&compressed_weakly, BOMB_LIMIT).unwrap()[..], &v[..]);
159 assert_eq!(&decompress(&compressed_strongly, BOMB_LIMIT).unwrap()[..], &v[..]);
160 }
161
162 #[test]
163 fn decompresses_only_when_magic() {
164 let v = vec![0; BOMB_LIMIT + 1];
165
166 assert_eq!(&decompress(&v, BOMB_LIMIT).unwrap()[..], &v[..]);
167 }
168
169 #[test]
170 fn possible_bomb_fails() {
171 let encoded_bigger_than_bomb = vec![0; BOMB_LIMIT + 1];
172 let mut buf = ZSTD_PREFIX.to_vec();
173
174 {
175 let mut v = zstd::Encoder::new(&mut buf, 3).unwrap().auto_finish();
176 v.write_all(&encoded_bigger_than_bomb[..]).unwrap();
177 }
178
179 assert_eq!(decompress(&buf[..], BOMB_LIMIT).err(), Some(Error::PossibleBomb));
180 }
181}