vole_document/encode/
mod.rs1pub mod candidates;
4pub mod court;
5
6#[cfg(feature = "dsfb-search")]
10pub mod governor;
11
12use crate::accounting::CostBreakdown;
13use crate::encode::candidates::CandidateKind;
14use crate::error::{Error, Result};
15use crate::integrity::{sha256, to_hex};
16use crate::limits::Limits;
17
18#[derive(Debug, Clone)]
20pub struct EncodeReport {
21 pub kind: CandidateKind,
23 pub source_len: u64,
25 pub encoded_len: u64,
27 pub cost: CostBreakdown,
29 pub sha256_hex: String,
31 pub candidates_evaluated: u32,
33 pub graph_ops: usize,
35}
36
37impl EncodeReport {
38 pub fn compression_ratio(&self) -> f64 {
40 if self.encoded_len == 0 {
41 return 0.0;
42 }
43 self.source_len as f64 / self.encoded_len as f64
44 }
45}
46
47pub fn encode(input: &[u8], limits: Limits) -> Result<(Vec<u8>, EncodeReport)> {
52 encode_with(input, limits, None)
53}
54
55pub fn encode_with(
69 input: &[u8],
70 limits: Limits,
71 force: Option<CandidateKind>,
72) -> Result<(Vec<u8>, EncodeReport)> {
73 let mut court = court::Court::new();
78 let mut matched: u32 = 0;
79 candidates::propose_each(input, limits, |c| {
80 if force.is_none_or(|k| c.kind == k) {
81 matched += 1;
82 court.offer(input, c, limits)?;
83 }
84 Ok(())
85 })?;
86 if let Some(kind) = force
87 && matched == 0
88 {
89 return Err(Error::usage(format!(
90 "candidate {kind:?} is not proposed for this input"
91 )));
92 }
93 let result = court.finish()?;
94 let report = EncodeReport {
95 kind: result.kind,
96 source_len: input.len() as u64,
97 encoded_len: result.bytes.len() as u64,
98 cost: result.cost,
99 sha256_hex: to_hex(&sha256(input)),
100 candidates_evaluated: result.candidates_evaluated,
101 graph_ops: result.graph_ops,
102 };
103 Ok((result.bytes, report))
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn encode_decode_identity() {
112 let input: Vec<u8> = (0..=255u8).cycle().take(10_000).collect();
113 let (bytes, report) = encode(&input, Limits::DEFAULT).unwrap();
114 assert_eq!(report.source_len, input.len() as u64);
115 assert_eq!(report.encoded_len, bytes.len() as u64);
116 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
117 assert_eq!(out, input);
118 }
119
120 #[test]
121 fn deterministic_output() {
122 let input = b"determinism check".to_vec();
123 let (a, _) = encode(&input, Limits::DEFAULT).unwrap();
124 let (b, _) = encode(&input, Limits::DEFAULT).unwrap();
125 assert_eq!(a, b);
126 }
127
128 #[test]
129 fn encode_with_none_matches_encode() {
130 let input = b"the court must not care how it was invoked".repeat(40);
131 let (auto, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
132 let (explicit, explicit_report) = encode_with(&input, Limits::DEFAULT, None).unwrap();
133 assert_eq!(auto, explicit, "None must equal the unforced court");
134 assert_eq!(auto_report.kind, explicit_report.kind);
135 assert_eq!(auto_report.encoded_len, explicit_report.encoded_len);
136 assert_eq!(
137 auto_report.candidates_evaluated,
138 explicit_report.candidates_evaluated
139 );
140 }
141
142 #[test]
143 fn encode_with_force_raw_is_exact() {
144 let input = vec![0u8; 8192];
147 let (_, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
148 assert_ne!(auto_report.kind, CandidateKind::Raw);
149
150 let (bytes, report) =
151 encode_with(&input, Limits::DEFAULT, Some(CandidateKind::Raw)).unwrap();
152 assert_eq!(report.kind, CandidateKind::Raw);
153 assert_eq!(report.candidates_evaluated, 1);
154 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
155 assert_eq!(out, input);
156 }
157
158 #[cfg(feature = "rans")]
159 #[test]
160 fn encode_with_force_byte_rans_is_exact() {
161 let input = b"The quick brown fox jumps over the lazy dog. ".repeat(800);
162 let (bytes, report) =
163 encode_with(&input, Limits::DEFAULT, Some(CandidateKind::ByteRans)).unwrap();
164 assert_eq!(report.kind, CandidateKind::ByteRans);
165 assert_eq!(report.candidates_evaluated, 1);
166 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
167 assert_eq!(out, input);
168 }
169
170 #[test]
171 fn encode_with_unproposed_kind_errors() {
172 let input = b"definitely not a pdf, just text".to_vec();
175 let err = encode_with(&input, Limits::DEFAULT, Some(CandidateKind::PdfChannels))
176 .expect_err("PDF_CHANNELS is not proposed for a non-PDF");
177 assert_eq!(err.class(), crate::error::ErrorClass::Usage);
178 assert!(
179 err.message().contains("is not proposed"),
180 "unexpected message: {}",
181 err.message()
182 );
183 }
184}