Skip to main content

links_notation/binary/
mapping.rs

1//! Lossless mapping between LiNo documents and [`LinksPacket`]s.
2//!
3//! The mapping only uses links, the way linksplatform represents data:
4//!
5//! - `()` is the null link `0`.
6//! - A numeric reference `n` is `(Number unary(n))`, where `unary(0)` is null,
7//!   `2^0` is the marker `One`, `2^k` is `(2^(k-1) 2^(k-1))` and other numbers
8//!   are right-nested sums of powers of two from the highest bit down. With
9//!   external references enabled, `n` is sent as an external reference instead.
10//! - Any other reference is `(String code points…)`; every code point is a
11//!   unary number, or an external reference when those are enabled.
12//! - A link without an id and with exactly two values is a plain doublet.
13//! - A link without an id and any other number of values is a list.
14//! - A link with an id is `(Identified id values…)`.
15//! - The document is a list of its top-level links, stored last (the root).
16//!
17//! A link with two values is always a doublet. A list or typed value with
18//! any other number of values is a single link of that many references when
19//! [`BinaryLinoOptions::arity`] allows it (`[marker, elements…]` for typed
20//! values, the bare elements for lists), and otherwise the doublet
21//! `(marker chain)`, where `chain` is the nil-terminated cons list
22//! `(e1 (e2 (… (en 0))))`. Identical sub-links are emitted once and shared,
23//! because links are content-addressed.
24//!
25//! Links are numbered so that every link only refers to earlier ones:
26//! doublets of plain doublets first, then the rest in creation order.
27
28use super::error::{BinaryError, BinaryResult};
29use super::packet::{
30    external_capacity, ArityRange, DecodeLimits, LinksPacket, Reference, FIRST_LINK_ADDRESS,
31    IDENTIFIED, LIST, NULL, NUMBER, ONE, STRING,
32};
33use crate::LiNo;
34use std::cell::Cell;
35use std::collections::HashMap;
36
37/// A LiNo document: the list of top-level links of a message.
38pub type LinoDocument = Vec<LiNo<String>>;
39
40/// Optional features of the binary LiNo protocol.
41///
42/// Every feature is off by default; each one can be switched on
43/// independently, like stacking a decorator.
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
45pub struct BinaryLinoOptions {
46    /// Send numbers and code points as Hybrid external references instead of
47    /// in-band unary links. Halves the internal address range of each width.
48    pub external_references: bool,
49    /// The link lengths the encoder may use. The default, exactly 2, sends
50    /// only doublets; `2..3` adds triplets and `1..` any length. The range
51    /// must include 2.
52    pub arity: ArityRange,
53    /// Give every section of the packet the narrowest width its links need
54    /// instead of one width for the whole packet.
55    pub packed_widths: bool,
56}
57
58impl BinaryLinoOptions {
59    /// Enables or disables external references.
60    pub fn with_external_references(mut self, enabled: bool) -> Self {
61        self.external_references = enabled;
62        self
63    }
64
65    /// Sets the link lengths the encoder may use.
66    pub fn with_arity(mut self, arity: ArityRange) -> Self {
67        self.arity = arity;
68        self
69    }
70
71    /// Enables or disables packed widths.
72    pub fn with_packed_widths(mut self, enabled: bool) -> Self {
73        self.packed_widths = enabled;
74        self
75    }
76
77    /// The options a peer most likely used to write `packet`, so a reply can
78    /// be written in the same style.
79    pub fn of_packet(packet: &LinksPacket) -> Self {
80        let (shortest, longest) = packet
81            .links()
82            .map(|(_, link)| link.len() as u64)
83            .fold((2, 2), |(shortest, longest), length| {
84                (shortest.min(length), longest.max(length))
85            });
86        let mut widths = packet.sections.iter().map(|section| section.width);
87        let first_width = widths.next();
88        Self {
89            external_references: packet.external_references,
90            arity: ArityRange::between(shortest, longest),
91            packed_widths: widths.any(|width| Some(width) != first_width),
92        }
93    }
94}
95
96/// Converts a document into a packet.
97pub fn encode_document(
98    document: &[LiNo<String>],
99    options: BinaryLinoOptions,
100) -> BinaryResult<LinksPacket> {
101    encode_document_with_limits(document, options, &DecodeLimits::default())
102}
103
104/// Converts a native model into a packet using caller-selected work budgets.
105pub fn encode_document_with_limits(
106    document: &[LiNo<String>],
107    options: BinaryLinoOptions,
108    limits: &DecodeLimits,
109) -> BinaryResult<LinksPacket> {
110    options.arity.validate().map_err(BinaryError::Unencodable)?;
111    if !options.arity.contains(2) {
112        return Err(BinaryError::Unencodable(format!(
113            "arity {} does not include doublets (2)",
114            options.arity
115        )));
116    }
117    // Check the model iteratively before entering the recursive encoder.
118    let mut pending: Vec<_> = document.iter().map(|link| (link, 0)).collect();
119    let mut budget = limits.max_nodes;
120    let mut strings_left = limits.max_string_bytes;
121    while let Some((link, depth)) = pending.pop() {
122        if depth >= limits.max_depth {
123            return Err(BinaryError::Unencodable(format!(
124                "nesting deeper than {}",
125                limits.max_depth
126            )));
127        }
128        budget = budget
129            .checked_sub(1)
130            .ok_or_else(|| BinaryError::Unencodable("too many LiNo nodes".into()))?;
131        let text = match link {
132            LiNo::Ref(text) => Some(text),
133            LiNo::Link { id, values } => {
134                pending.extend(values.iter().map(|value| (value, depth + 1)));
135                if id.is_some() {
136                    budget = budget
137                        .checked_sub(1)
138                        .ok_or_else(|| BinaryError::Unencodable("too many LiNo nodes".into()))?;
139                }
140                id.as_ref()
141            }
142        };
143        if let Some(text) = text {
144            strings_left = strings_left
145                .checked_sub(text.len())
146                .ok_or_else(|| BinaryError::Unencodable("too many string bytes".into()))?;
147        }
148    }
149    let mut encoder = Encoder::new(options);
150    if !document.is_empty() {
151        let items = document
152            .iter()
153            .map(|link| encoder.encode(link))
154            .collect::<Vec<_>>();
155        encoder.list(items);
156    }
157    let packet = encoder.finish()?;
158    if packet.sections.len() as u64 > limits.max_links || packet.link_count() > limits.max_links {
159        return Err(BinaryError::Unencodable("too many packet links".into()));
160    }
161    let mut references_left = limits.max_references;
162    for (_, link) in packet.links() {
163        references_left = references_left
164            .checked_sub(link.len() as u64)
165            .ok_or_else(|| BinaryError::Unencodable("too many packet references".into()))?;
166    }
167    Ok(packet)
168}
169
170/// Converts a packet back into a document.
171pub fn decode_document(packet: &LinksPacket, limits: &DecodeLimits) -> BinaryResult<LinoDocument> {
172    let decoder = Decoder::new(packet, limits)?;
173    let Some(root) = decoder.links.len().checked_sub(1) else {
174        return Ok(Vec::new());
175    };
176    let root = Reference::Internal(FIRST_LINK_ADDRESS + root as u64);
177    let items = match decoder.view(root) {
178        View::Link(&[Reference::Internal(LIST), chain]) => decoder.chain(chain)?,
179        View::Link(items) if !starts_with_marker(items) => items.to_vec(),
180        _ => return Err(BinaryError::malformed("the root link is not a list")),
181    };
182    let mut budget = limits.max_nodes;
183    items
184        .into_iter()
185        .map(|item| decoder.decode(item, 0, &mut budget))
186        .collect()
187}
188
189/// Parses a canonical unsigned decimal number (no sign, no leading zeros).
190pub(crate) fn canonical_number(text: &str) -> Option<u64> {
191    let canonical = !text.is_empty()
192        && text.bytes().all(|byte| byte.is_ascii_digit())
193        && (text == "0" || !text.starts_with('0'));
194    canonical.then(|| text.parse().ok()).flatten()
195}
196
197#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
198enum Node {
199    Internal(u64),
200    External(u64),
201    /// A doublet of plain doublets, markers and externals.
202    Doublet(usize),
203    /// Any other link; numbered after every [`Node::Doublet`].
204    Tuple(usize),
205}
206
207struct Encoder {
208    options: BinaryLinoOptions,
209    doublets: Vec<Vec<Node>>,
210    tuples: Vec<Vec<Node>>,
211    created: HashMap<Vec<Node>, Node>,
212    powers: Vec<Node>,
213}
214
215impl Encoder {
216    fn new(options: BinaryLinoOptions) -> Self {
217        Self {
218            options,
219            doublets: Vec::new(),
220            tuples: Vec::new(),
221            created: HashMap::new(),
222            powers: vec![Node::Internal(ONE)],
223        }
224    }
225
226    fn link(&mut self, items: Vec<Node>) -> Node {
227        if let Some(&node) = self.created.get(&items) {
228            return node;
229        }
230        let refers_to_tuple = items.iter().any(|item| matches!(item, Node::Tuple(_)));
231        let node = if items.len() == 2 && !refers_to_tuple {
232            self.doublets.push(items.clone());
233            Node::Doublet(self.doublets.len() - 1)
234        } else {
235            self.tuples.push(items.clone());
236            Node::Tuple(self.tuples.len() - 1)
237        };
238        self.created.insert(items, node);
239        node
240    }
241
242    fn pair(&mut self, first: Node, second: Node) -> Node {
243        self.link(vec![first, second])
244    }
245
246    fn chain(&mut self, items: &[Node]) -> Node {
247        items
248            .iter()
249            .rev()
250            .fold(Node::Internal(NULL), |tail, &head| self.pair(head, tail))
251    }
252
253    /// One link of `items` when the arity allows it, except that two items
254    /// are always a doublet.
255    fn fits_one_link(&self, items: usize) -> bool {
256        items != 2 && self.options.arity.contains(items as u64)
257    }
258
259    fn typed(&mut self, marker: u64, elements: Vec<Node>) -> Node {
260        if self.fits_one_link(elements.len() + 1) {
261            let mut items = Vec::with_capacity(elements.len() + 1);
262            items.push(Node::Internal(marker));
263            items.extend(elements);
264            self.link(items)
265        } else {
266            let chain = self.chain(&elements);
267            self.pair(Node::Internal(marker), chain)
268        }
269    }
270
271    fn list(&mut self, elements: Vec<Node>) -> Node {
272        match elements.as_slice() {
273            [] => Node::Internal(NULL),
274            &[first, second] => self.pair(first, second),
275            _ if self.fits_one_link(elements.len()) => self.link(elements),
276            _ => self.typed(LIST, elements),
277        }
278    }
279
280    fn power(&mut self, exponent: usize) -> Node {
281        while self.powers.len() <= exponent {
282            let previous = *self.powers.last().expect("powers start with One");
283            let next = self.pair(previous, previous);
284            self.powers.push(next);
285        }
286        self.powers[exponent]
287    }
288
289    fn unary(&mut self, value: u64) -> Node {
290        let bits = (0..64).rev().filter(|bit| value & (1u64 << bit) != 0);
291        let powers = bits.map(|bit| self.power(bit)).collect::<Vec<_>>();
292        let Some((&last, rest)) = powers.split_last() else {
293            return Node::Internal(NULL);
294        };
295        rest.iter()
296            .rev()
297            .fold(last, |sum, &power| self.pair(power, sum))
298    }
299
300    fn scalar(&mut self, value: u64) -> Node {
301        if self.options.external_references && value <= external_capacity(8) {
302            Node::External(value)
303        } else {
304            self.unary(value)
305        }
306    }
307
308    fn reference(&mut self, text: &str) -> Node {
309        if let Some(value) = canonical_number(text) {
310            if self.options.external_references && value <= external_capacity(8) {
311                return Node::External(value);
312            }
313            let unary = self.unary(value);
314            return self.pair(Node::Internal(NUMBER), unary);
315        }
316        let code_points = text
317            .chars()
318            .map(|character| self.scalar(u64::from(u32::from(character))))
319            .collect();
320        self.typed(STRING, code_points)
321    }
322
323    fn encode(&mut self, link: &LiNo<String>) -> Node {
324        match link {
325            LiNo::Ref(text) => self.reference(text),
326            LiNo::Link { id: None, values } => {
327                let elements = values.iter().map(|value| self.encode(value)).collect();
328                self.list(elements)
329            }
330            LiNo::Link {
331                id: Some(id),
332                values,
333            } => {
334                let mut elements = Vec::with_capacity(values.len() + 1);
335                elements.push(self.reference(id));
336                elements.extend(values.iter().map(|value| self.encode(value)));
337                self.typed(IDENTIFIED, elements)
338            }
339        }
340    }
341
342    fn finish(self) -> BinaryResult<LinksPacket> {
343        let doublet_count = self.doublets.len() as u64;
344        let resolve = |node: &Node| match *node {
345            Node::Internal(address) => Reference::Internal(address),
346            Node::External(value) => Reference::External(value),
347            Node::Doublet(index) => Reference::Internal(FIRST_LINK_ADDRESS + index as u64),
348            Node::Tuple(index) => {
349                Reference::Internal(FIRST_LINK_ADDRESS + doublet_count + index as u64)
350            }
351        };
352        let links: Vec<(u64, Vec<Reference>)> = self
353            .doublets
354            .iter()
355            .chain(&self.tuples)
356            .enumerate()
357            .map(|(index, items)| {
358                (
359                    FIRST_LINK_ADDRESS + index as u64,
360                    items.iter().map(resolve).collect(),
361                )
362            })
363            .collect();
364        LinksPacket::pack(
365            self.options.external_references,
366            &links,
367            self.options.packed_widths,
368        )
369    }
370}
371
372enum View<'a> {
373    Null,
374    Marker(u64),
375    External(u64),
376    Link(&'a [Reference]),
377}
378
379fn is_marker(reference: Reference) -> bool {
380    matches!(reference, Reference::Internal(address) if (ONE..FIRST_LINK_ADDRESS).contains(&address))
381}
382
383fn starts_with_marker(items: &[Reference]) -> bool {
384    items.first().copied().is_some_and(is_marker)
385}
386
387struct Decoder<'a> {
388    limits: &'a DecodeLimits,
389    string_bytes_left: Cell<usize>,
390    /// `links[i]` is the link at address `FIRST_LINK_ADDRESS + i`.
391    links: Vec<&'a [Reference]>,
392    /// `unary[i]` is the number link `i` denotes, if it is a unary number.
393    unary: Vec<Option<u64>>,
394}
395
396impl<'a> Decoder<'a> {
397    fn new(packet: &'a LinksPacket, limits: &'a DecodeLimits) -> BinaryResult<Self> {
398        if packet.sections.len() as u64 > limits.max_links || packet.link_count() > limits.max_links
399        {
400            return Err(BinaryError::LimitExceeded(format!(
401                "packet declares more than {} links",
402                limits.max_links
403            )));
404        }
405        let mut links: Vec<&'a [Reference]> = Vec::new();
406        packet
407            .validate()
408            .map_err(|error| BinaryError::malformed(error.to_string()))?;
409        let mut unary: Vec<Option<u64>> = Vec::new();
410        let mut references_left = limits.max_references;
411        for (address, link) in packet.links() {
412            references_left = references_left
413                .checked_sub(link.len() as u64)
414                .ok_or_else(|| {
415                    BinaryError::LimitExceeded(format!(
416                        "references exceed the limit of {}",
417                        limits.max_references
418                    ))
419                })?;
420            let expected = FIRST_LINK_ADDRESS + links.len() as u64;
421            if address != expected {
422                return Err(BinaryError::malformed(format!(
423                    "a LiNo packet stores its links contiguously from address \
424                     {FIRST_LINK_ADDRESS}, found link {address} where {expected} belongs"
425                )));
426            }
427            if let Some(&target) = link.iter().find(
428                |&&reference| matches!(reference, Reference::Internal(target) if target >= address),
429            ) {
430                return Err(BinaryError::malformed(format!(
431                    "link {address} refers to {target:?}, which is not an earlier link"
432                )));
433            }
434            // Links only refer backwards, so one forward pass evaluates every
435            // unary number without recursion.
436            let value_of = |reference: Reference| match reference {
437                Reference::Internal(NULL) => Some(0),
438                Reference::Internal(ONE) => Some(1),
439                Reference::Internal(address) if address >= FIRST_LINK_ADDRESS => {
440                    unary[(address - FIRST_LINK_ADDRESS) as usize]
441                }
442                _ => None,
443            };
444            let value = match *link {
445                [source, target] => value_of(source)
446                    .zip(value_of(target))
447                    .and_then(|(source, target)| source.checked_add(target)),
448                _ => None,
449            };
450            unary.push(value);
451            links.push(link);
452        }
453        Ok(Self {
454            limits,
455            string_bytes_left: Cell::new(limits.max_string_bytes),
456            links,
457            unary,
458        })
459    }
460
461    fn view(&self, reference: Reference) -> View<'a> {
462        match reference {
463            Reference::External(value) => View::External(value),
464            Reference::Internal(NULL) => View::Null,
465            Reference::Internal(address) if address < FIRST_LINK_ADDRESS => View::Marker(address),
466            Reference::Internal(address) => {
467                View::Link(self.links[(address - FIRST_LINK_ADDRESS) as usize])
468            }
469        }
470    }
471
472    fn number(&self, reference: Reference) -> BinaryResult<u64> {
473        match reference {
474            Reference::External(value) => Ok(value),
475            Reference::Internal(NULL) => Ok(0),
476            Reference::Internal(ONE) => Ok(1),
477            Reference::Internal(address) if address >= FIRST_LINK_ADDRESS => self.unary
478                [(address - FIRST_LINK_ADDRESS) as usize]
479                .ok_or_else(|| BinaryError::malformed("expected a unary number")),
480            _ => Err(BinaryError::malformed("expected a unary number")),
481        }
482    }
483
484    /// The elements of the cons list `(e1 (e2 (… (en 0))))`.
485    fn chain(&self, mut tail: Reference) -> BinaryResult<Vec<Reference>> {
486        let mut elements = Vec::new();
487        loop {
488            match self.view(tail) {
489                View::Null => return Ok(elements),
490                View::Link(&[head, next]) => {
491                    if elements.len() >= self.limits.max_nodes {
492                        return Err(BinaryError::LimitExceeded("chain too long".into()));
493                    }
494                    elements.push(head);
495                    tail = next;
496                }
497                _ => return Err(BinaryError::malformed("broken element chain")),
498            }
499        }
500    }
501
502    fn typed(
503        &self,
504        marker: u64,
505        elements: &[Reference],
506        depth: usize,
507        budget: &mut usize,
508    ) -> BinaryResult<LiNo<String>> {
509        match marker {
510            NUMBER => match elements {
511                [value] => self.reference_text(self.number(*value)?.to_string()),
512                _ => Err(BinaryError::malformed("a number needs exactly one value")),
513            },
514            STRING => {
515                let mut text =
516                    String::with_capacity(elements.len().min(self.string_bytes_left.get()));
517                for &element in elements {
518                    let code_point = self.number(element)?;
519                    let character = u32::try_from(code_point)
520                        .ok()
521                        .and_then(char::from_u32)
522                        .ok_or_else(|| {
523                            BinaryError::malformed(format!("invalid code point {code_point}"))
524                        })?;
525                    if character.len_utf8()
526                        > self.string_bytes_left.get().saturating_sub(text.len())
527                    {
528                        return Err(BinaryError::LimitExceeded(
529                            "too many expanded string bytes".into(),
530                        ));
531                    }
532                    text.push(character);
533                }
534                self.reference_text(text)
535            }
536            LIST => self.list(elements, depth, budget),
537            IDENTIFIED => {
538                let (&id, values) = elements
539                    .split_first()
540                    .ok_or_else(|| BinaryError::malformed("an identified link needs an id"))?;
541                let LiNo::Ref(id) = self.decode(id, depth, budget)? else {
542                    return Err(BinaryError::malformed("a link id must be a reference"));
543                };
544                Ok(LiNo::Link {
545                    id: Some(id),
546                    values: self.values(values, depth, budget)?,
547                })
548            }
549            _ => Err(BinaryError::malformed(format!(
550                "marker {marker} cannot start a typed value"
551            ))),
552        }
553    }
554
555    fn list(
556        &self,
557        elements: &[Reference],
558        depth: usize,
559        budget: &mut usize,
560    ) -> BinaryResult<LiNo<String>> {
561        Ok(LiNo::Link {
562            id: None,
563            values: self.values(elements, depth, budget)?,
564        })
565    }
566
567    fn values(
568        &self,
569        elements: &[Reference],
570        depth: usize,
571        budget: &mut usize,
572    ) -> BinaryResult<Vec<LiNo<String>>> {
573        elements
574            .iter()
575            .map(|&element| self.decode(element, depth + 1, budget))
576            .collect()
577    }
578
579    fn reference_text(&self, text: String) -> BinaryResult<LiNo<String>> {
580        let remaining = self
581            .string_bytes_left
582            .get()
583            .checked_sub(text.len())
584            .ok_or_else(|| BinaryError::LimitExceeded("too many expanded string bytes".into()))?;
585        self.string_bytes_left.set(remaining);
586        Ok(LiNo::Ref(text))
587    }
588
589    fn decode(
590        &self,
591        reference: Reference,
592        depth: usize,
593        budget: &mut usize,
594    ) -> BinaryResult<LiNo<String>> {
595        if depth >= self.limits.max_depth {
596            return Err(BinaryError::LimitExceeded(format!(
597                "nesting deeper than {}",
598                self.limits.max_depth
599            )));
600        }
601        *budget = budget
602            .checked_sub(1)
603            .ok_or_else(|| BinaryError::LimitExceeded("too many LiNo nodes".into()))?;
604        match self.view(reference) {
605            View::Null => Ok(LiNo::Link {
606                id: None,
607                values: Vec::new(),
608            }),
609            View::External(value) => self.reference_text(value.to_string()),
610            View::Marker(marker) => Err(BinaryError::malformed(format!(
611                "marker {marker} used as a value"
612            ))),
613            View::Link(&[Reference::Internal(NUMBER), value]) => {
614                self.typed(NUMBER, &[value], depth, budget)
615            }
616            View::Link(&[Reference::Internal(marker), chain])
617                if is_marker(Reference::Internal(marker)) =>
618            {
619                let elements = self.chain(chain)?;
620                self.typed(marker, &elements, depth, budget)
621            }
622            View::Link(items) => match items.split_first() {
623                Some((&Reference::Internal(marker), elements)) if starts_with_marker(items) => {
624                    self.typed(marker, elements, depth, budget)
625                }
626                _ => self.list(items, depth, budget),
627            },
628        }
629    }
630}