1pub mod candidates;
4pub mod court;
5
6#[cfg(feature = "dsfb-search")]
10pub mod governor;
11
12use crate::accounting::CostBreakdown;
13use crate::encode::candidates::{Candidate, 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 encode_impl(input, limits, force, false)
74}
75
76#[cfg(feature = "field")]
94pub(crate) fn encode_with_observation_index(
95 input: &[u8],
96 limits: Limits,
97 kind: CandidateKind,
98) -> Result<(Vec<u8>, EncodeReport)> {
99 encode_impl(input, limits, Some(kind), true)
100}
101
102fn encode_impl(
103 input: &[u8],
104 limits: Limits,
105 force: Option<CandidateKind>,
106 enrich_index: bool,
107) -> Result<(Vec<u8>, EncodeReport)> {
108 let mut court = court::Court::new();
109 if let Some(kind) = force {
110 let candidate = candidates::propose_forced(input, limits, kind)?.ok_or_else(|| {
113 Error::usage(format!("candidate {kind:?} is not proposed for this input"))
114 })?;
115 if enrich_index {
116 let enriched = Candidate {
117 kind,
118 descriptor: candidate.descriptor.with_observation_index(limits),
119 };
120 if court.offer(input, enriched, limits).is_err() {
121 let plain = candidates::propose_forced(input, limits, kind)?.ok_or_else(|| {
124 Error::usage(format!("candidate {kind:?} is not proposed for this input"))
125 })?;
126 court = court::Court::new();
127 court.offer(input, plain, limits)?;
128 }
129 } else {
130 court.offer(input, candidate, limits)?;
131 }
132 } else {
133 candidates::propose_each(input, limits, |c| court.offer(input, c, limits))?;
137 }
138 let result = court.finish()?;
139 let report = EncodeReport {
140 kind: result.kind,
141 source_len: input.len() as u64,
142 encoded_len: result.bytes.len() as u64,
143 cost: result.cost,
144 sha256_hex: to_hex(&sha256(input)),
145 candidates_evaluated: result.candidates_evaluated,
146 graph_ops: result.graph_ops,
147 };
148 Ok((result.bytes, report))
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn encode_decode_identity() {
157 let input: Vec<u8> = (0..=255u8).cycle().take(10_000).collect();
158 let (bytes, report) = encode(&input, Limits::DEFAULT).unwrap();
159 assert_eq!(report.source_len, input.len() as u64);
160 assert_eq!(report.encoded_len, bytes.len() as u64);
161 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
162 assert_eq!(out, input);
163 }
164
165 #[test]
166 fn deterministic_output() {
167 let input = b"determinism check".to_vec();
168 let (a, _) = encode(&input, Limits::DEFAULT).unwrap();
169 let (b, _) = encode(&input, Limits::DEFAULT).unwrap();
170 assert_eq!(a, b);
171 }
172
173 #[test]
174 fn encode_with_none_matches_encode() {
175 let input = b"the court must not care how it was invoked".repeat(40);
176 let (auto, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
177 let (explicit, explicit_report) = encode_with(&input, Limits::DEFAULT, None).unwrap();
178 assert_eq!(auto, explicit, "None must equal the unforced court");
179 assert_eq!(auto_report.kind, explicit_report.kind);
180 assert_eq!(auto_report.encoded_len, explicit_report.encoded_len);
181 assert_eq!(
182 auto_report.candidates_evaluated,
183 explicit_report.candidates_evaluated
184 );
185 }
186
187 #[cfg(feature = "field")]
188 #[test]
189 fn forced_encode_omits_index_and_enrichment_is_byte_identical() {
190 let input = b"the enriched authority must be byte-identical".repeat(64);
191
192 let (plain, _) = encode_with(&input, Limits::DEFAULT, Some(CandidateKind::Raw)).unwrap();
195 let parsed = crate::container::Descriptor::parse(&plain, Limits::DEFAULT).unwrap();
196 assert!(parsed.descriptor.observation_index.is_none());
197
198 let after = crate::field::ingest::with_observation_index(&plain, Limits::DEFAULT).unwrap();
200 let (before, report) =
201 encode_with_observation_index(&input, Limits::DEFAULT, CandidateKind::Raw).unwrap();
202 assert_eq!(
203 before, after,
204 "the one-pass enriched authority must equal the two-pass bytes"
205 );
206 assert_eq!(report.candidates_evaluated, 1);
207 assert_eq!(report.kind, CandidateKind::Raw);
208
209 let enriched = crate::container::Descriptor::parse(&before, Limits::DEFAULT).unwrap();
211 assert!(enriched.descriptor.observation_index.is_some());
212 let (out, _) = crate::materialize::decode_to_bytes(&before, Limits::DEFAULT).unwrap();
213 assert_eq!(out, input);
214 }
215
216 #[test]
217 fn encode_with_force_raw_is_exact() {
218 let input = vec![0u8; 8192];
221 let (_, auto_report) = encode(&input, Limits::DEFAULT).unwrap();
222 assert_ne!(auto_report.kind, CandidateKind::Raw);
223
224 let (bytes, report) =
225 encode_with(&input, Limits::DEFAULT, Some(CandidateKind::Raw)).unwrap();
226 assert_eq!(report.kind, CandidateKind::Raw);
227 assert_eq!(report.candidates_evaluated, 1);
228 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
229 assert_eq!(out, input);
230 }
231
232 #[cfg(feature = "rans")]
233 #[test]
234 fn encode_with_force_byte_rans_is_exact() {
235 let input = b"The quick brown fox jumps over the lazy dog. ".repeat(800);
236 let (bytes, report) =
237 encode_with(&input, Limits::DEFAULT, Some(CandidateKind::ByteRans)).unwrap();
238 assert_eq!(report.kind, CandidateKind::ByteRans);
239 assert_eq!(report.candidates_evaluated, 1);
240 let (out, _) = crate::materialize::decode_to_bytes(&bytes, Limits::DEFAULT).unwrap();
241 assert_eq!(out, input);
242 }
243
244 #[test]
245 fn encode_with_unproposed_kind_errors() {
246 let input = b"definitely not a pdf, just text".to_vec();
249 let err = encode_with(&input, Limits::DEFAULT, Some(CandidateKind::PdfChannels))
250 .expect_err("PDF_CHANNELS is not proposed for a non-PDF");
251 assert_eq!(err.class(), crate::error::ErrorClass::Usage);
252 assert!(
253 err.message().contains("is not proposed"),
254 "unexpected message: {}",
255 err.message()
256 );
257 }
258}