Skip to main content

zstd_zero/
fse.rs

1use crate::bitstream::{BackwardBits, ForwardBits};
2use crate::DecodeError;
3
4pub(crate) const MAX_TABLE_LOG: u8 = 9;
5pub(crate) const MAX_SYMBOLS: usize = 256;
6
7/// An initialized entropy-table slot; the decoder manages its contents.
8#[derive(Clone, Copy, Debug, Default)]
9pub struct Entry {
10    pub(crate) baseline: u16,
11    pub(crate) bits: u8,
12    pub(crate) symbol: u8,
13}
14
15impl Entry {
16    /// Create an empty slot, including for statically allocated workspaces.
17    pub const fn new() -> Self {
18        Self {
19            baseline: 0,
20            bits: 0,
21            symbol: 0,
22        }
23    }
24}
25
26pub(crate) struct Table<'a> {
27    entries: &'a mut [Entry],
28    len: usize,
29    log: u8,
30    valid: bool,
31}
32
33impl<'a> Table<'a> {
34    pub(crate) fn new(entries: &'a mut [Entry]) -> Self {
35        Self {
36            entries,
37            len: 0,
38            log: 0,
39            valid: false,
40        }
41    }
42
43    pub(crate) fn reset(&mut self) {
44        self.len = 0;
45        self.log = 0;
46        self.valid = false;
47    }
48
49    pub(crate) fn is_valid(&self) -> bool {
50        self.valid
51    }
52
53    pub(crate) fn log(&self) -> u8 {
54        self.log
55    }
56
57    pub(crate) fn symbol(&self, state: u32) -> Result<u8, DecodeError> {
58        self.entry(state).map(|entry| entry.symbol)
59    }
60
61    pub(crate) fn update(
62        &self,
63        state: &mut u32,
64        bits: &mut BackwardBits<'_>,
65    ) -> Result<(), DecodeError> {
66        let entry = self.entry(*state)?;
67        *state = entry.baseline as u32 + bits.read(entry.bits)?;
68        Ok(())
69    }
70
71    pub(crate) fn entry(&self, state: u32) -> Result<Entry, DecodeError> {
72        if !self.valid || state as usize >= self.len {
73            return Err(DecodeError::InvalidEntropyTable);
74        }
75        Ok(self.entries[state as usize])
76    }
77
78    pub(crate) fn build_rle(&mut self, symbol: u8) {
79        self.entries[0] = Entry {
80            baseline: 0,
81            bits: 0,
82            symbol,
83        };
84        self.len = 1;
85        self.log = 0;
86        self.valid = true;
87    }
88
89    pub(crate) fn build(
90        &mut self,
91        probabilities: &[i16],
92        table_log: u8,
93        scratch: &mut [i16],
94    ) -> Result<(), DecodeError> {
95        let workspace = scratch
96            .get_mut(..probabilities.len())
97            .ok_or(DecodeError::InvalidEntropyTable)?;
98        workspace.copy_from_slice(probabilities);
99        self.build_in_place(workspace, table_log)
100    }
101
102    fn build_in_place(
103        &mut self,
104        probabilities: &mut [i16],
105        table_log: u8,
106    ) -> Result<(), DecodeError> {
107        if table_log > MAX_TABLE_LOG
108            || probabilities.is_empty()
109            || probabilities.len() > MAX_SYMBOLS
110        {
111            return Err(DecodeError::InvalidEntropyTable);
112        }
113        if table_log == 0 {
114            let symbol = probabilities
115                .iter()
116                .position(|probability| *probability == 1)
117                .ok_or(DecodeError::InvalidEntropyTable)?;
118            self.build_rle(symbol as u8);
119            return Ok(());
120        }
121
122        let table_size = 1usize << table_log;
123        let mut total = 0usize;
124        for probability in probabilities.iter() {
125            total = total
126                .checked_add(if *probability == -1 {
127                    1
128                } else if *probability > 0 {
129                    *probability as usize
130                } else {
131                    0
132                })
133                .ok_or(DecodeError::ArithmeticOverflow)?;
134        }
135        if total != table_size {
136            return Err(DecodeError::InvalidEntropyTable);
137        }
138
139        self.entries[..table_size].fill(Entry::default());
140        let mut high = table_size;
141        for (symbol, probability) in probabilities.iter().enumerate() {
142            if *probability == -1 {
143                high = high
144                    .checked_sub(1)
145                    .ok_or(DecodeError::InvalidEntropyTable)?;
146                self.entries[high].symbol = symbol as u8;
147            }
148        }
149
150        let step = (table_size >> 1) + (table_size >> 3) + 3;
151        let mask = table_size - 1;
152        let mut position = 0usize;
153        for (symbol, probability) in probabilities.iter().enumerate() {
154            if *probability <= 0 {
155                continue;
156            }
157            for _ in 0..*probability as usize {
158                if position >= high {
159                    return Err(DecodeError::InvalidEntropyTable);
160                }
161                self.entries[position].symbol = symbol as u8;
162                position = (position + step) & mask;
163                while position >= high {
164                    position = (position + step) & mask;
165                }
166            }
167        }
168        if position != 0 {
169            return Err(DecodeError::InvalidEntropyTable);
170        }
171
172        // Symbol spreading no longer needs the probabilities. Reuse their
173        // storage for next-state counters (at most twice the 512-entry table).
174        for probability in probabilities.iter_mut() {
175            *probability = match *probability {
176                -1 => 1,
177                value => value.max(0),
178            };
179        }
180        let next = probabilities;
181        for entry in &mut self.entries[..table_size] {
182            let symbol = entry.symbol as usize;
183            let state = next[symbol] as u16;
184            if state == 0 {
185                return Err(DecodeError::InvalidEntropyTable);
186            }
187            // The validated probabilities sum to at most 512. A symbol's
188            // counter starts at its count and advances once per table slot,
189            // so even the final increment is at most 1024 and fits in i16.
190            next[symbol] += 1;
191            let floor_log = (u16::BITS - 1 - state.leading_zeros()) as u8;
192            let bits = table_log - floor_log;
193            entry.bits = bits;
194            entry.baseline = ((state as u32) << bits)
195                .checked_sub(table_size as u32)
196                .ok_or(DecodeError::InvalidEntropyTable)? as u16;
197        }
198
199        self.len = table_size;
200        self.log = table_log;
201        self.valid = true;
202        Ok(())
203    }
204
205    pub(crate) fn read_description(
206        &mut self,
207        input: &[u8],
208        max_symbol: usize,
209        max_log: u8,
210        scratch: &mut [i16],
211    ) -> Result<usize, DecodeError> {
212        if input.is_empty() || max_symbol >= MAX_SYMBOLS {
213            return Err(DecodeError::InvalidEntropyTable);
214        }
215        let mut bits = ForwardBits::new(input, 0);
216        let table_log = bits.read(4)? as u8 + 5;
217        if table_log > max_log || table_log > MAX_TABLE_LOG {
218            return Err(DecodeError::InvalidEntropyTable);
219        }
220
221        let expected = 1u32 << table_log;
222        let mut accumulated = 0u32;
223        let probabilities = scratch
224            .get_mut(..MAX_SYMBOLS)
225            .ok_or(DecodeError::InvalidEntropyTable)?;
226        // Zero-run symbols must not inherit counters from a previous table.
227        probabilities.fill(0);
228        let mut symbols = 0usize;
229        while accumulated < expected {
230            if symbols > max_symbol {
231                return Err(DecodeError::InvalidEntropyTable);
232            }
233            let remaining = expected - accumulated + 1;
234            let width = (32 - remaining.leading_zeros()) as u8;
235            let unchecked = bits.read(width)?;
236            let low_threshold = (1u32 << width) - 1 - remaining;
237            let low_mask = (1u32 << (width - 1)) - 1;
238            let low = unchecked & low_mask;
239            let value = if low < low_threshold {
240                bits.rewind(1)?;
241                low
242            } else if unchecked > low_mask {
243                unchecked - low_threshold
244            } else {
245                unchecked
246            };
247            let probability = value as i32 - 1;
248            probabilities[symbols] = probability as i16;
249            symbols += 1;
250            match probability {
251                -1 => accumulated += 1,
252                value if value > 0 => accumulated += value as u32,
253                0 => loop {
254                    let zeros = bits.read(2)? as usize;
255                    if symbols + zeros > max_symbol + 1 {
256                        return Err(DecodeError::InvalidEntropyTable);
257                    }
258                    symbols += zeros;
259                    if zeros != 3 {
260                        break;
261                    }
262                },
263                _ => return Err(DecodeError::InvalidEntropyTable),
264            }
265        }
266        if accumulated != expected {
267            return Err(DecodeError::InvalidEntropyTable);
268        }
269        self.build_in_place(&mut probabilities[..symbols], table_log)?;
270        Ok(bits.position().div_ceil(8))
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn shared_counters_cover_the_largest_table_and_ignore_dirty_storage() {
280        let mut entries = std::vec![Entry::new(); 512];
281        let mut scratch = [i16::MIN; MAX_SYMBOLS];
282        let mut table = Table::new(&mut entries);
283        table.build(&[512], 9, &mut scratch).unwrap();
284        for state in 0..512 {
285            let entry = table.entry(state).unwrap();
286            assert_eq!(
287                (entry.symbol, entry.bits, entry.baseline),
288                (0, 0, state as u16)
289            );
290        }
291        // Reusing dirty counters must preserve zero-probability symbols and
292        // the special -1 probability without changing the predefined input.
293        let probabilities = [16, 0, -1, 15];
294        table.build(&probabilities, 5, &mut scratch).unwrap();
295        let mut counts = [0; 4];
296        for state in 0..32 {
297            counts[table.entry(state).unwrap().symbol as usize] += 1;
298        }
299        assert_eq!(counts, [16, 0, 1, 15]);
300        assert_eq!(probabilities, [16, 0, -1, 15]);
301    }
302}