Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_sbj/src/canon.rs

49.7 KiB, 1 run

created by r1870400018:22208, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Canonical encoding: one document, one byte string. See `SPEC.md` §3.
2//!
3//! The hash of the tree region is the document's address, so a tree must encode to exactly one byte
4//! string or it has more than one address. BDAT is happy to encode the same logical value several
5//! ways, which is why every rule of §3 exists, and why non-canonical bytes are rejected here rather
6//! than quietly re-encoded.
7//!
8//! Two of the rules cannot be checked on a decoded tree at all, because the decoder has already
9//! thrown the evidence away. A duplicate map key (§3 rule 3) collapses into one entry when BDAT
10//! builds its `BTreeMap`, and a `c64` length written in more bytes than it needs decodes to the
11//! same number. Both survive only in the bytes, so [`decode`] re-encodes the tree it decoded and
12//! insists on getting the same bytes back. That single comparison enforces every byte-level rule at
13//! once, including the ones nobody has thought of yet.
14
15use crate::{
16 kinds::{
17 known_style_field,
18 Content,
19 FieldType,
20 NodeKind,
21 ReservedKind,
22 StyleCheck,
23 ADDR_HASH,
24 ADDR_NAME,
25 KEY_ALT,
26 KEY_CHILDREN,
27 KEY_FALLBACK,
28 KEY_STYLE,
29 KEY_STYLES,
30 },
31 limit,
32};
33
34use oxedyne_fe2o3_core::prelude::*;
35use oxedyne_fe2o3_jdat::{
36 prelude::*,
37 bdat::DecodeLimits,
38};
39use oxedyne_fe2o3_text::unicode::norm::{
40 self,
41 Form,
42};
43
44/// Greatest daticle nesting depth a tree region may reach, given the node depth limit of §5.
45///
46/// The decoder counts daticles, not nodes, and a node costs up to three daticle levels: the `usr`
47/// itself, its payload map, and the list its children sit in. This is therefore an upper bound on
48/// the daticle depth of a tree that obeys the node depth limit, and the exact node depth is
49/// enforced by the validator.
50pub const DAT_DEPTH: usize = 3 * limit::DEPTH + 2;
51
52/// The limits a tree region is decoded under. See `SPEC.md` §5.
53pub fn decode_limits() -> DecodeLimits {
54 DecodeLimits::new(DAT_DEPTH, limit::TREE_BYTES)
55}
56
57/// Checks a decoded tree against every canonical encoding rule, naming the node and the rule broken.
58///
59/// The rules that survive decoding are all checked here: maps are `Dat::Map` (rule 2), keys are
60/// lowercase ASCII strings (rule 3), no `Dat::Box` and no `Dat::Opt` (rule 4), strings carry no
61/// forbidden control characters (rule 5), integers are exactly the width the schema declares
62/// (rules 1 and 6), and children sit in a `Dat::List` (rule 7). The rules that do not survive
63/// decoding are checked by [`decode`].
64pub fn check(tree: &Dat) -> Outcome<()> {
65 let mut next: usize = 0;
66 res!(check_node(tree, &mut next, 1));
67 Ok(())
68}
69
70/// Encodes a tree canonically, having first checked that it obeys §3.
71///
72/// A tree that fails [`check`] is never encoded, since encoding it would mint an address for a
73/// document that has no canonical form.
74pub fn encode(tree: &Dat) -> Outcome<Vec<u8>> {
75 res!(check(tree));
76 Ok(res!(tree.to_bytes(Vec::new())))
77}
78
79/// Decodes a tree region, rejecting bytes that are not the canonical encoding of the tree they
80/// decode to.
81///
82/// The buffer must hold the tree and nothing else. After decoding under the limits of §5 and
83/// checking the tree against §3, the tree is re-encoded and the bytes compared, which catches the
84/// non-canonicities the decoder cannot preserve: duplicate map keys, over-long `c64` lengths, and
85/// any other encoding of the same value that is not the one the encoder produces.
86pub fn decode(buf: &[u8]) -> Outcome<Dat> {
87 let (tree, n) = res!(Dat::from_bytes_limited(buf, &decode_limits()));
88 if n != buf.len() {
89 return Err(err!(
90 "The tree region is {} bytes, but the tree in it occupies {}. Bytes trailing a \
91 tree are not part of a canonical encoding.", buf.len(), n;
92 Invalid, Input, Excessive));
93 }
94 res!(check(&tree));
95 let reenc = res!(tree.to_bytes(Vec::new()));
96 if reenc.as_slice() != buf {
97 let at = match reenc.iter().zip(buf.iter()).position(|(a, b)| a != b) {
98 Some(i) => fmt!("first differing at byte {}", i),
99 None => fmt!("one is a prefix of the other"),
100 };
101 return Err(err!(
102 "The bytes are not the canonical encoding of the tree they decode to (SPEC.md §3): \
103 re-encoding gives {} bytes against the {} supplied, {}. A duplicate map key \
104 (rule 3), or a length written in more bytes than it needs, encodes one tree two \
105 ways, and is rejected rather than silently re-encoded.",
106 reenc.len(), buf.len(), at;
107 Invalid, Input, Mismatch));
108 }
109 Ok(tree)
110}
111
112/// Checks a string against §3 rule 5: no forbidden control characters, and Unicode NFC.
113///
114/// UTF-8 well-formedness and the absence of unpaired surrogates come free: BDAT rejects a string
115/// whose bytes are not UTF-8, and a Rust `String` cannot hold a surrogate. What remains is the
116/// control characters, and this rejects the whole Unicode `Cc` category, which is C0, C1 and
117/// delete, save for tab and newline. Carriage return is rejected too, so one line ending has one
118/// encoding.
119///
120/// The NFC requirement closes the last way a single logical document could hold two addresses. The
121/// letter é can be written as one code point or as an e followed by a combining accent. The two
122/// display identically, mean the same thing, and hash differently, so without this rule one
123/// document has two addresses and neither is wrong. Requiring the composed form makes the mapping
124/// from meaning to address a function again.
125pub fn check_string(s: &str) -> Outcome<()> {
126 for (i, ch) in s.chars().enumerate() {
127 if ch.is_control() && ch != '\t' && ch != '\n' {
128 return Err(err!(
129 "SPEC.md §3 rule 5: character {} of the string is U+{:04X}, a control \
130 character. Only tab and newline are permitted.", i, ch as u32;
131 Invalid, Input));
132 }
133 }
134 if !norm::is_normalised(s, Form::Nfc) {
135 return Err(err!(
136 "SPEC.md §3 rule 5: the string is not in Unicode NFC. Text that displays identically \
137 must encode identically, or one document has two addresses. Normalise the text to NFC \
138 before signing it.";
139 Invalid, Input));
140 }
141 Ok(())
142}
143
144/// Checks a map key against §3 rule 3: a lowercase ASCII string.
145pub fn check_key_string(k: &str) -> Outcome<()> {
146 if k.is_empty() {
147 return Err(err!(
148 "SPEC.md §3 rule 3: a map key must not be empty.";
149 Invalid, Input));
150 }
151 for (i, ch) in k.chars().enumerate() {
152 if !ch.is_ascii() {
153 return Err(err!(
154 "SPEC.md §3 rule 3: map key '{}' is not ASCII: character {} is U+{:04X}.",
155 k, i, ch as u32;
156 Invalid, Input));
157 }
158 if ch.is_ascii_uppercase() {
159 return Err(err!(
160 "SPEC.md §3 rule 3: map key '{}' is not lowercase: character {} is '{}'.",
161 k, i, ch;
162 Invalid, Input));
163 }
164 }
165 Ok(())
166}
167
168/// Checks one node, and recurses into its children in depth-first, pre-order, so that `next` names
169/// nodes exactly as §4.6 does.
170fn check_node(
171 node: &Dat,
172 next: &mut usize,
173 depth: usize,
174)
175 -> Outcome<()>
176{
177 let id = *next;
178 *next += 1;
179 if depth > limit::DEPTH {
180 return Err(err!(
181 "Node {}: nesting reaches depth {}, past the limit of {} (SPEC.md §5).",
182 id, depth, limit::DEPTH;
183 Invalid, Input, Excessive));
184 }
185 let (ukid, payload) = match node {
186 Dat::Usr(ukid, Some(boxd)) => (ukid, &**boxd),
187 Dat::Usr(ukid, None) => return Err(err!(
188 "Node {}: the usr daticle of kind code {} carries no payload.", id, ukid.code();
189 Invalid, Input, Missing)),
190 _ => return Err(err!(
191 "Node {}: a node is a usr daticle (SPEC.md §4.1), found {:?}.", id, node.kind();
192 Invalid, Input)),
193 };
194
195 // A node whose kind code this version does not know is still canonicalised: §4.5 lets an unknown
196 // kind carry a fallback of known nodes, and its bytes obey §3 either way. Whether the kind is
197 // legal at all, and whether its fallback is present, are the validator's call, not canon's.
198 match NodeKind::from_code(ukid.code()) {
199 Ok(kind) => check_known_node(kind, payload, id, next, depth),
200 Err(_) => match ReservedKind::from_code(ukid.code()) {
201 Some(reserved) => check_reserved_node(reserved, payload, id, next, depth),
202 None => check_unknown_node(payload, ukid.code(), id, next, depth),
203 },
204 }
205}
206
207/// Canonicalises a node whose kind this version knows, pinning each field to the type and width the
208/// schema declares before recursing into its children.
209fn check_known_node(
210 kind: NodeKind,
211 payload: &Dat,
212 id: usize,
213 next: &mut usize,
214 depth: usize,
215)
216 -> Outcome<()>
217{
218 // A text run carries a bare string, the one node whose payload is not a map.
219 if kind.payload_is_str() {
220 return match payload {
221 Dat::Str(s) => check_str_at(s, id, kind.label(), "text payload"),
222 _ => Err(err!(
223 "Node {} ({}): the payload of a text node is a str (SPEC.md §4.2), \
224 found {:?}.", id, kind.label(), payload.kind();
225 Invalid, Input)),
226 };
227 }
228
229 let map = match payload {
230 Dat::Map(map) => map,
231 Dat::OrdMap(_) => return Err(err!(
232 "Node {} ({}): SPEC.md §3 rule 2: a map is a Dat::Map, whose order follows its \
233 keys, never a Dat::OrdMap, whose order follows the author's typing.",
234 id, kind.label();
235 Invalid, Input)),
236 Dat::Box(_) => return Err(err!(
237 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so a payload is not \
238 wrapped in a Dat::Box.", id, kind.label();
239 Invalid, Input)),
240 _ => return Err(err!(
241 "Node {} ({}): the payload of this node is a map (SPEC.md §4.1), found {:?}.",
242 id, kind.label(), payload.kind();
243 Invalid, Input)),
244 };
245
246 // Check the payload's own keys and values before descending, so that the children of this node
247 // take the ids immediately after it.
248 let mut kids: Option<&Vec<Dat>> = None;
249 for (k, v) in map {
250 let key = match k {
251 Dat::Str(s) => s,
252 _ => return Err(err!(
253 "Node {} ({}): SPEC.md §3 rule 3: a map key is a Dat::Str, found {:?}.",
254 id, kind.label(), k.kind();
255 Invalid, Input)),
256 };
257 res!(check_key_at(key, id, kind.label()));
258 if key == KEY_CHILDREN {
259 kids = Some(res!(check_children(v, id, kind)));
260 } else if key == KEY_STYLE {
261 // The universal style field (§4.4), permitted on any map-payload node.
262 res!(check_style_name(v, id, kind.label()));
263 } else if key == KEY_STYLES {
264 // The document style table (§4.4); its placement is the validator's call.
265 res!(check_styles_table(v, id, kind.label()));
266 } else {
267 res!(check_field(key, v, id, kind));
268 }
269 }
270
271 if let Some(list) = kids {
272 for child in list {
273 res!(check_node(child, next, depth + 1));
274 }
275 }
276
277 Ok(())
278}
279
280/// Canonicalises a node of a kind the format reserves (§4.2): an `edit` or a `surface`.
281///
282/// Canon does not pin a reserved kind's field widths, and this is deliberate. Whether a reserved kind
283/// is admitted at all depends on the schema the envelope declares, which canon is not told and must
284/// not consult: a document carrying a surface is a tree the *validator* refuses, and it must reach the
285/// validator to be refused by name. A reserved node the schema does not admit may therefore carry
286/// anything at all -- including a `fallback`, which is exactly the smuggling attempt §4.5 exists to
287/// close -- and holding it to a field table here would refuse it as a canonicity fault rather than as
288/// what it is. The fields of a reserved node the schema *does* admit are pinned by the validator,
289/// which knows the schema.
290///
291/// What canon does own is the bytes. Every field is held to the structural rules of §3, and the two
292/// fields that carry nodes -- a surface's `alt` (§4.2) and a fallback (§4.5) -- are walked as nodes,
293/// so that the content standing in for an application is canonicalised exactly as the content around
294/// it is, and takes its node ids in the same pre-order.
295fn check_reserved_node(
296 reserved: ReservedKind,
297 payload: &Dat,
298 id: usize,
299 next: &mut usize,
300 depth: usize,
301)
302 -> Outcome<()>
303{
304 let label = reserved.label();
305 let map = match payload {
306 Dat::Map(map) => map,
307 Dat::OrdMap(_) => return Err(err!(
308 "Node {} ({}): SPEC.md §3 rule 2: a map is a Dat::Map, never a Dat::OrdMap.", id, label;
309 Invalid, Input)),
310 Dat::Box(_) => return Err(err!(
311 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so a payload is not wrapped in \
312 a Dat::Box.", id, label;
313 Invalid, Input)),
314 // A non-map payload is what the validator refuses; canon still holds its bytes to §3.
315 other => return check_struct(other, id),
316 };
317
318 // The alternative, then the fallback: both are lists of nodes, and both are walked. A `BTreeMap`
319 // hands the keys back in order, and 'alt' precedes 'fallback', so the ids fall in the order the
320 // bytes carry them.
321 let mut alt: Option<&Vec<Dat>> = None;
322 let mut fallback: Option<&Vec<Dat>> = None;
323 for (k, v) in map {
324 let key = match k {
325 Dat::Str(s) => s,
326 _ => return Err(err!(
327 "Node {} ({}): SPEC.md §3 rule 3: a map key is a Dat::Str, found {:?}.",
328 id, label, k.kind();
329 Invalid, Input)),
330 };
331 res!(check_key_at(key, id, label));
332 if key == KEY_ALT && reserved == ReservedKind::Surface {
333 alt = Some(res!(check_node_list(v, id, label, KEY_ALT)));
334 } else if key == KEY_FALLBACK {
335 fallback = Some(res!(check_node_list(v, id, label, KEY_FALLBACK)));
336 } else {
337 res!(check_struct(v, id));
338 }
339 }
340
341 if let Some(list) = alt {
342 for child in list {
343 res!(check_node(child, next, depth + 1));
344 }
345 }
346 if let Some(list) = fallback {
347 for child in list {
348 res!(check_node(child, next, depth + 1));
349 }
350 }
351
352 Ok(())
353}
354
355/// Canonicalises a node whose kind code this version does not know (§4.5).
356///
357/// Canon has no schema for an unknown kind, so it cannot pin the widths of its fields; it enforces
358/// only the structural rules of §3 that hold regardless of type. The one field it recognises is the
359/// `fallback` list, whose elements are known nodes and take node ids in pre-order like any other
360/// children. Whether the fallback is present and non-empty is the validator's call.
361fn check_unknown_node(
362 payload: &Dat,
363 code: u16,
364 id: usize,
365 next: &mut usize,
366 depth: usize,
367)
368 -> Outcome<()>
369{
370 let label = fmt!("unknown kind {}", code);
371 let map = match payload {
372 Dat::Map(map) => map,
373 Dat::OrdMap(_) => return Err(err!(
374 "Node {} ({}): SPEC.md §3 rule 2: a map is a Dat::Map, never a Dat::OrdMap.",
375 id, label;
376 Invalid, Input)),
377 Dat::Box(_) => return Err(err!(
378 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so a payload is not \
379 wrapped in a Dat::Box.", id, label;
380 Invalid, Input)),
381 // A non-map payload cannot carry a fallback, which the validator rejects; canon still holds
382 // its bytes to §3.
383 other => return check_struct(other, id),
384 };
385
386 let mut fallback: Option<&Vec<Dat>> = None;
387 for (k, v) in map {
388 let key = match k {
389 Dat::Str(s) => s,
390 _ => return Err(err!(
391 "Node {} ({}): SPEC.md §3 rule 3: a map key is a Dat::Str, found {:?}.",
392 id, label, k.kind();
393 Invalid, Input)),
394 };
395 res!(check_key_at(key, id, &label));
396 if key == KEY_FALLBACK {
397 fallback = Some(res!(check_node_list(v, id, &label, KEY_FALLBACK)));
398 } else {
399 // Canon does not know this field's width, so it holds only its structure to §3.
400 res!(check_struct(v, id));
401 }
402 }
403
404 if let Some(list) = fallback {
405 for child in list {
406 res!(check_node(child, next, depth + 1));
407 }
408 }
409
410 Ok(())
411}
412
413/// Checks the value under the `children` key, returning the list it must be.
414fn check_children<'a>(
415 v: &'a Dat,
416 id: usize,
417 kind: NodeKind,
418)
419 -> Outcome<&'a Vec<Dat>>
420{
421 if kind.content() == Content::None {
422 return Err(err!(
423 "Node {} ({}): this kind takes no children (SPEC.md §4.2), so the '{}' key is \
424 omitted rather than carried empty (SPEC.md §3 rule 4).",
425 id, kind.label(), KEY_CHILDREN;
426 Invalid, Input));
427 }
428 match v {
429 Dat::List(list) => {
430 if list.is_empty() {
431 Err(err!(
432 "Node {} ({}): SPEC.md §3 rule 4: a node with no children omits the '{}' \
433 key rather than carrying an empty list, which would give one document two \
434 encodings.", id, kind.label(), KEY_CHILDREN;
435 Invalid, Input))
436 } else {
437 Ok(list)
438 }
439 },
440 Dat::Vek(_) => Err(err!(
441 "Node {} ({}): SPEC.md §3 rule 7: children sit in a Dat::List, never a Dat::Vek, \
442 even where every child shares a kind.", id, kind.label();
443 Invalid, Input)),
444 Dat::Opt(_) | Dat::Box(_) => Err(err!(
445 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so the '{}' key carries a \
446 bare list.", id, kind.label(), KEY_CHILDREN;
447 Invalid, Input)),
448 _ => Err(err!(
449 "Node {} ({}): the '{}' key carries a list of nodes (SPEC.md §4.2), found {:?}.",
450 id, kind.label(), KEY_CHILDREN, v.kind();
451 Invalid, Input)),
452 }
453}
454
455/// Checks one field of a node's payload map against the type the schema declares for it.
456fn check_field(
457 key: &str,
458 v: &Dat,
459 id: usize,
460 kind: NodeKind,
461)
462 -> Outcome<()>
463{
464 let field = match kind.fields().iter().find(|f| f.name == key) {
465 Some(field) => field,
466 None => return Err(err!(
467 "Node {} ({}): SPEC.md §3 rule 1: field types are fixed by the schema, and the \
468 schema for this kind declares no field '{}'.", id, kind.label(), key;
469 Invalid, Input)),
470 };
471
472 // Rule 4 before rule 1, so that a wrapper is named as a wrapper rather than as a type error.
473 res!(check_no_wrapper(v, id, kind.label(), &fmt!("field '{}'", key)));
474
475 // A typed address is a structural map (§4.3), canonicalised entry by entry rather than pinned to
476 // a single daticle width.
477 if field.typ == FieldType::Address {
478 return check_address_map(v, id, kind.label());
479 }
480
481 let ok = match (field.typ, v) {
482 (FieldType::Str, Dat::Str(_)) => true,
483 (FieldType::U8, Dat::U8(_)) => true,
484 (FieldType::I8, Dat::I8(_)) => true,
485 (FieldType::U32, Dat::U32(_)) => true,
486 (FieldType::Bool, Dat::Bool(_)) => true,
487 (FieldType::Hash32, Dat::B32(_)) => true,
488 _ => false,
489 };
490 if !ok {
491 return Err(err!(
492 "Node {} ({}): SPEC.md §3 rules 1 and 6: field '{}' is declared {} by the schema, \
493 with no promotion and no demotion, but carries a {:?}.",
494 id, kind.label(), key, type_name(field.typ), v.kind();
495 Invalid, Input));
496 }
497 if let Dat::Str(s) = v {
498 res!(check_str_at(s, id, kind.label(), key));
499 }
500 Ok(())
501}
502
503/// The wire type a field type names, as the schema writes it.
504fn type_name(typ: FieldType) -> &'static str {
505 match typ {
506 FieldType::Str => "str",
507 FieldType::U8 => "u8",
508 FieldType::I8 => "i8",
509 FieldType::U32 => "u32",
510 FieldType::Bool => "bool",
511 FieldType::Hash32 => "b32",
512 FieldType::Address => "address",
513 FieldType::Nodes => "a non-empty list of nodes",
514 }
515}
516
517/// Pins the universal `style` field (§4.4) to a `str`, permitted on any map-payload node.
518fn check_style_name(
519 v: &Dat,
520 id: usize,
521 label: &str,
522)
523 -> Outcome<()>
524{
525 res!(check_no_wrapper(v, id, label, "the 'style' field"));
526 match v {
527 Dat::Str(s) => check_str_at(s, id, label, "the 'style' field"),
528 _ => Err(err!(
529 "Node {} ({}): SPEC.md §3 rule 1: the 'style' field names a style entry as a str, \
530 found {:?}.", id, label, v.kind();
531 Invalid, Input)),
532 }
533}
534
535/// Canonicalises the document `styles` table (§4.4): a map from style name to a style record.
536fn check_styles_table(
537 v: &Dat,
538 id: usize,
539 label: &str,
540)
541 -> Outcome<()>
542{
543 res!(check_no_wrapper(v, id, label, "the 'styles' table"));
544 let table = match v {
545 Dat::Map(map) => map,
546 Dat::OrdMap(_) => return Err(err!(
547 "Node {} ({}): SPEC.md §3 rule 2: the 'styles' table is a Dat::Map, whose order \
548 follows its keys, never a Dat::OrdMap.", id, label;
549 Invalid, Input)),
550 _ => return Err(err!(
551 "Node {} ({}): the 'styles' table is a map from style name to record (SPEC.md §4.4), \
552 found {:?}.", id, label, v.kind();
553 Invalid, Input)),
554 };
555 // An empty table defines nothing, exactly like an absent one, so accepting it would give one
556 // document two encodings and two addresses. It must be omitted rather than written empty.
557 if table.is_empty() {
558 return Err(err!(
559 "Node {} ({}): SPEC.md §3: an empty 'styles' table defines nothing and must be omitted, \
560 since it would otherwise give one document two encodings.", id, label;
561 Invalid, Input));
562 }
563 for (k, rec) in table {
564 let name = match k {
565 Dat::Str(s) => s,
566 _ => return Err(err!(
567 "Node {} ({}): SPEC.md §3 rule 3: a style name is a Dat::Str, found {:?}.",
568 id, label, k.kind();
569 Invalid, Input)),
570 };
571 res!(check_key_at(name, id, label));
572 res!(check_style_record(rec, id, label, name));
573 }
574 Ok(())
575}
576
577/// Canonicalises one style record: a map whose property values are pinned by their `StyleCheck`.
578fn check_style_record(
579 rec: &Dat,
580 id: usize,
581 label: &str,
582 name: &str,
583)
584 -> Outcome<()>
585{
586 let record = match rec {
587 Dat::Map(map) => map,
588 Dat::OrdMap(_) => return Err(err!(
589 "Node {} ({}): SPEC.md §3 rule 2: style record '{}' is a Dat::Map, never a \
590 Dat::OrdMap.", id, label, name;
591 Invalid, Input)),
592 Dat::Box(_) => return Err(err!(
593 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so style record '{}' is not \
594 wrapped in a Dat::Box.", id, label, name;
595 Invalid, Input)),
596 _ => return Err(err!(
597 "Node {} ({}): style record '{}' is a map (SPEC.md §4.4), found {:?}.",
598 id, label, name, rec.kind();
599 Invalid, Input)),
600 };
601 // An empty record sets no property and so has no effect, the same two-encodings trap as an empty
602 // table: a style worth naming defines at least one property.
603 if record.is_empty() {
604 return Err(err!(
605 "Node {} ({}): SPEC.md §3: style record '{}' is empty and sets nothing, so it must be \
606 removed rather than written empty.", id, label, name;
607 Invalid, Input));
608 }
609 for (k, v) in record {
610 let prop = match k {
611 Dat::Str(s) => s,
612 _ => return Err(err!(
613 "Node {} ({}): SPEC.md §3 rule 3: a style property key is a Dat::Str, found {:?}.",
614 id, label, k.kind();
615 Invalid, Input)),
616 };
617 res!(check_key_at(prop, id, label));
618 res!(check_no_wrapper(v, id, label, &fmt!("style property '{}'", prop)));
619 // Canon asks what a property IS, and never whether this tree may name it: a property's wire
620 // type is the same in every schema, so `grid` is a u8 wherever it is legal, and pinning that
621 // width is the same work in a document as in a chrome. Whether a document may name `grid` at
622 // all is the validator's question (§4.4), and it is asked whatever these bytes say.
623 let sf = match known_style_field(prop) {
624 Some(sf) => sf,
625 // An unknown style property has no declared width; canon holds its bytes to §3 and the
626 // validator rejects the property itself.
627 None => {
628 res!(check_struct(v, id));
629 continue;
630 },
631 };
632 // A border is the one style property that is not a scalar, so its shape is pinned by its own
633 // routine rather than by the width table below.
634 if sf.check == StyleCheck::Border {
635 res!(check_style_border(v, id, label, prop));
636 continue;
637 }
638 let ok = match (sf.check, v) {
639 (StyleCheck::Palette, Dat::Str(_)) => true,
640 (StyleCheck::Lang, Dat::Str(_)) => true,
641 (StyleCheck::Direction, Dat::Str(_)) => true,
642 (StyleCheck::Alignment, Dat::Str(_)) => true,
643 (StyleCheck::ScaleStep, Dat::I8(_)) => true,
644 (StyleCheck::Spacing, Dat::U8(_)) => true,
645 (StyleCheck::Tile, Dat::U16(_)) => true,
646 (StyleCheck::Share, Dat::U8(_)) => true,
647 (StyleCheck::Elevation, Dat::U8(_)) => true,
648 _ => false,
649 };
650 if !ok {
651 return Err(err!(
652 "Node {} ({}): SPEC.md §3 rules 1 and 6: style property '{}' is declared \
653 {} by the schema, with no promotion and no demotion, but carries a {:?}.",
654 id, label, prop, style_check_type(sf.check), v.kind();
655 Invalid, Input));
656 }
657 if let Dat::Str(s) = v {
658 res!(check_str_at(s, id, label, prop));
659 }
660 }
661 Ok(())
662}
663
664/// Canonicalises a style's `border`: a two-element list of a palette name and a width in pixels.
665///
666/// Its parts are pinned exactly as any other style value is. The width is the `u8` the schema
667/// declares, with no promotion and no demotion (§3 rules 1 and 6); the list is a `Dat::List` and
668/// never a `Dat::Vek` (rule 7); and the name obeys the string rules (rule 5). Neither part can be
669/// wrapped, since a wrapper is not a `Dat::Str` or a `Dat::U8` and the match below takes nothing else
670/// (rule 4).
671///
672/// Whether the name is one the palette holds is the validator's question and not canon's, exactly as
673/// with `fill` and `bg`: a colour outside the palette is a well-formed encoding of a style that means
674/// nothing, and it is refused for meaning nothing.
675fn check_style_border(
676 v: &Dat,
677 id: usize,
678 label: &str,
679 prop: &str,
680)
681 -> Outcome<()>
682{
683 let list = match v {
684 Dat::List(list) => list,
685 Dat::Vek(_) => return Err(err!(
686 "Node {} ({}): SPEC.md §3 rule 7: style property '{}' is a Dat::List, never a Dat::Vek.",
687 id, label, prop;
688 Invalid, Input)),
689 _ => return Err(err!(
690 "Node {} ({}): style property '{}' is a palette name and a width in pixels, written as \
691 a two-element list (SPEC.md §4.4), found {:?}.", id, label, prop, v.kind();
692 Invalid, Input)),
693 };
694 match list.as_slice() {
695 [Dat::Str(colour), Dat::U8(_)] => check_str_at(colour, id, label, prop),
696 _ => Err(err!(
697 "Node {} ({}): SPEC.md §3 rules 1 and 6: style property '{}' is declared {} by the \
698 schema, with no promotion and no demotion.",
699 id, label, prop, style_check_type(StyleCheck::Border);
700 Invalid, Input)),
701 }
702}
703
704/// The wire type a style property's check names, as the schema writes it.
705fn style_check_type(check: StyleCheck) -> &'static str {
706 match check {
707 StyleCheck::Palette => "str",
708 StyleCheck::Lang => "str",
709 StyleCheck::Direction => "str",
710 StyleCheck::Alignment => "str",
711 StyleCheck::ScaleStep => "i8",
712 StyleCheck::Spacing => "u8",
713 StyleCheck::Tile => "u16",
714 StyleCheck::Share => "u8",
715 StyleCheck::Elevation => "u8",
716 StyleCheck::Border => "a str and a u8, in a two-element list",
717 }
718}
719
720/// Canonicalises a `link` address map (§4.3), pinning `name` to a `str` and `hash` to a `b32`.
721///
722/// Canon enforces only the byte-canonicity of whatever entries are present; whether the map holds
723/// exactly one, and whether its key is one [`check_address`](crate::kinds::check_address) knows, is
724/// the validator's call.
725fn check_address_map(
726 v: &Dat,
727 id: usize,
728 label: &str,
729)
730 -> Outcome<()>
731{
732 let map = match v {
733 Dat::Map(map) => map,
734 Dat::OrdMap(_) => return Err(err!(
735 "Node {} ({}): SPEC.md §3 rule 2: a link address is a Dat::Map, whose order follows \
736 its keys, never a Dat::OrdMap.", id, label;
737 Invalid, Input)),
738 _ => return Err(err!(
739 "Node {} ({}): a link address is a single-entry map (SPEC.md §4.3), found {:?}.",
740 id, label, v.kind();
741 Invalid, Input)),
742 };
743 for (k, av) in map {
744 let key = match k {
745 Dat::Str(s) => s,
746 _ => return Err(err!(
747 "Node {} ({}): SPEC.md §3 rule 3: an address key is a Dat::Str, found {:?}.",
748 id, label, k.kind();
749 Invalid, Input)),
750 };
751 res!(check_key_at(key, id, label));
752 res!(check_no_wrapper(av, id, label, "an address value"));
753 if key == ADDR_NAME {
754 match av {
755 Dat::Str(s) => res!(check_str_at(s, id, label, "the address name")),
756 _ => return Err(err!(
757 "Node {} ({}): SPEC.md §3 rules 1 and 6: an address '{}' is a str, found {:?}.",
758 id, label, ADDR_NAME, av.kind();
759 Invalid, Input)),
760 }
761 } else if key == ADDR_HASH {
762 match av {
763 Dat::B32(_) => (),
764 _ => return Err(err!(
765 "Node {} ({}): SPEC.md §3 rules 1 and 6: an address '{}' is a b32, found {:?}.",
766 id, label, ADDR_HASH, av.kind();
767 Invalid, Input)),
768 }
769 } else {
770 // An unknown address key has no declared width; canon holds only its bytes to §3.
771 res!(check_struct(av, id));
772 }
773 }
774 Ok(())
775}
776
777/// Checks the value under a key that carries nodes, returning the list it must be.
778///
779/// Two keys do: an unknown kind's `fallback` (§4.5), and a surface's `alt` (§4.2). Both stand in for
780/// something the reader is not showing, and both are lists of ordinary nodes, canonicalised as such.
781fn check_node_list<'a>(
782 v: &'a Dat,
783 id: usize,
784 label: &str,
785 key: &str,
786)
787 -> Outcome<&'a Vec<Dat>>
788{
789 match v {
790 Dat::List(list) => Ok(list),
791 Dat::Vek(_) => Err(err!(
792 "Node {} ({}): SPEC.md §3 rule 7: the '{}' list is a Dat::List, never a Dat::Vek.",
793 id, label, key;
794 Invalid, Input)),
795 Dat::Opt(_) | Dat::Box(_) => Err(err!(
796 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so the '{}' key carries a \
797 bare list.", id, label, key;
798 Invalid, Input)),
799 _ => Err(err!(
800 "Node {} ({}): the '{}' key carries a list of nodes, found {:?}.",
801 id, label, key, v.kind();
802 Invalid, Input)),
803 }
804}
805
806/// Rejects a value wrapped in a redundant `Dat::Opt` or `Dat::Box` (§3 rule 4), naming the node.
807fn check_no_wrapper(
808 v: &Dat,
809 id: usize,
810 label: &str,
811 what: &str,
812)
813 -> Outcome<()>
814{
815 match v {
816 Dat::Opt(_) => Err(err!(
817 "Node {} ({}): SPEC.md §3 rule 4: {} carries a Dat::Opt. An optional field carries \
818 its bare value when present and is omitted when absent, so an optional never reaches \
819 the wire as a none, and never as a redundant some.", id, label, what;
820 Invalid, Input)),
821 Dat::Box(_) => Err(err!(
822 "Node {} ({}): SPEC.md §3 rule 4: no redundant wrappers, so {} is not wrapped in a \
823 Dat::Box.", id, label, what;
824 Invalid, Input)),
825 _ => Ok(()),
826 }
827}
828
829/// Canonicalises a value whose field width the schema does not fix, enforcing the structural rules
830/// of §3 that hold regardless of type.
831///
832/// It rejects a `Dat::OrdMap` (rule 2), a `Dat::Box` (rule 4), and a `Dat::Vek` (rule 7), insists on
833/// lowercase ASCII string keys (rule 3), and forbids control characters in strings (rule 5),
834/// recursing through maps and lists. It is used for the fields of an unknown kind (§4.5) and for any
835/// entry of an address map or style record whose key the schema does not name.
836fn check_struct(
837 v: &Dat,
838 id: usize,
839)
840 -> Outcome<()>
841{
842 match v {
843 Dat::Map(map) => {
844 for (k, val) in map {
845 let key = match k {
846 Dat::Str(s) => s,
847 _ => return Err(err!(
848 "Node {}: SPEC.md §3 rule 3: a map key is a Dat::Str, found {:?}.",
849 id, k.kind();
850 Invalid, Input)),
851 };
852 match check_key_string(key) {
853 Ok(()) => (),
854 Err(e) => return Err(err!(e,
855 "Node {}: the map carries a key the format does not permit (§3 rule 3).",
856 id;
857 Invalid, Input)),
858 }
859 res!(check_struct(val, id));
860 }
861 Ok(())
862 },
863 Dat::OrdMap(_) => Err(err!(
864 "Node {}: SPEC.md §3 rule 2: a map is a Dat::Map, never a Dat::OrdMap.", id;
865 Invalid, Input)),
866 Dat::Box(_) => Err(err!(
867 "Node {}: SPEC.md §3 rule 4: no redundant wrappers, so a value is not wrapped in a \
868 Dat::Box.", id;
869 Invalid, Input)),
870 Dat::Vek(_) => Err(err!(
871 "Node {}: SPEC.md §3 rule 7: a homogeneous sequence is still a Dat::List, never a \
872 Dat::Vek.", id;
873 Invalid, Input)),
874 Dat::List(list) => check_seq(list, id),
875 // A tuple, a user-tagged value, and an option each enclose further daticles. §4.5 holds an
876 // unknown kind's uninterpreted fields to §3, so the recursion must reach inside them, or a
877 // forbidden string or an OrdMap could hide in a field the schema does not name. Leaving these
878 // to the catch-all was the gap that let `(20|{...,"rows":[bad\rstring, 0]})` earn an address.
879 Dat::Tup2(a) => check_seq(&a[..], id),
880 Dat::Tup3(a) => check_seq(&a[..], id),
881 Dat::Tup4(a) => check_seq(&a[..], id),
882 Dat::Tup5(a) => check_seq(&a[..], id),
883 Dat::Tup6(a) => check_seq(&a[..], id),
884 Dat::Tup7(a) => check_seq(&a[..], id),
885 Dat::Tup8(a) => check_seq(&a[..], id),
886 Dat::Tup9(a) => check_seq(&a[..], id),
887 Dat::Tup10(a) => check_seq(&a[..], id),
888 Dat::Usr(_, Some(boxd)) => check_struct(boxd, id),
889 Dat::Usr(_, None) => Ok(()),
890 Dat::Opt(boxoptd) => match &**boxoptd {
891 Some(d) => check_struct(d, id),
892 None => Ok(()),
893 },
894 Dat::Str(s) => match check_string(s) {
895 Ok(()) => Ok(()),
896 Err(e) => Err(err!(e,
897 "Node {}: a string carries a character the format does not permit (§3 rule 5).",
898 id;
899 Invalid, Input)),
900 },
901 _ => Ok(()),
902 }
903}
904
905/// Runs [`check_struct`] over every element of a sequence, naming the node it sits in.
906fn check_seq(
907 items: &[Dat],
908 id: usize,
909)
910 -> Outcome<()>
911{
912 for item in items {
913 res!(check_struct(item, id));
914 }
915 Ok(())
916}
917
918/// Checks a string, naming the node it sits in if it fails.
919fn check_str_at(
920 s: &str,
921 id: usize,
922 label: &str,
923 what: &str,
924)
925 -> Outcome<()>
926{
927 match check_string(s) {
928 Ok(()) => Ok(()),
929 Err(e) => Err(err!(e,
930 "Node {} ({}): the {} carries a character the format does not permit.",
931 id, label, what;
932 Invalid, Input)),
933 }
934}
935
936/// Checks a map key, naming the node it sits in if it fails.
937fn check_key_at(
938 key: &str,
939 id: usize,
940 label: &str,
941)
942 -> Outcome<()>
943{
944 match check_key_string(key) {
945 Ok(()) => Ok(()),
946 Err(e) => Err(err!(e,
947 "Node {} ({}): the payload map carries a key the format does not permit.",
948 id, label;
949 Invalid, Input)),
950 }
951}
952
953#[cfg(test)]
954mod tests {
955 use super::*;
956
957 use oxedyne_fe2o3_jdat::usr::UsrKindId;
958
959 /// Builds a node of the given kind around the given payload.
960 fn node(kind: NodeKind, payload: Dat) -> Dat {
961 Dat::Usr(
962 UsrKindId::new(kind.code(), Some(kind.label()), None),
963 Some(Box::new(payload)),
964 )
965 }
966
967 /// Builds a payload map from string keys.
968 fn map(kv: Vec<(&str, Dat)>) -> Dat {
969 create_dat_map(
970 kv.into_iter().map(|(k, v)| (Dat::Str(k.to_string()), v)).collect()
971 )
972 }
973
974 /// Builds a text node.
975 fn text(s: &str) -> Dat {
976 node(NodeKind::Text, Dat::Str(s.to_string()))
977 }
978
979 /// A document exercising every v0 node kind once, with a style table, an optional field present,
980 /// an optional field absent, a node with no children, an inherited style property (`size`), a
981 /// self-only property (`bg`), a link by name, and a link by hash.
982 fn valid_doc() -> Dat {
983 node(NodeKind::Doc, map(vec![
984 ("title", Dat::Str("Style without a cascade".to_string())),
985 ("lang", Dat::Str("en".to_string())),
986 (KEY_STYLES, map(vec![
987 ("callout", map(vec![
988 ("bg", Dat::Str("muted".to_string())),
989 ("pad", Dat::U8(3)),
990 ("fill", Dat::Str("ink".to_string())),
991 ])),
992 ("lede", map(vec![
993 ("size", Dat::I8(1)),
994 ])),
995 ])),
996 (KEY_CHILDREN, Dat::List(vec![
997 node(NodeKind::Heading, map(vec![
998 ("level", Dat::U8(2)),
999 (KEY_CHILDREN, Dat::List(vec![text("Style without a cascade")])),
1000 ])),
1001 node(NodeKind::Section, map(vec![
1002 ("title", Dat::Str("A section".to_string())),
1003 (KEY_CHILDREN, Dat::List(vec![
1004 node(NodeKind::Para, map(vec![
1005 (KEY_STYLE, Dat::Str("lede".to_string())),
1006 (KEY_CHILDREN, Dat::List(vec![
1007 text("A run\twith a tab\nand a newline."),
1008 node(NodeKind::Emph, map(vec![
1009 ("strong", Dat::Bool(true)),
1010 (KEY_CHILDREN, Dat::List(vec![text("loud")])),
1011 ])),
1012 node(NodeKind::Link, map(vec![
1013 ("to", map(vec![
1014 ("name", Dat::Str("news.cricket".to_string())),
1015 ])),
1016 (KEY_CHILDREN, Dat::List(vec![text("a link")])),
1017 ])),
1018 node(NodeKind::Link, map(vec![
1019 ("to", map(vec![
1020 ("hash", Dat::from([0x9fu8; 32])),
1021 ])),
1022 (KEY_CHILDREN, Dat::List(vec![text("a hash link")])),
1023 ])),
1024 ])),
1025 ])),
1026 // A paragraph with no children omits the key entirely.
1027 node(NodeKind::Para, map(vec![])),
1028 node(NodeKind::Code, map(vec![
1029 ("lang", Dat::Str("rust".to_string())),
1030 ("text", Dat::Str("fn main() {}".to_string())),
1031 ])),
1032 node(NodeKind::Quote, map(vec![
1033 ("cite", Dat::Str("a source".to_string())),
1034 (KEY_CHILDREN, Dat::List(vec![
1035 node(NodeKind::Para, map(vec![
1036 (KEY_CHILDREN, Dat::List(vec![text("quoted")])),
1037 ])),
1038 ])),
1039 ])),
1040 node(NodeKind::List, map(vec![
1041 ("ordered", Dat::Bool(false)),
1042 (KEY_CHILDREN, Dat::List(vec![
1043 node(NodeKind::Item, map(vec![
1044 (KEY_CHILDREN, Dat::List(vec![
1045 node(NodeKind::Para, map(vec![
1046 (KEY_CHILDREN, Dat::List(vec![text("one")])),
1047 ])),
1048 ])),
1049 ])),
1050 ])),
1051 ])),
1052 // A box naming a style entry, with an image whose optional dimensions are
1053 // present.
1054 node(NodeKind::Boxx, map(vec![
1055 (KEY_STYLE, Dat::Str("callout".to_string())),
1056 (KEY_CHILDREN, Dat::List(vec![
1057 node(NodeKind::Image, map(vec![
1058 ("hash", Dat::from([0x01u8; 32])),
1059 ("alt", Dat::Str("a picture".to_string())),
1060 ("w", Dat::U32(640)),
1061 ("h", Dat::U32(480)),
1062 ])),
1063 ])),
1064 ])),
1065 ])),
1066 ])),
1067 ])),
1068 ]))
1069 }
1070
1071 /// Replaces the payload map of the heading, which is node 1 of `valid_doc`.
1072 fn doc_with_heading(payload: Dat) -> Dat {
1073 node(NodeKind::Doc, map(vec![
1074 ("title", Dat::Str("T".to_string())),
1075 ("lang", Dat::Str("en".to_string())),
1076 (KEY_CHILDREN, Dat::List(vec![
1077 Dat::Usr(
1078 UsrKindId::new(NodeKind::Heading.code(), Some("heading"), None),
1079 Some(Box::new(payload)),
1080 ),
1081 ])),
1082 ]))
1083 }
1084
1085 /// Asserts that checking the tree fails, and that the message names the given rule and node.
1086 fn rejects(tree: &Dat, rule: &str, node_id: &str) {
1087 match check(tree) {
1088 Ok(()) => assert!(false, "Expected a rejection naming {}, but the tree passed.", rule),
1089 Err(e) => {
1090 let msg = fmt!("{}", e);
1091 assert!(msg.contains(rule), "Expected {} in the error, got: {}", rule, msg);
1092 assert!(msg.contains(node_id), "Expected {} in the error, got: {}", node_id, msg);
1093 },
1094 }
1095 // A tree that fails the check is never encoded.
1096 assert!(encode(tree).is_err(), "A tree that fails check() was encoded anyway.");
1097 }
1098
1099 #[test]
1100 fn test_valid_tree_passes() -> Outcome<()> {
1101 res!(check(&valid_doc()));
1102 Ok(())
1103 }
1104
1105 #[test]
1106 fn test_round_trip() -> Outcome<()> {
1107 let tree = valid_doc();
1108 let enc1 = res!(encode(&tree));
1109 let dec = res!(decode(&enc1));
1110 let enc2 = res!(encode(&dec));
1111 assert_eq!(enc1, enc2, "Encoding is not stable across a decode.");
1112 // And the decoded tree is the tree.
1113 assert_eq!(tree, dec, "A tree did not survive its own encoding.");
1114 Ok(())
1115 }
1116
1117 #[test]
1118 fn test_rule_1_undeclared_field() {
1119 let tree = doc_with_heading(map(vec![
1120 ("level", Dat::U8(2)),
1121 ("colour", Dat::Str("red".to_string())),
1122 (KEY_CHILDREN, Dat::List(vec![text("h")])),
1123 ]));
1124 rejects(&tree, "rule 1", "Node 1");
1125 }
1126
1127 #[test]
1128 fn test_rule_2_ordmap_payload() {
1129 let payload = create_dat_ordmap(vec![
1130 (Dat::Str("level".to_string()), Dat::U8(2)),
1131 ]);
1132 rejects(&doc_with_heading(payload), "rule 2", "Node 1");
1133 }
1134
1135 #[test]
1136 fn test_rule_3_key_not_a_string() {
1137 let mut m = DaticleMap::new();
1138 m.insert(Dat::U8(1), Dat::U8(2));
1139 rejects(&doc_with_heading(Dat::Map(m)), "rule 3", "Node 1");
1140 }
1141
1142 #[test]
1143 fn test_rule_3_uppercase_key() {
1144 let tree = doc_with_heading(map(vec![
1145 ("Level", Dat::U8(2)),
1146 ]));
1147 rejects(&tree, "rule 3", "Node 1");
1148 }
1149
1150 #[test]
1151 fn test_rule_3_duplicate_key_survives_only_in_the_bytes() -> Outcome<()> {
1152 // A BTreeMap cannot hold a duplicate key, so a duplicate can only be written by hand, and
1153 // can only be caught by comparing the bytes with the re-encoding of what they decode to.
1154 let mut inner = Vec::new();
1155 for _ in 0..2 {
1156 inner = res!(Dat::Str("level".to_string()).to_bytes(inner));
1157 inner = res!(Dat::U8(2).to_bytes(inner));
1158 }
1159 let mut payload = Vec::new();
1160 payload.push(Dat::MAP_CODE);
1161 payload = res!(Dat::C64(inner.len() as u64).to_bytes(payload));
1162 payload.extend_from_slice(&inner);
1163
1164 let mut buf = Vec::new();
1165 buf.push(Dat::USR_CODE);
1166 buf.extend_from_slice(&NodeKind::Heading.code().to_be_bytes());
1167 buf.push(Dat::OPT_SOME_CODE);
1168 buf.extend_from_slice(&payload);
1169
1170 // The tree the bytes decode to is perfectly canonical, which is the point.
1171 let (tree, n) = res!(Dat::from_bytes(&buf));
1172 assert_eq!(n, buf.len());
1173 res!(check(&tree));
1174 assert!(res!(encode(&tree)).len() < buf.len(), "The duplicate key did not collapse.");
1175
1176 match decode(&buf) {
1177 Ok(_) => assert!(false, "Bytes carrying a duplicate map key were accepted."),
1178 Err(e) => {
1179 let msg = fmt!("{}", e);
1180 assert!(msg.contains("§3"), "Expected a canonicity rejection, got: {}", msg);
1181 },
1182 }
1183 Ok(())
1184 }
1185
1186 #[test]
1187 fn test_rule_4_box_payload() {
1188 let payload = Dat::Box(Box::new(map(vec![("level", Dat::U8(2))])));
1189 rejects(&doc_with_heading(payload), "rule 4", "Node 1");
1190 }
1191
1192 #[test]
1193 fn test_rule_4_optional_encoded_as_none() {
1194 // A section's title is optional, and an absent one is omitted, never written as a none.
1195 let tree = node(NodeKind::Doc, map(vec![
1196 ("title", Dat::Str("T".to_string())),
1197 ("lang", Dat::Str("en".to_string())),
1198 (KEY_CHILDREN, Dat::List(vec![
1199 node(NodeKind::Section, map(vec![
1200 ("title", Dat::Opt(Box::new(None))),
1201 (KEY_CHILDREN, Dat::List(vec![
1202 node(NodeKind::Para, map(vec![
1203 (KEY_CHILDREN, Dat::List(vec![text("p")])),
1204 ])),
1205 ])),
1206 ])),
1207 ])),
1208 ]));
1209 rejects(&tree, "rule 4", "Node 1");
1210 }
1211
1212 #[test]
1213 fn test_rule_4_optional_wrapped_in_some() {
1214 let tree = node(NodeKind::Doc, map(vec![
1215 ("title", Dat::Str("T".to_string())),
1216 ("lang", Dat::Str("en".to_string())),
1217 (KEY_CHILDREN, Dat::List(vec![
1218 node(NodeKind::Boxx, map(vec![
1219 ("style", Dat::Opt(Box::new(Some(Dat::Str("note".to_string()))))),
1220 (KEY_CHILDREN, Dat::List(vec![
1221 node(NodeKind::Para, map(vec![
1222 (KEY_CHILDREN, Dat::List(vec![text("p")])),
1223 ])),
1224 ])),
1225 ])),
1226 ])),
1227 ]));
1228 rejects(&tree, "rule 4", "Node 1");
1229 }
1230
1231 #[test]
1232 fn test_rule_4_empty_children_list() {
1233 let tree = doc_with_heading(map(vec![
1234 ("level", Dat::U8(2)),
1235 (KEY_CHILDREN, Dat::List(Vec::new())),
1236 ]));
1237 rejects(&tree, "rule 4", "Node 1");
1238 }
1239
1240 #[test]
1241 fn test_rule_4_children_on_a_childless_kind() {
1242 let tree = node(NodeKind::Doc, map(vec![
1243 ("title", Dat::Str("T".to_string())),
1244 ("lang", Dat::Str("en".to_string())),
1245 (KEY_CHILDREN, Dat::List(vec![
1246 node(NodeKind::Image, map(vec![
1247 ("hash", Dat::BU8(vec![0x01])),
1248 ("alt", Dat::Str("a".to_string())),
1249 (KEY_CHILDREN, Dat::List(vec![text("x")])),
1250 ])),
1251 ])),
1252 ]));
1253 rejects(&tree, "rule 4", "Node 1");
1254 }
1255
1256 #[test]
1257 fn test_rule_5_control_character_in_text() {
1258 let tree = doc_with_heading(map(vec![
1259 ("level", Dat::U8(2)),
1260 (KEY_CHILDREN, Dat::List(vec![text("a carriage\rreturn")])),
1261 ]));
1262 rejects(&tree, "rule 5", "Node 2");
1263 }
1264
1265 #[test]
1266 fn test_rule_5_decomposed_text_is_not_canonical() {
1267 // "café" with a combining acute accent: it displays exactly as the composed form does, and
1268 // would hash differently, so one document would have two addresses.
1269 let tree = doc_with_heading(map(vec![
1270 ("level", Dat::U8(2)),
1271 (KEY_CHILDREN, Dat::List(vec![text("cafe\u{0301}")])),
1272 ]));
1273 rejects(&tree, "rule 5", "Node 2");
1274 }
1275
1276 #[test]
1277 fn test_rule_5_composed_text_is_canonical() -> Outcome<()> {
1278 // The same word, composed. This is the one encoding the format accepts.
1279 let tree = doc_with_heading(map(vec![
1280 ("level", Dat::U8(2)),
1281 (KEY_CHILDREN, Dat::List(vec![text("caf\u{00E9}")])),
1282 ]));
1283 res!(check(&tree));
1284 Ok(())
1285 }
1286
1287 #[test]
1288 fn test_rule_5_decomposed_text_in_a_field() {
1289 // A node with no children omits the key (rule 4), so this doc carries none.
1290 let tree = node(NodeKind::Doc, map(vec![
1291 ("title", Dat::Str("cafe\u{0301}".to_string())),
1292 ("lang", Dat::Str("en".to_string())),
1293 ]));
1294 rejects(&tree, "rule 5", "Node 0");
1295 }
1296
1297 #[test]
1298 fn test_rule_5_control_character_in_a_field() {
1299 let tree = node(NodeKind::Doc, map(vec![
1300 ("title", Dat::Str("a bell\u{0007}".to_string())),
1301 ("lang", Dat::Str("en".to_string())),
1302 (KEY_CHILDREN, Dat::List(vec![
1303 node(NodeKind::Para, map(vec![
1304 (KEY_CHILDREN, Dat::List(vec![text("p")])),
1305 ])),
1306 ])),
1307 ]));
1308 rejects(&tree, "rule 5", "Node 0");
1309 }
1310
1311 #[test]
1312 fn test_rule_5_tab_and_newline_are_permitted() -> Outcome<()> {
1313 res!(check_string("a tab\tand a newline\n"));
1314 Ok(())
1315 }
1316
1317 #[test]
1318 fn test_rule_6_wrong_integer_width() {
1319 let tree = doc_with_heading(map(vec![
1320 ("level", Dat::U32(2)),
1321 (KEY_CHILDREN, Dat::List(vec![text("h")])),
1322 ]));
1323 rejects(&tree, "rules 1 and 6", "Node 1");
1324 }
1325
1326 #[test]
1327 fn test_rule_6_wrong_byte_string_width() {
1328 let tree = node(NodeKind::Doc, map(vec![
1329 ("title", Dat::Str("T".to_string())),
1330 ("lang", Dat::Str("en".to_string())),
1331 (KEY_CHILDREN, Dat::List(vec![
1332 node(NodeKind::Image, map(vec![
1333 ("hash", Dat::BU16(vec![0x01, 0x02])),
1334 ("alt", Dat::Str("a".to_string())),
1335 ])),
1336 ])),
1337 ]));
1338 rejects(&tree, "rules 1 and 6", "Node 1");
1339 }
1340
1341 #[test]
1342 fn test_rule_7_vek_children() {
1343 let vek = match Vek::try_from(vec![text("h")]) {
1344 Ok(vek) => vek,
1345 Err(_) => {
1346 assert!(false, "Could not build a Vek.");
1347 return;
1348 },
1349 };
1350 let tree = doc_with_heading(map(vec![
1351 ("level", Dat::U8(2)),
1352 (KEY_CHILDREN, Dat::Vek(vek)),
1353 ]));
1354 rejects(&tree, "rule 7", "Node 1");
1355 }
1356
1357 #[test]
1358 fn test_hash32_field_carrying_a_bu8() {
1359 // A content hash is a b32 (§4.2); a variable-length bu8 of the same bytes would encode the
1360 // reference two ways, so canon pins the width.
1361 let tree = node(NodeKind::Doc, map(vec![
1362 ("title", Dat::Str("T".to_string())),
1363 ("lang", Dat::Str("en".to_string())),
1364 (KEY_CHILDREN, Dat::List(vec![
1365 node(NodeKind::Image, map(vec![
1366 ("hash", Dat::BU8(vec![0x01, 0x02, 0x03])),
1367 ("alt", Dat::Str("a".to_string())),
1368 ])),
1369 ])),
1370 ]));
1371 rejects(&tree, "rules 1 and 6", "Node 1");
1372 }
1373
1374 #[test]
1375 fn test_i8_style_value_carrying_a_u8() {
1376 // A style record's size is a scale step, an i8 (§4.4); a u8 of the same value is a second
1377 // encoding, so canon pins the width even though both are non-negative.
1378 let tree = node(NodeKind::Doc, map(vec![
1379 ("title", Dat::Str("T".to_string())),
1380 ("lang", Dat::Str("en".to_string())),
1381 (KEY_STYLES, map(vec![
1382 ("lede", map(vec![
1383 ("size", Dat::U8(1)),
1384 ])),
1385 ])),
1386 (KEY_CHILDREN, Dat::List(vec![
1387 node(NodeKind::Para, map(vec![
1388 (KEY_CHILDREN, Dat::List(vec![text("p")])),
1389 ])),
1390 ])),
1391 ]));
1392 rejects(&tree, "rules 1 and 6", "Node 0");
1393 }
1394
1395 #[test]
1396 fn test_styles_table_with_an_ordmap() {
1397 // The style table is a Dat::Map, whose order follows its keys, never a Dat::OrdMap.
1398 let styles = create_dat_ordmap(vec![
1399 (Dat::Str("lede".to_string()), map(vec![("size", Dat::I8(1))])),
1400 ]);
1401 let tree = node(NodeKind::Doc, map(vec![
1402 ("title", Dat::Str("T".to_string())),
1403 ("lang", Dat::Str("en".to_string())),
1404 (KEY_STYLES, styles),
1405 (KEY_CHILDREN, Dat::List(vec![
1406 node(NodeKind::Para, map(vec![
1407 (KEY_CHILDREN, Dat::List(vec![text("p")])),
1408 ])),
1409 ])),
1410 ]));
1411 rejects(&tree, "rule 2", "Node 0");
1412 }
1413
1414 #[test]
1415 fn test_address_map_with_a_non_string_name() {
1416 // A link address's name is a str (§4.3); a u8 there is a wrong width, named at the link node.
1417 let tree = node(NodeKind::Doc, map(vec![
1418 ("title", Dat::Str("T".to_string())),
1419 ("lang", Dat::Str("en".to_string())),
1420 (KEY_CHILDREN, Dat::List(vec![
1421 node(NodeKind::Para, map(vec![
1422 (KEY_CHILDREN, Dat::List(vec![
1423 node(NodeKind::Link, map(vec![
1424 ("to", map(vec![("name", Dat::U8(1))])),
1425 (KEY_CHILDREN, Dat::List(vec![text("x")])),
1426 ])),
1427 ])),
1428 ])),
1429 ])),
1430 ]));
1431 rejects(&tree, "rules 1 and 6", "Node 2");
1432 }
1433
1434 #[test]
1435 fn test_unknown_kind_is_canonicalised() -> Outcome<()> {
1436 // Canon does not reject an unknown kind (§4.5): it canonicalises its bytes, recurses its
1437 // fallback of known nodes, and leaves the legality of the kind to the validator.
1438 let tree = node(NodeKind::Doc, map(vec![
1439 ("title", Dat::Str("T".to_string())),
1440 ("lang", Dat::Str("en".to_string())),
1441 (KEY_CHILDREN, Dat::List(vec![
1442 Dat::Usr(
1443 UsrKindId::new(20, Some("table"), None),
1444 Some(Box::new(map(vec![
1445 (KEY_FALLBACK, Dat::List(vec![
1446 node(NodeKind::Para, map(vec![
1447 (KEY_CHILDREN, Dat::List(vec![text("Q1 revenue")])),
1448 ])),
1449 ])),
1450 ]))),
1451 ),
1452 ])),
1453 ]));
1454 res!(check(&tree));
1455 Ok(())
1456 }
1457
1458 #[test]
1459 fn test_unknown_kind_non_canonical_field_rejected() {
1460 // An unknown kind's other fields are not interpreted, but are still held to §3: an OrdMap in
1461 // one is rejected even though canon does not know the field's width.
1462 let field = create_dat_ordmap(vec![
1463 (Dat::Str("a".to_string()), Dat::U8(1)),
1464 ]);
1465 let tree = node(NodeKind::Doc, map(vec![
1466 ("title", Dat::Str("T".to_string())),
1467 ("lang", Dat::Str("en".to_string())),
1468 (KEY_CHILDREN, Dat::List(vec![
1469 Dat::Usr(
1470 UsrKindId::new(20, Some("table"), None),
1471 Some(Box::new(map(vec![
1472 ("rows", field),
1473 (KEY_FALLBACK, Dat::List(vec![
1474 node(NodeKind::Para, map(vec![
1475 (KEY_CHILDREN, Dat::List(vec![text("Q1")])),
1476 ])),
1477 ])),
1478 ]))),
1479 ),
1480 ])),
1481 ]));
1482 rejects(&tree, "rule 2", "Node 1");
1483 }
1484
1485 #[test]
1486 fn test_unknown_kind_tuple_hides_control_char() {
1487 // The uninterpreted field is a tuple, not a map, and it hides a string with a carriage
1488 // return. The catch-all once let it through; check_struct must recurse the tuple (§4.5, §3
1489 // rule 5).
1490 let field = Dat::Tup2(Box::new([
1491 Dat::Str("bad\rstring".to_string()),
1492 Dat::U8(0),
1493 ]));
1494 let tree = node(NodeKind::Doc, map(vec![
1495 ("title", Dat::Str("T".to_string())),
1496 ("lang", Dat::Str("en".to_string())),
1497 (KEY_CHILDREN, Dat::List(vec![
1498 Dat::Usr(
1499 UsrKindId::new(20, Some("table"), None),
1500 Some(Box::new(map(vec![
1501 ("rows", field),
1502 (KEY_FALLBACK, Dat::List(vec![
1503 node(NodeKind::Para, map(vec![
1504 (KEY_CHILDREN, Dat::List(vec![text("Q1")])),
1505 ])),
1506 ])),
1507 ]))),
1508 ),
1509 ])),
1510 ]));
1511 rejects(&tree, "rule 5", "Node 1");
1512 }
1513
1514 #[test]
1515 fn test_empty_styles_table_rejected() {
1516 // An empty style table renders like an absent one, so accepting it would give one document
1517 // two addresses.
1518 let tree = node(NodeKind::Doc, map(vec![
1519 ("title", Dat::Str("T".to_string())),
1520 ("lang", Dat::Str("en".to_string())),
1521 ("styles", map(vec![])),
1522 (KEY_CHILDREN, Dat::List(vec![
1523 node(NodeKind::Para, map(vec![
1524 (KEY_CHILDREN, Dat::List(vec![text("x")])),
1525 ])),
1526 ])),
1527 ]));
1528 rejects(&tree, "empty", "Node 0");
1529 }
1530
1531 #[test]
1532 fn test_empty_style_record_rejected() {
1533 // A style that sets no property has no effect, the same two-address trap.
1534 let tree = node(NodeKind::Doc, map(vec![
1535 ("title", Dat::Str("T".to_string())),
1536 ("lang", Dat::Str("en".to_string())),
1537 ("styles", map(vec![("x", map(vec![]))])),
1538 (KEY_CHILDREN, Dat::List(vec![
1539 node(NodeKind::Para, map(vec![
1540 (KEY_CHILDREN, Dat::List(vec![text("x")])),
1541 ])),
1542 ])),
1543 ]));
1544 rejects(&tree, "empty", "Node 0");
1545 }
1546
1547 #[test]
1548 fn test_root_that_is_not_a_node() {
1549 match check(&Dat::Str("not a tree".to_string())) {
1550 Ok(()) => assert!(false, "A tree that is not a node was accepted."),
1551 Err(e) => {
1552 let msg = fmt!("{}", e);
1553 assert!(msg.contains("Node 0"), "Expected the node id in the error: {}", msg);
1554 },
1555 }
1556 }
1557
1558 #[test]
1559 fn test_trailing_bytes_rejected() -> Outcome<()> {
1560 let mut buf = res!(encode(&valid_doc()));
1561 buf.push(0x00);
1562 match decode(&buf) {
1563 Ok(_) => assert!(false, "Bytes trailing the tree were accepted."),
1564 Err(_) => (),
1565 }
1566 Ok(())
1567 }
1568}