1use std::collections::BTreeSet;
14use std::io::{self, Write};
15
16use serde::{Deserialize, Serialize};
17use sha2::{Digest, Sha256};
18
19use crate::{
20 AssetResolver, DataValue, ElementIr, ErrorCode, FileMakerError, FontManager, Patch,
21 PatchOperation, ResourceLimits, Result, TemplateIr, ENGINE_VERSION, FILEMAKER_SCHEMA_V1,
22};
23
24const DEFAULT_MAX_FINGERPRINT_BYTES: usize = 512 * 1024 * 1024;
25
26#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct DocumentFingerprint([u8; 32]);
30
31impl DocumentFingerprint {
32 pub fn compute(
34 template: &TemplateIr,
35 data: &DataValue,
36 assets: Option<&dyn AssetResolver>,
37 fonts: &FontManager,
38 limits: &ResourceLimits,
39 ) -> Result<Self> {
40 Self::compute_with_patches(template, data, &[], assets, fonts, limits)
41 }
42
43 pub fn compute_with_patches(
45 template: &TemplateIr,
46 data: &DataValue,
47 patches: &[Patch],
48 assets: Option<&dyn AssetResolver>,
49 fonts: &FontManager,
50 limits: &ResourceLimits,
51 ) -> Result<Self> {
52 let mut builder = FingerprintBuilder::with_max_bytes(limits.max_output_bytes)?;
53 builder.field("schema", FILEMAKER_SCHEMA_V1.as_bytes())?;
54 builder.field("engine", ENGINE_VERSION.as_bytes())?;
55 builder.serialized("template", template)?;
56 builder.serialized("data", data)?;
57 builder.serialized("patches", &patches)?;
58 for (name, digest) in fonts.digests() {
59 builder.field("font-name", name.as_bytes())?;
60 builder.field("font-digest", digest)?;
61 }
62 for name in fonts.fallback_names() {
63 builder.field("font-fallback", name.as_bytes())?;
64 }
65 let mut names = asset_names(&template.elements);
66 for patch in patches {
67 for operation in &patch.operations {
68 if let PatchOperation::Add { element, .. }
69 | PatchOperation::Replace { element, .. } = operation
70 {
71 names.extend(asset_names(std::slice::from_ref(element)));
72 }
73 }
74 }
75 if !names.is_empty() && assets.is_none() {
76 return Err(fingerprint_error(
77 "fingerprint requires a resolver for referenced assets",
78 ));
79 }
80 if let Some(resolver) = assets {
81 for name in names {
82 let asset = resolver.resolve_asset(name, limits.max_asset_bytes)?;
83 builder.field("asset-name", name.as_bytes())?;
84 builder.field("asset-media-type", asset.media_type.as_bytes())?;
85 builder.field("asset-digest", &asset.digest)?;
86 }
87 }
88 Ok(builder.finish())
89 }
90
91 #[must_use]
93 pub fn to_hex(self) -> String {
94 const DIGITS: &[u8; 16] = b"0123456789abcdef";
95 let mut result = String::with_capacity(64);
96 for byte in self.0 {
97 result.push(char::from(DIGITS[usize::from(byte >> 4)]));
98 result.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
99 }
100 result
101 }
102
103 #[must_use]
105 pub const fn as_bytes(&self) -> &[u8; 32] {
106 &self.0
107 }
108}
109
110pub struct FingerprintBuilder {
112 hasher: Sha256,
113 remaining: usize,
114}
115
116impl FingerprintBuilder {
117 #[must_use]
119 pub fn new() -> Self {
120 Self::with_max_bytes_unchecked(DEFAULT_MAX_FINGERPRINT_BYTES)
121 }
122
123 pub fn with_max_bytes(max_bytes: usize) -> Result<Self> {
125 if max_bytes == 0 {
126 return Err(fingerprint_limit_error(
127 "fingerprint aggregate byte limit must be non-zero",
128 ));
129 }
130 Ok(Self::with_max_bytes_unchecked(max_bytes))
131 }
132
133 fn with_max_bytes_unchecked(max_bytes: usize) -> Self {
134 let mut hasher = Sha256::new();
135 hasher.update(b"appcore-filemaker-fingerprint-v1\0");
136 Self {
137 hasher,
138 remaining: max_bytes,
139 }
140 }
141
142 pub fn field(&mut self, name: &str, bytes: &[u8]) -> Result<()> {
144 let framed_bytes = framed_field_size(name, bytes.len())?;
145 self.require_remaining(framed_bytes)?;
146 let byte_len = u64::try_from(bytes.len())
147 .map_err(|_| fingerprint_error("fingerprint field is too long"))?;
148 frame_field(&mut self.hasher, name, byte_len)?;
149 self.hasher.update(bytes);
150 self.remaining -= framed_bytes;
151 Ok(())
152 }
153
154 pub fn serialized<T: Serialize>(&mut self, name: &str, value: &T) -> Result<()> {
161 let framing_bytes = framed_field_size(name, 0)?;
162 self.require_remaining(framing_bytes)?;
163 let mut counter = JsonLengthWriter::new(self.remaining - framing_bytes);
164 if let Err(error) = serde_json::to_writer(&mut counter, value) {
165 if counter.exceeded {
166 return Err(fingerprint_limit_error(
167 "serialized fingerprint fields exceed the aggregate byte limit",
168 ));
169 }
170 return Err(serialization_error(error));
171 }
172 let framed_bytes = framing_bytes
173 .checked_add(counter.written)
174 .ok_or_else(|| fingerprint_limit_error("fingerprint field size overflow"))?;
175 let byte_len = u64::try_from(counter.written)
176 .map_err(|_| fingerprint_error("fingerprint field is too long"))?;
177 let mut candidate = self.hasher.clone();
178 frame_field(&mut candidate, name, byte_len)?;
179 let (remaining, exceeded) = {
180 let mut writer = JsonDigestWriter::new(&mut candidate, counter.written);
181 let result = serde_json::to_writer(&mut writer, value);
182 if let Err(error) = result {
183 if writer.exceeded {
184 return Err(fingerprint_error(
185 "fingerprint serialization changed while hashing",
186 ));
187 }
188 return Err(serialization_error(error));
189 }
190 (writer.remaining, writer.exceeded)
191 };
192 if remaining != 0 || exceeded {
193 return Err(fingerprint_error(
194 "fingerprint serialization changed while hashing",
195 ));
196 }
197 self.hasher = candidate;
198 self.remaining -= framed_bytes;
199 Ok(())
200 }
201
202 fn require_remaining(&self, bytes: usize) -> Result<()> {
203 if bytes > self.remaining {
204 return Err(fingerprint_limit_error(
205 "fingerprint fields exceed the aggregate byte limit",
206 ));
207 }
208 Ok(())
209 }
210
211 #[must_use]
213 pub fn finish(self) -> DocumentFingerprint {
214 DocumentFingerprint(self.hasher.finalize().into())
215 }
216}
217
218struct JsonLengthWriter {
219 written: usize,
220 limit: usize,
221 exceeded: bool,
222}
223
224impl JsonLengthWriter {
225 const fn new(limit: usize) -> Self {
226 Self {
227 written: 0,
228 limit,
229 exceeded: false,
230 }
231 }
232}
233
234impl Write for JsonLengthWriter {
235 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
236 let Some(written) = self.written.checked_add(bytes.len()) else {
237 self.exceeded = true;
238 return Err(io::Error::other("fingerprint JSON length overflow"));
239 };
240 if written > self.limit {
241 self.exceeded = true;
242 return Err(io::Error::other("fingerprint JSON exceeded byte limit"));
243 }
244 self.written = written;
245 Ok(bytes.len())
246 }
247
248 fn flush(&mut self) -> io::Result<()> {
249 Ok(())
250 }
251}
252
253struct JsonDigestWriter<'a> {
254 hasher: &'a mut Sha256,
255 remaining: usize,
256 exceeded: bool,
257}
258
259impl<'a> JsonDigestWriter<'a> {
260 const fn new(hasher: &'a mut Sha256, expected: usize) -> Self {
261 Self {
262 hasher,
263 remaining: expected,
264 exceeded: false,
265 }
266 }
267}
268
269impl Write for JsonDigestWriter<'_> {
270 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
271 if bytes.len() > self.remaining {
272 self.exceeded = true;
273 return Err(io::Error::other("fingerprint JSON exceeded measured size"));
274 }
275 self.hasher.update(bytes);
276 self.remaining -= bytes.len();
277 Ok(bytes.len())
278 }
279
280 fn flush(&mut self) -> io::Result<()> {
281 Ok(())
282 }
283}
284
285fn frame_field(hasher: &mut Sha256, name: &str, byte_len: u64) -> Result<()> {
286 let name_len = u64::try_from(name.len())
287 .map_err(|_| fingerprint_error("fingerprint field name is too long"))?;
288 hasher.update(name_len.to_be_bytes());
289 hasher.update(name.as_bytes());
290 hasher.update(byte_len.to_be_bytes());
291 Ok(())
292}
293
294fn framed_field_size(name: &str, payload_bytes: usize) -> Result<usize> {
295 16_usize
296 .checked_add(name.len())
297 .and_then(|size| size.checked_add(payload_bytes))
298 .ok_or_else(|| fingerprint_limit_error("fingerprint field size overflow"))
299}
300
301fn serialization_error(error: serde_json::Error) -> FileMakerError {
302 fingerprint_error(format!("cannot serialize fingerprint field: {error}"))
303}
304
305impl Default for FingerprintBuilder {
306 fn default() -> Self {
307 Self::new()
308 }
309}
310
311fn asset_names(elements: &[ElementIr]) -> BTreeSet<&str> {
312 let mut names = BTreeSet::new();
313 let mut stack = elements.iter().collect::<Vec<_>>();
314 while let Some(element) = stack.pop() {
315 if let Some(asset) = &element.asset {
316 names.insert(asset.as_str());
317 }
318 stack.extend(element.children.iter());
319 }
320 names
321}
322
323fn fingerprint_error(message: impl Into<String>) -> FileMakerError {
324 FileMakerError::new(ErrorCode::Validation, message)
325}
326
327fn fingerprint_limit_error(message: impl Into<String>) -> FileMakerError {
328 FileMakerError::new(ErrorCode::LimitExceeded, message)
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use std::cell::Cell;
335
336 struct ChangingSerialization(Cell<bool>);
337
338 impl Serialize for ChangingSerialization {
339 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
340 where
341 S: serde::Serializer,
342 {
343 if self.0.replace(true) {
344 "longer".serialize(serializer)
345 } else {
346 "x".serialize(serializer)
347 }
348 }
349 }
350
351 #[test]
352 fn length_boundaries_prevent_ambiguous_hashes() {
353 let mut left = FingerprintBuilder::new();
354 left.field("a", b"bc").unwrap();
355 let mut right = FingerprintBuilder::new();
356 right.field("ab", b"c").unwrap();
357 assert_ne!(left.finish(), right.finish());
358 }
359
360 #[test]
361 fn streaming_serialization_preserves_the_buffered_v1_fingerprint() {
362 let value = serde_json::json!({
363 "alpha": [1, 2, 3],
364 "nested": {"stable": true},
365 "unicode": "日本語 العربية",
366 });
367 let mut streamed = FingerprintBuilder::new();
368 streamed.serialized("value", &value).unwrap();
369
370 let mut buffered = FingerprintBuilder::new();
371 buffered
372 .field("value", &serde_json::to_vec(&value).unwrap())
373 .unwrap();
374 assert_eq!(streamed.finish(), buffered.finish());
375 }
376
377 #[test]
378 fn aggregate_budget_counts_field_framing_and_serialized_bytes() {
379 let mut exact_field = FingerprintBuilder::with_max_bytes(18).unwrap();
380 exact_field.field("a", b"b").unwrap();
381 let mut short_field = FingerprintBuilder::with_max_bytes(17).unwrap();
382 assert!(short_field.field("a", b"b").is_err());
383
384 let mut exact_json = FingerprintBuilder::with_max_bytes(20).unwrap();
385 exact_json.serialized("v", &"x").unwrap();
386 let mut short_json = FingerprintBuilder::with_max_bytes(19).unwrap();
387 assert!(short_json.serialized("v", &"x").is_err());
388 }
389
390 #[test]
391 fn changing_serialization_does_not_mutate_the_builder() {
392 let mut actual = FingerprintBuilder::with_max_bytes(128).unwrap();
393 actual.field("before", b"stable").unwrap();
394 assert!(actual
395 .serialized("changing", &ChangingSerialization(Cell::new(false)))
396 .is_err());
397 actual.field("after", b"stable").unwrap();
398
399 let mut expected = FingerprintBuilder::with_max_bytes(128).unwrap();
400 expected.field("before", b"stable").unwrap();
401 expected.field("after", b"stable").unwrap();
402 assert_eq!(actual.finish(), expected.finish());
403 }
404}