Skip to main content

vole_document/encode/
candidates.rs

1//! Candidate generators.
2//!
3//! Each generator proposes a bounded, deterministic reconstruction hypothesis.
4//! Generators are cheap to decline and never trusted because their logic
5//! "looks obvious": every candidate reaches the common court.
6
7use crate::SOURCE_FORMAT_OPAQUE;
8use crate::adapter::opaque;
9use crate::container::{Descriptor, UNIVERSE};
10use crate::dra::{Op, Program};
11#[cfg(feature = "rans")]
12use crate::entropy::{
13    CODER_ORDER0_BYTE_RANS, CODER_VERSION_1, EntropyChannelDescriptor, EntropyModel, encode_channel,
14};
15use crate::error::Result;
16use crate::integrity::sha256;
17use crate::limits::Limits;
18
19/// Stable candidate identity. The discriminant order is the final
20/// deterministic tie-breaker when prices and work are equal.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
22#[repr(u8)]
23pub enum CandidateKind {
24    /// Single literal object, literal program (Phase 1 floor).
25    Raw = 0,
26    /// Run-length generated repeats (Phase 2).
27    Rle = 1,
28    /// Typed byte rANS entropy channel (Phase 2).
29    ByteRans = 2,
30    /// PDF physical span partition as literal-span DRA ops (Phase 3).
31    PdfPhysical = 3,
32    /// PDF typed lexical channels, each entropy-coded (Phase 4).
33    PdfChannels = 4,
34    /// PDF classic cross-reference offsets regenerated from marked positions
35    /// (Phase 5).
36    PdfLayout = 5,
37    /// PDF layout plan (data object + item table) carried through two rANS
38    /// entropy channels (Phase 5.8).
39    PdfLayoutRans = 6,
40}
41
42impl CandidateKind {
43    /// Stable short name for reports and receipts.
44    pub const fn name(self) -> &'static str {
45        match self {
46            CandidateKind::Raw => "RAW",
47            CandidateKind::Rle => "RLE",
48            CandidateKind::ByteRans => "BYTE_RANS",
49            CandidateKind::PdfPhysical => "PDF_PHYSICAL",
50            CandidateKind::PdfChannels => "PDF_CHANNELS",
51            CandidateKind::PdfLayout => "PDF_LAYOUT",
52            CandidateKind::PdfLayoutRans => "PDF_LAYOUT_RANS",
53        }
54    }
55}
56
57/// A proposed descriptor together with its originating family.
58#[derive(Debug, Clone)]
59pub struct Candidate {
60    /// Which family produced this proposal.
61    pub kind: CandidateKind,
62    /// The proposed descriptor.
63    pub descriptor: Descriptor,
64}
65
66/// Generate the complete bounded candidate set for `input`.
67///
68/// Every candidate that currently applies is returned: RAW (always), then RLE,
69/// BYTE_RANS, PDF_PHYSICAL, PDF_CHANNELS, and PDF_LAYOUT, each only when its
70/// generator can express the input within `limits`. Order is deterministic and
71/// matches the [`CandidateKind`] discriminant order, so the court's final
72/// tie-break is stable.
73///
74/// This is the honest ablation surface: forcing a single kind must select from
75/// exactly the same set the unforced court would have priced.
76pub fn propose_all(input: &[u8], limits: Limits) -> Result<Vec<Candidate>> {
77    let mut out = vec![Candidate {
78        kind: CandidateKind::Raw,
79        descriptor: opaque::propose(input, limits)?,
80    }];
81    if let Some(rle) = propose_rle(input, limits)? {
82        out.push(rle);
83    }
84    #[cfg(feature = "rans")]
85    if let Some(byte_rans) = propose_byte_rans(input, limits)? {
86        out.push(byte_rans);
87    }
88    if let Some(pdf) = crate::adapter::pdf::propose_pdf(input, limits)? {
89        out.push(pdf);
90    }
91    #[cfg(feature = "rans")]
92    if let Some(pdf_channels) = crate::adapter::pdf::propose_pdf_channels(input, limits)? {
93        out.push(pdf_channels);
94    }
95    if let Some(pdf_layout) = crate::adapter::pdf::propose_pdf_layout(input, limits)? {
96        out.push(pdf_layout);
97    }
98    #[cfg(feature = "rans")]
99    if let Some(pdf_layout_rans) = crate::adapter::pdf::propose_pdf_layout_rans(input, limits)? {
100        out.push(pdf_layout_rans);
101    }
102    Ok(out)
103}
104
105/// Generate the bounded candidate set for `input`.
106///
107/// Thin alias for [`propose_all`] kept for existing callers; it proposes exactly
108/// the same set in the same order.
109pub fn propose(input: &[u8], limits: Limits) -> Result<Vec<Candidate>> {
110    propose_all(input, limits)
111}
112
113/// Propose an inline-run + `REPEAT_LAST` representation of `input`.
114///
115/// Each maximal run of equal bytes becomes one `INLINE` of that single byte,
116/// optionally followed by a `REPEAT_LAST` repeating it `length - 1` more times.
117/// The sequence is therefore always `INLINE, REPEAT_LAST, INLINE, ...`, so no
118/// two `REPEAT_LAST` instructions are ever adjacent. `objects` is empty because
119/// every byte is carried in the graph.
120///
121/// Returns `Ok(None)`, declining honestly, when the run list cannot be
122/// expressed within `limits`: the program would need more instructions than
123/// `max_graph_ops`, or a run is too long to reconstruct with a single
124/// `REPEAT_LAST` (`length - 1` exceeds `max_repeat_count` or `u32::MAX`).
125pub fn propose_rle(input: &[u8], limits: Limits) -> Result<Option<Candidate>> {
126    // Maximal runs of equal bytes: (byte value, run length).
127    let mut runs: Vec<(u8, u64)> = Vec::new();
128    for &b in input {
129        match runs.last_mut() {
130            Some((last, len)) if *last == b => *len += 1,
131            _ => runs.push((b, 1)),
132        }
133    }
134
135    // Worst case is one INLINE plus one REPEAT_LAST per run.
136    if runs.len().saturating_mul(2) > limits.max_graph_ops as usize {
137        return Ok(None);
138    }
139
140    // A run must be reconstructible by a single INLINE + REPEAT_LAST pair.
141    for &(_, length) in &runs {
142        let extra = length - 1;
143        if extra > limits.max_repeat_count || extra > u32::MAX as u64 {
144            return Ok(None);
145        }
146    }
147
148    let mut ops = Vec::with_capacity(runs.len() * 2);
149    for &(byte, length) in &runs {
150        ops.push(Op::Inline { bytes: vec![byte] });
151        if length > 1 {
152            ops.push(Op::RepeatLast {
153                count: (length - 1) as u32,
154            });
155        }
156    }
157
158    let descriptor = Descriptor {
159        universe: UNIVERSE.to_string(),
160        source_format: SOURCE_FORMAT_OPAQUE,
161        format_basis: "opaque;rle-runs".to_string(),
162        models: vec![],
163        channels: vec![],
164        objects: vec![],
165        program: Program::new(ops),
166        source_sha256: sha256(input),
167        source_len: input.len() as u64,
168    };
169    Ok(Some(Candidate {
170        kind: CandidateKind::Rle,
171        descriptor,
172    }))
173}
174
175/// Propose an order-0 byte-rANS representation of the whole `input` as one
176/// entropy channel.
177///
178/// The 256-entry normalized model and the channel header are fully serialized
179/// and charged by the court, so this candidate only wins when order-0 coding
180/// recovers more bytes than the model costs. Declines (`Ok(None)`) honestly:
181///
182/// - empty input: RAW is trivially smaller and there is nothing to code;
183/// - `input.len() > limits.max_channel_symbols`: a single channel cannot carry
184///   it, so the candidate is not expressible within the declared bounds.
185///
186/// Determinism follows from the pure `from_counts` normalizer and from
187/// `encode_channel`, which both depend only on their inputs.
188#[cfg(feature = "rans")]
189pub fn propose_byte_rans(input: &[u8], limits: Limits) -> Result<Option<Candidate>> {
190    if input.is_empty() || input.len() as u64 > limits.max_channel_symbols {
191        return Ok(None);
192    }
193
194    let mut counts = [0u64; 256];
195    for &b in input {
196        counts[b as usize] += 1;
197    }
198    let model = EntropyModel::from_counts(&counts, 12)?;
199    let capsule = encode_channel(&model, input)?;
200
201    let channel = EntropyChannelDescriptor {
202        coder: CODER_ORDER0_BYTE_RANS,
203        coder_version: CODER_VERSION_1,
204        scale_bits: model.scale_bits,
205        lane_count: 1,
206        model_id: 0,
207        symbol_count: capsule.symbol_count,
208        decoded_length: capsule.decoded_length,
209        initial_state: capsule.initial_state,
210        payload: capsule.payload,
211    };
212
213    let descriptor = Descriptor {
214        universe: UNIVERSE.to_string(),
215        source_format: SOURCE_FORMAT_OPAQUE,
216        format_basis: "opaque;byte-rans".to_string(),
217        models: vec![model],
218        channels: vec![channel],
219        objects: vec![],
220        program: Program::new(vec![Op::DecodeChannel { channel_id: 0 }]),
221        source_sha256: sha256(input),
222        source_len: input.len() as u64,
223    };
224
225    Ok(Some(Candidate {
226        kind: CandidateKind::ByteRans,
227        descriptor,
228    }))
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    /// Deterministic xorshift64 PRNG for incompressible test data.
236    fn xorshift64(state: &mut u64) -> u64 {
237        let mut x = *state;
238        x ^= x << 13;
239        x ^= x >> 7;
240        x ^= x << 17;
241        *state = x;
242        x
243    }
244
245    fn xorshift_bytes(n: usize, seed: u64) -> Vec<u8> {
246        let mut state = seed | 1; // avoid the zero fixed point
247        let mut out = Vec::with_capacity(n + 8);
248        while out.len() < n {
249            out.extend_from_slice(&xorshift64(&mut state).to_le_bytes());
250        }
251        out.truncate(n);
252        out
253    }
254
255    fn assert_exact(bytes: &[u8], input: &[u8], limits: Limits) {
256        let (out, parsed) = crate::materialize::decode_to_bytes(bytes, limits).unwrap();
257        assert_eq!(out, input, "materialized bytes must equal the source");
258        assert_eq!(out.len() as u64, parsed.descriptor.source_len);
259        assert_eq!(
260            crate::integrity::sha256(&out),
261            crate::integrity::sha256(input)
262        );
263    }
264
265    #[test]
266    fn rle_wins_on_zeros() {
267        let input = vec![0u8; 65536];
268        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
269        assert_eq!(report.kind, CandidateKind::Rle);
270        assert!(
271            report.encoded_len < 512,
272            "RLE encoding of zeros was {} bytes",
273            report.encoded_len
274        );
275        assert_exact(&bytes, &input, Limits::DEFAULT);
276    }
277
278    #[test]
279    fn rle_wins_on_long_runs() {
280        let mut input = Vec::new();
281        input.extend_from_slice(&[0xAAu8; 1000]);
282        input.extend_from_slice(&[0x00, 0x01]);
283        input.extend_from_slice(&[0x55u8; 5000]);
284        input.extend_from_slice(b"tail");
285
286        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
287        assert_eq!(report.kind, CandidateKind::Rle);
288        assert!(report.encoded_len < 512);
289        assert_exact(&bytes, &input, Limits::DEFAULT);
290    }
291
292    #[test]
293    fn raw_wins_on_incompressible() {
294        let input = xorshift_bytes(64 * 1024, 0x9E37_79B9_7F4A_7C15);
295        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
296        assert_eq!(
297            report.kind,
298            CandidateKind::Raw,
299            "incompressible data must be stored RAW"
300        );
301        assert_exact(&bytes, &input, Limits::DEFAULT);
302    }
303
304    #[test]
305    fn single_byte_is_rle() {
306        let input = [7u8];
307        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
308        assert_eq!(report.kind, CandidateKind::Rle);
309        assert!(report.encoded_len < 512);
310        assert_exact(&bytes, &input, Limits::DEFAULT);
311    }
312
313    #[test]
314    fn rle_declines_when_graph_too_big() {
315        let input = vec![0u8; 100];
316        let limits = Limits {
317            max_graph_ops: 1,
318            ..Limits::DEFAULT
319        };
320        assert!(
321            propose_rle(&input, limits).unwrap().is_none(),
322            "100 single-byte runs cannot fit in a one-op graph"
323        );
324        // With RLE declined, the court must fall back to another exact lane.
325        // (The compact v2 model makes BYTE_RANS the winner here; whether it or
326        // RAW wins depends on model cost, so only "not RLE" is pinned.)
327        let (bytes, report) = crate::encode::encode(&input, limits).unwrap();
328        assert_ne!(report.kind, CandidateKind::Rle);
329        assert_exact(&bytes, &input, limits);
330    }
331
332    #[cfg(feature = "rans")]
333    #[test]
334    fn byte_rans_wins_on_text() {
335        let input = b"The quick brown fox jumps over the lazy dog. ".repeat(1500);
336        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
337        assert_eq!(report.kind, CandidateKind::ByteRans);
338        assert!(
339            report.encoded_len < report.source_len,
340            "order-0 rANS must beat RAW on low-entropy text: {} vs {}",
341            report.encoded_len,
342            report.source_len
343        );
344        assert_exact(&bytes, &input, Limits::DEFAULT);
345    }
346
347    #[cfg(feature = "rans")]
348    #[test]
349    fn byte_rans_exact_on_all_byte_values() {
350        // Exercise every byte value through the channel. A uniform `0..=255`
351        // stream is incompressible at order 0 (and the 516-byte model makes rANS
352        // lose to RAW), so the body is a skewed, deterministic stream whose head
353        // guarantees all 256 symbols are present and nonzero-frequency.
354        let mut input: Vec<u8> = (0..=255u8).collect();
355        let mut state = 0x2545_F491_4F6C_DD1D;
356        while input.len() < 64 * 1024 {
357            let r = xorshift64(&mut state);
358            if !r.is_multiple_of(8) {
359                input.push(0x00);
360            } else {
361                input.push((r >> 32) as u8);
362            }
363        }
364        input.truncate(64 * 1024);
365
366        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
367        assert_eq!(report.kind, CandidateKind::ByteRans);
368        assert_exact(&bytes, &input, Limits::DEFAULT);
369    }
370
371    #[test]
372    fn byte_rans_is_deterministic() {
373        let input = b"deterministic byte rANS stream ".repeat(600);
374        let (a, _) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
375        let (b, _) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
376        assert_eq!(a, b, ".voldoc bytes must be identical across encodes");
377    }
378
379    #[cfg(feature = "rans")]
380    #[test]
381    fn byte_rans_declines_empty() {
382        assert!(
383            propose_byte_rans(&[], Limits::DEFAULT).unwrap().is_none(),
384            "empty input must decline: RAW is trivially smaller"
385        );
386    }
387
388    #[cfg(feature = "rans")]
389    #[test]
390    fn model_cost_is_charged() {
391        // On a two-byte input the 516-byte canonical model cannot pay for
392        // itself, so BYTE_RANS must lose the complete-cost court. This documents
393        // model-cost honesty.
394        let input = b"ab";
395        let (bytes, report) = crate::encode::encode(input, Limits::DEFAULT).unwrap();
396        assert_ne!(report.kind, CandidateKind::ByteRans);
397        assert_exact(&bytes, input, Limits::DEFAULT);
398    }
399}