Skip to main content

vole_document/encode/
mod.rs

1//! Encoding: candidate portfolio, complete-cost court, and decode-before-commit.
2
3pub mod candidates;
4pub mod court;
5
6use crate::accounting::CostBreakdown;
7use crate::encode::candidates::CandidateKind;
8use crate::error::{Error, Result};
9use crate::integrity::{sha256, to_hex};
10use crate::limits::Limits;
11
12/// A report describing encode-time decisions and physical attribution.
13#[derive(Debug, Clone)]
14pub struct EncodeReport {
15    /// Winning candidate family.
16    pub kind: CandidateKind,
17    /// Source length in bytes.
18    pub source_len: u64,
19    /// Serialized `.voldoc` length in bytes.
20    pub encoded_len: u64,
21    /// Physical byte attribution of the serialized descriptor.
22    pub cost: CostBreakdown,
23    /// Lower-case hex SHA-256 of the source.
24    pub sha256_hex: String,
25    /// Number of candidates evaluated.
26    pub candidates_evaluated: u32,
27    /// Reconstruction work (DRA instruction count) of the winner.
28    pub graph_ops: usize,
29}
30
31impl EncodeReport {
32    /// Source bytes divided by encoded bytes (0.0 when the source is empty).
33    pub fn compression_ratio(&self) -> f64 {
34        if self.encoded_len == 0 {
35            return 0.0;
36        }
37        self.source_len as f64 / self.encoded_len as f64
38    }
39}
40
41/// Encode `input` exactly, returning the serialized descriptor and a report.
42///
43/// Every returned byte sequence has already been round-tripped through the
44/// normative decoder and byte-compared against `input`.
45pub fn encode(input: &[u8], limits: Limits) -> Result<(Vec<u8>, EncodeReport)> {
46    encode_with(input, limits, None)
47}
48
49/// Encode `input` exactly, optionally forcing a single candidate family.
50///
51/// With `force == None` this is the ordinary complete-cost court over every
52/// proposed candidate. With `force == Some(kind)` the candidate set is first
53/// filtered to proposals of exactly that kind, then the *same* court runs over
54/// the filtered set: the forced lane is still serialized, decoded, and
55/// byte-compared before it may be returned, so forcing never weakens exactness
56/// and determinism.
57///
58/// Returns [`Error::usage`] when `force` names a kind that this input does not
59/// propose (for example PDF_CHANNELS on a non-PDF, or BYTE_RANS on empty input),
60/// rather than silently substituting another lane. That failure is the honest
61/// signal that the mechanism does not apply.
62pub fn encode_with(
63    input: &[u8],
64    limits: Limits,
65    force: Option<CandidateKind>,
66) -> Result<(Vec<u8>, EncodeReport)> {
67    let mut cands = candidates::propose_all(input, limits)?;
68    if let Some(kind) = force {
69        cands.retain(|c| c.kind == kind);
70        if cands.is_empty() {
71            return Err(Error::usage(format!(
72                "candidate {kind:?} is not proposed for this input"
73            )));
74        }
75    }
76    let result = court::run(input, cands, limits)?;
77    let report = EncodeReport {
78        kind: result.kind,
79        source_len: input.len() as u64,
80        encoded_len: result.bytes.len() as u64,
81        cost: result.cost,
82        sha256_hex: to_hex(&sha256(input)),
83        candidates_evaluated: result.candidates_evaluated,
84        graph_ops: result.graph_ops,
85    };
86    Ok((result.bytes, report))
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn encode_decode_identity() {
95        let input: Vec<u8> = (0..=255u8).cycle().take(10_000).collect();
96        let (bytes, report) = encode(&input, Limits::DEFAULT).unwrap();
97        assert_eq!(report.source_len, input.len() as u64);
98        assert_eq!(report.encoded_len, bytes.len() as u64);
99        let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
100        assert_eq!(out, input);
101    }
102
103    #[test]
104    fn deterministic_output() {
105        let input = b"determinism check".to_vec();
106        let (a, _) = encode(&input, Limits::DEFAULT).unwrap();
107        let (b, _) = encode(&input, Limits::DEFAULT).unwrap();
108        assert_eq!(a, b);
109    }
110
111    #[test]
112    fn encode_with_none_matches_encode() {
113        let input = b"the court must not care how it was invoked".repeat(40);
114        let (auto, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
115        let (explicit, explicit_report) = encode_with(&input, Limits::DEFAULT, None).unwrap();
116        assert_eq!(auto, explicit, "None must equal the unforced court");
117        assert_eq!(auto_report.kind, explicit_report.kind);
118        assert_eq!(auto_report.encoded_len, explicit_report.encoded_len);
119        assert_eq!(
120            auto_report.candidates_evaluated,
121            explicit_report.candidates_evaluated
122        );
123    }
124
125    #[test]
126    fn encode_with_force_raw_is_exact() {
127        // RAW normally loses to RLE on this input, so forcing RAW proves the
128        // filter really overrides the court's own choice.
129        let input = vec![0u8; 8192];
130        let (_, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
131        assert_ne!(auto_report.kind, CandidateKind::Raw);
132
133        let (bytes, report) =
134            encode_with(&input, Limits::DEFAULT, Some(CandidateKind::Raw)).unwrap();
135        assert_eq!(report.kind, CandidateKind::Raw);
136        assert_eq!(report.candidates_evaluated, 1);
137        let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
138        assert_eq!(out, input);
139    }
140
141    #[cfg(feature = "rans")]
142    #[test]
143    fn encode_with_force_byte_rans_is_exact() {
144        let input = b"The quick brown fox jumps over the lazy dog. ".repeat(800);
145        let (bytes, report) =
146            encode_with(&input, Limits::DEFAULT, Some(CandidateKind::ByteRans)).unwrap();
147        assert_eq!(report.kind, CandidateKind::ByteRans);
148        assert_eq!(report.candidates_evaluated, 1);
149        let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
150        assert_eq!(out, input);
151    }
152
153    #[test]
154    fn encode_with_unproposed_kind_errors() {
155        // A non-PDF cannot propose PDF_CHANNELS, so forcing it must fail with a
156        // typed Usage error rather than silently falling back to another lane.
157        let input = b"definitely not a pdf, just text".to_vec();
158        let err = encode_with(&input, Limits::DEFAULT, Some(CandidateKind::PdfChannels))
159            .expect_err("PDF_CHANNELS is not proposed for a non-PDF");
160        assert_eq!(err.class(), crate::error::ErrorClass::Usage);
161        assert!(
162            err.message().contains("is not proposed"),
163            "unexpected message: {}",
164            err.message()
165        );
166    }
167}