1use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::diagnostic::{Diagnostic, DiagnosticCategory};
8use crate::limits::{CANONICAL_CODEC_LIMITS, CodecLimits};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct FormatVersion(u16);
14
15impl FormatVersion {
16 pub const V1: Self = Self(1);
18 pub const fn from_raw(value: u16) -> Self {
20 Self(value)
21 }
22 pub const fn get(self) -> u16 {
24 self.0
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
30#[serde(transparent)]
31pub struct CodecVersion(u16);
32
33impl CodecVersion {
34 pub const V1: Self = Self(1);
36 pub const fn from_raw(value: u16) -> Self {
38 Self(value)
39 }
40 pub const fn get(self) -> u16 {
42 self.0
43 }
44}
45
46pub fn ensure_format_version(
48 actual: FormatVersion,
49 supported: FormatVersion,
50) -> Result<(), Diagnostic> {
51 if actual == supported {
52 Ok(())
53 } else {
54 Err(Diagnostic::stable(
55 DiagnosticCategory::InvalidContract,
56 "unsupported_format_version",
57 "contract format version is not supported",
58 )
59 .with_detail("actual", i64::from(actual.get()))
60 .with_detail("supported", i64::from(supported.get())))
61 }
62}
63
64pub fn ensure_codec_version(
66 actual: CodecVersion,
67 supported: CodecVersion,
68) -> Result<(), Diagnostic> {
69 if actual == supported {
70 Ok(())
71 } else {
72 Err(Diagnostic::stable(
73 DiagnosticCategory::InvalidContract,
74 "unsupported_codec_version",
75 "canonical codec version is not supported",
76 )
77 .with_detail("actual", i64::from(actual.get()))
78 .with_detail("supported", i64::from(supported.get())))
79 }
80}
81
82pub fn to_canonical_json<T: Serialize>(value: &T) -> Result<Vec<u8>, Diagnostic> {
84 to_canonical_json_with_limits(value, CANONICAL_CODEC_LIMITS)
85}
86
87pub fn to_canonical_json_with_limits<T: Serialize>(
89 value: &T,
90 limits: CodecLimits,
91) -> Result<Vec<u8>, Diagnostic> {
92 let mut value = serde_json::to_value(value).map_err(|_| {
93 Diagnostic::stable(
94 DiagnosticCategory::InvalidContract,
95 "canonical_json_encode_failed",
96 "value cannot be represented as canonical JSON",
97 )
98 })?;
99 normalize_numbers(&mut value).map_err(|()| {
100 Diagnostic::stable(
101 DiagnosticCategory::InvalidContract,
102 "canonical_json_encode_failed",
103 "value contains a number outside the canonical JSON domain",
104 )
105 })?;
106 sort_object_keys(&mut value);
107 inspect(&value, 1, limits)?;
108 let bytes = serde_json::to_vec(&value).map_err(|_| {
109 Diagnostic::stable(
110 DiagnosticCategory::InvalidContract,
111 "canonical_json_encode_failed",
112 "value cannot be encoded as canonical JSON",
113 )
114 })?;
115 ensure_bytes(bytes.len(), limits)?;
116 Ok(bytes)
117}
118
119pub fn from_canonical_json<T>(bytes: &[u8]) -> Result<T, Diagnostic>
121where
122 T: DeserializeOwned + Serialize,
123{
124 from_canonical_json_with_limits(bytes, CANONICAL_CODEC_LIMITS)
125}
126
127pub fn from_canonical_json_with_limits<T>(
129 bytes: &[u8],
130 limits: CodecLimits,
131) -> Result<T, Diagnostic>
132where
133 T: DeserializeOwned + Serialize,
134{
135 ensure_bytes(bytes.len(), limits)?;
136 let mut value: Value = serde_json::from_slice(bytes).map_err(|_| {
137 Diagnostic::stable(
138 DiagnosticCategory::InvalidContract,
139 "malformed_canonical_json",
140 "input is not valid canonical JSON",
141 )
142 })?;
143 inspect(&value, 1, limits)?;
144 normalize_numbers(&mut value).map_err(|()| {
145 Diagnostic::stable(
146 DiagnosticCategory::InvalidContract,
147 "malformed_canonical_json",
148 "input is not valid canonical JSON",
149 )
150 })?;
151 sort_object_keys(&mut value);
152 let canonical = serde_json::to_vec(&value).map_err(|_| {
153 Diagnostic::stable(
154 DiagnosticCategory::InvalidContract,
155 "canonical_json_encode_failed",
156 "decoded JSON cannot be re-encoded",
157 )
158 })?;
159 if canonical != bytes {
160 return Err(Diagnostic::stable(
161 DiagnosticCategory::InvalidContract,
162 "non_canonical_json",
163 "input is valid JSON but not the canonical encoding",
164 )
165 .with_detail("actual_bytes", count(bytes.len()))
166 .with_detail("canonical_bytes", count(canonical.len())));
167 }
168 serde_json::from_value(value).map_err(|_| {
169 Diagnostic::stable(
170 DiagnosticCategory::InvalidContract,
171 "invalid_canonical_value",
172 "canonical JSON does not satisfy the requested contract type",
173 )
174 })
175}
176
177fn sort_object_keys(value: &mut Value) {
185 match value {
186 Value::Array(values) => {
187 for value in values {
188 sort_object_keys(value);
189 }
190 }
191 Value::Object(values) => {
192 for value in values.values_mut() {
193 sort_object_keys(value);
194 }
195 let mut entries = std::mem::take(values).into_iter().collect::<Vec<_>>();
196 entries.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
197 values.extend(entries);
198 }
199 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
200 }
201}
202
203fn normalize_numbers(value: &mut Value) -> Result<(), ()> {
208 match value {
209 Value::Array(values) => {
210 for value in values {
211 normalize_numbers(value)?;
212 }
213 }
214 Value::Object(values) => {
215 for value in values.values_mut() {
216 normalize_numbers(value)?;
217 }
218 }
219 Value::Number(number) => {
220 let normalized = if let Some(value) = number.as_i64() {
221 value.into()
222 } else if let Some(value) = number.as_u64() {
223 value.into()
224 } else if let Some(value) = number.as_f64() {
225 serde_json::Number::from_f64(value).ok_or(())?
226 } else {
227 return Err(());
228 };
229 *number = normalized;
230 }
231 Value::Null | Value::Bool(_) | Value::String(_) => {}
232 }
233 Ok(())
234}
235
236fn inspect(value: &Value, depth: usize, limits: CodecLimits) -> Result<(), Diagnostic> {
237 if depth > limits.max_depth {
238 return Err(Diagnostic::stable(
239 DiagnosticCategory::ResourceLimit,
240 "canonical_json_too_deep",
241 "canonical JSON exceeds the nesting-depth ceiling",
242 )
243 .with_detail("maximum_depth", count(limits.max_depth)));
244 }
245 match value {
246 Value::String(value) => ensure_string(value.len(), limits),
247 Value::Array(values) => {
248 ensure_collection(values.len(), limits)?;
249 for value in values {
250 inspect(value, depth + 1, limits)?;
251 }
252 Ok(())
253 }
254 Value::Object(values) => {
255 ensure_collection(values.len(), limits)?;
256 for (key, value) in values {
257 ensure_string(key.len(), limits)?;
258 inspect(value, depth + 1, limits)?;
259 }
260 Ok(())
261 }
262 Value::Null | Value::Bool(_) | Value::Number(_) => Ok(()),
263 }
264}
265
266fn ensure_bytes(actual: usize, limits: CodecLimits) -> Result<(), Diagnostic> {
267 if actual <= limits.max_bytes {
268 Ok(())
269 } else {
270 Err(Diagnostic::stable(
271 DiagnosticCategory::ResourceLimit,
272 "canonical_json_too_large",
273 "canonical JSON exceeds the byte ceiling",
274 )
275 .with_detail("actual_bytes", count(actual))
276 .with_detail("maximum_bytes", count(limits.max_bytes)))
277 }
278}
279fn ensure_collection(actual: usize, limits: CodecLimits) -> Result<(), Diagnostic> {
280 if actual <= limits.max_collection_len {
281 Ok(())
282 } else {
283 Err(Diagnostic::stable(
284 DiagnosticCategory::ResourceLimit,
285 "canonical_collection_too_large",
286 "canonical JSON collection exceeds its member ceiling",
287 )
288 .with_detail("actual_items", count(actual))
289 .with_detail("maximum_items", count(limits.max_collection_len)))
290 }
291}
292fn ensure_string(actual: usize, limits: CodecLimits) -> Result<(), Diagnostic> {
293 if actual <= limits.max_string_bytes {
294 Ok(())
295 } else {
296 Err(Diagnostic::stable(
297 DiagnosticCategory::ResourceLimit,
298 "canonical_string_too_large",
299 "canonical JSON string exceeds its byte ceiling",
300 )
301 .with_detail("actual_bytes", count(actual))
302 .with_detail("maximum_bytes", count(limits.max_string_bytes)))
303 }
304}
305fn count(value: usize) -> i64 {
306 i64::try_from(value).unwrap_or(i64::MAX)
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::value::CanonicalValue;
313 use serde::{Deserialize, Serialize};
314 use serde_json::Value;
315
316 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
317 struct OutOfOrderObject {
318 zeta: u8,
319 alpha: OutOfOrderNested,
320 }
321
322 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
323 struct OutOfOrderNested {
324 zeta: u8,
325 alpha: u8,
326 }
327
328 #[test]
329 fn canonical_object_order_is_independent_of_the_serde_json_map_backend() {
330 let value = OutOfOrderObject {
331 zeta: 3,
332 alpha: OutOfOrderNested { zeta: 2, alpha: 1 },
333 };
334 let canonical = br#"{"alpha":{"alpha":1,"zeta":2},"zeta":3}"#;
335 assert_eq!(to_canonical_json(&value).unwrap(), canonical);
336 assert_eq!(
337 from_canonical_json::<OutOfOrderObject>(canonical).unwrap(),
338 value
339 );
340
341 let insertion_order = br#"{"zeta":3,"alpha":{"zeta":2,"alpha":1}}"#;
342 assert_eq!(
343 from_canonical_json::<OutOfOrderObject>(insertion_order)
344 .unwrap_err()
345 .code()
346 .as_str(),
347 "non_canonical_json"
348 );
349 }
350
351 #[test]
352 fn canonical_decoder_distinguishes_malformed_and_noncanonical_input() {
353 assert_eq!(
354 from_canonical_json::<CanonicalValue>(b"{")
355 .unwrap_err()
356 .code()
357 .as_str(),
358 "malformed_canonical_json"
359 );
360 let spaced = br#"{ "kind":"long","value":"1"}"#;
361 assert_eq!(
362 from_canonical_json::<CanonicalValue>(spaced)
363 .unwrap_err()
364 .code()
365 .as_str(),
366 "non_canonical_json"
367 );
368
369 for noncanonical in [b"1e0" as &[u8], b"1E+0", b"-0"] {
370 for error in [
371 from_canonical_json::<FormatVersion>(noncanonical).unwrap_err(),
372 from_canonical_json::<Value>(noncanonical).unwrap_err(),
373 ] {
374 assert_eq!(error.code().as_str(), "non_canonical_json");
375 }
376 }
377 assert_eq!(
378 from_canonical_json::<Value>(b"01")
379 .unwrap_err()
380 .code()
381 .as_str(),
382 "malformed_canonical_json"
383 );
384 }
385
386 #[test]
387 fn canonical_numbers_are_independent_of_the_serde_json_number_backend() {
388 for canonical in [
389 b"0" as &[u8],
390 b"-1",
391 b"-9223372036854775808",
392 b"18446744073709551615",
393 b"1.0",
394 b"0.0",
395 b"-0.0",
396 b"5e-324",
397 ] {
398 let value = from_canonical_json::<Value>(canonical).unwrap();
399 assert_eq!(to_canonical_json(&value).unwrap(), canonical);
400 }
401
402 for noncanonical in [
403 b"-9223372036854775809" as &[u8],
404 b"18446744073709551616",
405 b"100000000000000000000000000000000000000000000000000",
406 b"4.9406564584124654e-324",
407 ] {
408 assert_eq!(
409 from_canonical_json::<Value>(noncanonical)
410 .unwrap_err()
411 .code()
412 .as_str(),
413 "non_canonical_json"
414 );
415 }
416
417 assert_eq!(to_canonical_json(&1.0_f64).unwrap(), b"1.0");
418 assert_eq!(to_canonical_json(&f64::from_bits(1)).unwrap(), b"5e-324");
419 }
420
421 #[test]
422 fn exact_limits_accept_boundary_and_reject_next_value() {
423 let value = CanonicalValue::String(crate::value::CanonicalString::new("abc").unwrap());
424 let bytes = to_canonical_json(&value).unwrap();
425 let mut limits = CodecLimits::CANONICAL;
426 limits.max_bytes = bytes.len();
427 assert!(from_canonical_json_with_limits::<CanonicalValue>(&bytes, limits).is_ok());
428 limits.max_bytes -= 1;
429 assert_eq!(
430 from_canonical_json_with_limits::<CanonicalValue>(&bytes, limits)
431 .unwrap_err()
432 .code()
433 .as_str(),
434 "canonical_json_too_large"
435 );
436 }
437
438 #[test]
439 fn required_versions_fail_closed() {
440 assert!(serde_json::from_str::<FormatVersion>(r#""1""#).is_err());
441 assert!(serde_json::from_str::<CodecVersion>(r#""1""#).is_err());
442 assert!(ensure_format_version(FormatVersion::V1, FormatVersion::V1).is_ok());
443 assert!(ensure_codec_version(CodecVersion::V1, CodecVersion::V1).is_ok());
444
445 assert_eq!(
446 ensure_format_version(FormatVersion::from_raw(2), FormatVersion::V1)
447 .unwrap_err()
448 .code()
449 .as_str(),
450 "unsupported_format_version",
451 );
452 assert_eq!(
453 ensure_codec_version(CodecVersion::from_raw(2), CodecVersion::V1)
454 .unwrap_err()
455 .code()
456 .as_str(),
457 "unsupported_codec_version",
458 );
459 }
460
461 #[test]
462 fn structural_limits_reject_depth_members_strings_and_keys() {
463 let base = CodecLimits {
464 max_bytes: 128,
465 max_depth: 8,
466 max_collection_len: 8,
467 max_string_bytes: 8,
468 };
469
470 let depth = CodecLimits {
471 max_depth: 2,
472 ..base
473 };
474 assert_eq!(
475 from_canonical_json_with_limits::<Value>(b"[[0]]", depth)
476 .unwrap_err()
477 .code()
478 .as_str(),
479 "canonical_json_too_deep",
480 );
481
482 let members = CodecLimits {
483 max_collection_len: 1,
484 ..base
485 };
486 assert_eq!(
487 from_canonical_json_with_limits::<Value>(b"[0,1]", members)
488 .unwrap_err()
489 .code()
490 .as_str(),
491 "canonical_collection_too_large",
492 );
493
494 let strings = CodecLimits {
495 max_string_bytes: 3,
496 ..base
497 };
498 for bytes in [br#""abcd""# as &[u8], br#"{"abcd":0}"# as &[u8]] {
499 assert_eq!(
500 from_canonical_json_with_limits::<Value>(bytes, strings)
501 .unwrap_err()
502 .code()
503 .as_str(),
504 "canonical_string_too_large",
505 );
506 }
507 }
508}