vole_document/encode/
mod.rs1pub 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#[derive(Debug, Clone)]
14pub struct EncodeReport {
15 pub kind: CandidateKind,
17 pub source_len: u64,
19 pub encoded_len: u64,
21 pub cost: CostBreakdown,
23 pub sha256_hex: String,
25 pub candidates_evaluated: u32,
27 pub graph_ops: usize,
29}
30
31impl EncodeReport {
32 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
41pub fn encode(input: &[u8], limits: Limits) -> Result<(Vec<u8>, EncodeReport)> {
46 encode_with(input, limits, None)
47}
48
49pub 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 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 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}