1use constriction::stream::{
25 model::ContiguousCategoricalEntropyModel, queue::DefaultRangeDecoder,
26 queue::DefaultRangeEncoder, Decode, Encode,
27};
28
29const SCALE: u64 = 1 << 24;
31pub const MIX_FAST_CAP: u64 = 4096;
33pub const MIX_SLOW_CAP: u64 = 65536;
35const EWA_LR: f64 = 0.15;
37const COUNT_INC: u64 = 32;
39
40#[inline]
43pub fn mix_ctx_bucket(prev: u8) -> u8 {
44 match prev {
45 0 => 0,
46 1..=2 => 1,
47 3..=5 => 2,
48 6..=11 => 3,
49 12..=24 => 4,
50 25..=48 => 5,
51 49..=96 => 6,
52 _ => 7,
53 }
54}
55
56#[derive(Clone)]
58struct MixCtx {
59 fast: Vec<u64>,
60 slow: Vec<u64>,
61 log_w: f64,
62}
63
64impl MixCtx {
65 fn new() -> Self {
66 Self {
67 fast: vec![1; 256],
68 slow: vec![1; 256],
69 log_w: 0.0,
70 }
71 }
72
73 fn probs(&self) -> Vec<u32> {
76 let tf = self.fast.iter().sum::<u64>() as f64;
77 let ts = self.slow.iter().sum::<u64>() as f64;
78 let wf = 1.0 / (1.0 + (-self.log_w).exp());
79 let mut p: Vec<u64> = self
80 .fast
81 .iter()
82 .zip(&self.slow)
83 .map(|(&cf, &cs)| {
84 ((wf * (cf as f64 / tf) + (1.0 - wf) * (cs as f64 / ts)) * SCALE as f64) as u64
85 })
86 .collect();
87 let sum: u64 = p.iter().sum();
90 p[0] += SCALE - sum;
91 let mut dense = 0usize;
92 for (i, &v) in p.iter().enumerate() {
93 if v > p[dense] {
94 dense = i;
95 }
96 }
97 let zeros = p.iter().filter(|&&v| v == 0).count();
98 p[dense] = p[dense].max(1 + zeros as u64);
99 for v in p.iter_mut() {
100 if *v == 0 {
101 *v = 1;
102 }
103 }
104 p.into_iter().map(|v| v as u32).collect()
105 }
106
107 fn model(&self) -> Option<ContiguousCategoricalEntropyModel<u32, Vec<u32>, 24>> {
108 ContiguousCategoricalEntropyModel::<u32, Vec<u32>, 24>::from_nonzero_fixed_point_probabilities(
109 self.probs(),
110 false,
111 )
112 .ok()
113 }
114
115 fn step(&mut self, s: u8) {
116 let si = usize::from(s);
117 let tf = self.fast.iter().sum::<u64>() as f64;
118 let ts = self.slow.iter().sum::<u64>() as f64;
119 let pf = (self.fast[si] as f64 / tf).max(1e-9);
120 let ps = (self.slow[si] as f64 / ts).max(1e-9);
121 self.log_w = (self.log_w + EWA_LR * (pf.ln() - ps.ln())).clamp(-20.0, 20.0);
122 self.fast[si] += COUNT_INC;
123 self.slow[si] += COUNT_INC;
124 if self.fast.iter().sum::<u64>() > MIX_FAST_CAP {
125 for c in &mut self.fast {
126 *c = (*c + 1) >> 1;
127 }
128 }
129 if self.slow.iter().sum::<u64>() > MIX_SLOW_CAP {
130 for c in &mut self.slow {
131 *c = (*c + 1) >> 1;
132 }
133 }
134 }
135}
136
137pub fn mix_encode(data: &[u8]) -> Vec<u8> {
140 let mut ctxs: Vec<MixCtx> = (0..8).map(|_| MixCtx::new()).collect();
141 let mut enc = DefaultRangeEncoder::new();
142 let mut prev = 0u8;
143 for &b in data {
144 let c = mix_ctx_bucket(prev);
145 let m = ctxs[usize::from(c)]
146 .model()
147 .expect("mix model with all-nonzero probabilities is always buildable");
148 enc.encode_symbols(std::iter::once((usize::from(b), &m)))
149 .expect("mix encode symbol");
150 ctxs[usize::from(c)].step(b);
151 prev = b;
152 }
153 let compressed = enc.into_compressed().expect("mix seal");
154 let mut out = Vec::with_capacity(compressed.len() * 4 + 4);
158 out.extend_from_slice(
159 &u32::try_from(data.len())
160 .expect("stream len fits u32")
161 .to_le_bytes(),
162 );
163 for w in compressed {
164 out.extend_from_slice(&w.to_le_bytes());
165 }
166 out
167}
168
169pub fn mix_decode(data: &[u8], limit: usize) -> Result<Vec<u8>, crate::error::Error> {
172 if data.len() < 4 || !(data.len() - 4).is_multiple_of(4) {
174 return Err(crate::error::Error::fmt_msg("mix payload not word-aligned"));
175 }
176 let stream_len = usize::try_from(u32::from_le_bytes(data[..4].try_into().expect("4 bytes")))
177 .map_err(|_| crate::error::Error::fmt_msg("mix stream length"))?;
178 if stream_len > limit {
179 return Err(crate::error::Error::fmt_msg("bomb"));
180 }
181 let mut words = Vec::with_capacity((data.len() - 4) / 4);
182 for chunk in data[4..].chunks_exact(4) {
183 words.push(u32::from_le_bytes(chunk.try_into().expect("4 bytes")));
184 }
185 let mut dec = DefaultRangeDecoder::from_compressed(words)
186 .map_err(|e| crate::error::Error::fmt_msg(format!("mix decoder open: {e:?}")))?;
187 let mut ctxs: Vec<MixCtx> = (0..8).map(|_| MixCtx::new()).collect();
188 let mut out = Vec::with_capacity(stream_len);
189 let mut prev = 0u8;
190 for _i in 0..stream_len {
191 let c = usize::from(mix_ctx_bucket(prev));
192 let m = ctxs[c]
193 .model()
194 .ok_or_else(|| crate::error::Error::fmt_msg("mix model build failed"))?;
195 let s = dec
196 .decode_symbols(std::iter::once(&m))
197 .next()
198 .ok_or_else(|| crate::error::Error::fmt_msg("mix stream exhausted"))?
199 .map_err(|e| crate::error::Error::fmt_msg(format!("mix decode symbol: {e:?}")))?;
200 let s = u8::try_from(s)
201 .map_err(|_| crate::error::Error::fmt_msg("mix symbol out of byte range"))?;
202 ctxs[c].step(s);
203 out.push(s);
204 prev = s;
205 }
206 Ok(out)
207}