Skip to main content

sim_codec_bridge/
warrant.rs

1use std::collections::BTreeSet;
2
3use sim_kernel::{ContentId, Datum, Error, Expr, NumberLiteral, Result, Symbol};
4
5use crate::{
6    AuthorityClass, BridgeBook, BridgeFrameBook, BridgeMoveBook, BridgePacket, BridgePartSpec,
7    FrameHoleKind, FrameKind, FrameSpec, RenderClass, ReplyRule, UnknownPolicy,
8};
9
10/// Policy for checking packet warrants at receive time.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum BridgeWarrantPolicy {
13    /// The receiver already trusts that it shares the sender's books.
14    SharedTrust,
15    /// The receiver checks packet warrant content ids against its local books.
16    Verify,
17}
18
19/// Content ids of protocol books and part specs used to build a packet.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct BridgeWarrant {
22    /// Content id of the dialogue move book.
23    pub moves: ContentId,
24    /// Content id of the fluent frame book.
25    pub frames: ContentId,
26    /// Content ids of the part specs used by this packet, keyed by part kind.
27    pub parts: Vec<(Symbol, ContentId)>,
28}
29
30/// Builds the warrant for `packet` from the local bridge book.
31pub fn warrant_for_packet(book: &BridgeBook, packet: &BridgePacket) -> Result<BridgeWarrant> {
32    let mut used_parts = BTreeSet::new();
33    for part in &packet.body {
34        used_parts.insert(part.kind.clone());
35    }
36    let mut parts = Vec::new();
37    for kind in used_parts {
38        let spec = book
39            .parts
40            .spec(&kind)
41            .ok_or_else(|| Error::Eval(format!("unknown BRIDGE part kind {kind}")))?;
42        parts.push((kind, part_spec_content_id(spec)?));
43    }
44    Ok(BridgeWarrant {
45        moves: move_book_content_id(&book.moves)?,
46        frames: frame_book_content_id(&book.frames)?,
47        parts,
48    })
49}
50
51/// Computes the content id for a dialogue move book registry record.
52pub fn move_book_content_id(book: &BridgeMoveBook) -> Result<ContentId> {
53    Datum::Node {
54        tag: Symbol::qualified("bridge", "MoveBook"),
55        fields: vec![(
56            Symbol::new("moves"),
57            Datum::Vector(book.specs().map(move_spec_datum).collect()),
58        )],
59    }
60    .content_id()
61}
62
63/// Computes the content id for a fluent frame book registry record.
64pub fn frame_book_content_id(book: &BridgeFrameBook) -> Result<ContentId> {
65    Datum::Node {
66        tag: Symbol::qualified("bridge", "FrameBook"),
67        fields: vec![(
68            Symbol::new("frames"),
69            Datum::Vector(book.specs().map(frame_spec_datum).collect()),
70        )],
71    }
72    .content_id()
73}
74
75/// Computes the content id for one part-kind registry record.
76pub fn part_spec_content_id(spec: &BridgePartSpec) -> Result<ContentId> {
77    part_spec_datum(spec).content_id()
78}
79
80pub(crate) fn content_id_datum(id: &ContentId) -> Datum {
81    Datum::Node {
82        tag: Symbol::qualified("core", "ContentId"),
83        fields: vec![
84            (
85                Symbol::new("algorithm"),
86                Datum::Symbol(id.algorithm.clone()),
87            ),
88            (Symbol::new("bytes"), Datum::Bytes(id.bytes.to_vec())),
89        ],
90    }
91}
92
93pub(crate) fn parse_content_id_string(text: &str) -> Result<ContentId> {
94    let (algorithm, hex) = text
95        .split_once(':')
96        .ok_or_else(|| Error::Eval(format!("content id must contain algorithm:hex: {text}")))?;
97    if hex.len() != 64 {
98        return Err(Error::Eval(format!(
99            "content id digest must be 64 hex digits: {text}"
100        )));
101    }
102    let mut bytes = [0u8; 32];
103    for (index, pair) in hex.as_bytes().chunks_exact(2).enumerate() {
104        let pair = std::str::from_utf8(pair)
105            .map_err(|err| Error::Eval(format!("content id digest is not UTF-8: {err}")))?;
106        bytes[index] = u8::from_str_radix(pair, 16)
107            .map_err(|err| Error::Eval(format!("content id digest is not hex: {err}")))?;
108    }
109    Ok(ContentId::from_bytes(parse_symbol(algorithm), bytes))
110}
111
112fn move_spec_datum(spec: &crate::BridgeMoveSpec) -> Datum {
113    Datum::Node {
114        tag: Symbol::qualified("bridge", "MoveSpec"),
115        fields: vec![
116            (Symbol::new("intent"), Datum::Symbol(spec.intent.clone())),
117            (
118                Symbol::new("replies-to"),
119                reply_rule_datum(&spec.replies_to),
120            ),
121            (
122                Symbol::new("requires-parts"),
123                Datum::Vector(
124                    spec.requires_parts
125                        .iter()
126                        .cloned()
127                        .map(Datum::Symbol)
128                        .collect(),
129                ),
130            ),
131            (
132                Symbol::new("requires-any-parts"),
133                Datum::Vector(
134                    spec.requires_any_parts
135                        .iter()
136                        .map(|group| {
137                            Datum::Vector(group.iter().cloned().map(Datum::Symbol).collect())
138                        })
139                        .collect(),
140                ),
141            ),
142            (Symbol::new("terminal"), Datum::Bool(spec.terminal)),
143        ],
144    }
145}
146
147fn reply_rule_datum(rule: &ReplyRule) -> Datum {
148    match rule {
149        ReplyRule::Opens => rule_node("Opens", Vec::new()),
150        ReplyRule::Any => rule_node("Any", Vec::new()),
151        ReplyRule::AnyNonReceipt => rule_node("AnyNonReceipt", Vec::new()),
152        ReplyRule::Only(intents) => rule_node(
153            "Only",
154            vec![(
155                Symbol::new("intents"),
156                Datum::Vector(intents.iter().cloned().map(Datum::Symbol).collect()),
157            )],
158        ),
159    }
160}
161
162fn rule_node(name: &str, fields: Vec<(Symbol, Datum)>) -> Datum {
163    let mut fields = fields;
164    fields.insert(
165        0,
166        (
167            Symbol::new("rule"),
168            Datum::Symbol(Symbol::qualified("bridge", name)),
169        ),
170    );
171    Datum::Node {
172        tag: Symbol::qualified("bridge", "ReplyRule"),
173        fields,
174    }
175}
176
177fn frame_spec_datum(spec: &FrameSpec) -> Datum {
178    Datum::Node {
179        tag: Symbol::qualified("bridge", "FrameSpec"),
180        fields: vec![
181            (Symbol::new("id"), Datum::Symbol(spec.id.clone())),
182            (
183                Symbol::new("kind"),
184                Datum::Symbol(frame_kind_symbol(spec.kind)),
185            ),
186            (Symbol::new("prefix"), Datum::String(spec.prefix.to_owned())),
187            (
188                Symbol::new("template"),
189                Datum::String(spec.template.to_owned()),
190            ),
191            (
192                Symbol::new("holes"),
193                Datum::Vector(
194                    spec.holes
195                        .iter()
196                        .map(|hole| Datum::Node {
197                            tag: Symbol::qualified("bridge", "FrameHoleSpec"),
198                            fields: vec![
199                                (Symbol::new("name"), Datum::Symbol(hole.name.clone())),
200                                (
201                                    Symbol::new("kind"),
202                                    Datum::Symbol(frame_hole_kind_symbol(hole.kind)),
203                                ),
204                            ],
205                        })
206                        .collect(),
207                ),
208            ),
209            (
210                Symbol::new("grammar-priority"),
211                Datum::Number(NumberLiteral {
212                    domain: Symbol::qualified("numbers", "u8"),
213                    canonical: spec.grammar_priority.to_string(),
214                }),
215            ),
216        ],
217    }
218}
219
220fn part_spec_datum(spec: &BridgePartSpec) -> Datum {
221    Datum::Node {
222        tag: Symbol::qualified("bridge", "PartSpec"),
223        fields: vec![
224            (Symbol::new("kind"), Datum::Symbol(spec.kind.clone())),
225            (Symbol::new("shape"), expr_datum(&spec.shape_expr)),
226            (
227                Symbol::new("render-class"),
228                Datum::Symbol(render_class_symbol(&spec.render_class)),
229            ),
230            (
231                Symbol::new("authority-class"),
232                Datum::Symbol(authority_class_symbol(&spec.authority_class)),
233            ),
234            (
235                Symbol::new("unknown-policy"),
236                Datum::Symbol(unknown_policy_symbol(&spec.unknown_policy)),
237            ),
238        ],
239    }
240}
241
242fn expr_datum(expr: &Expr) -> Datum {
243    Datum::Node {
244        tag: Symbol::qualified("bridge", "ExprJson"),
245        fields: vec![(
246            Symbol::new("json"),
247            Datum::String(
248                serde_json::to_string(&sim_codec_json::expr_to_json(expr))
249                    .expect("JSON value encodes"),
250            ),
251        )],
252    }
253}
254
255fn frame_kind_symbol(kind: FrameKind) -> Symbol {
256    let name = match kind {
257        FrameKind::Use => "Use",
258        FrameKind::Inform => "Inform",
259        FrameKind::Task => "Task",
260        FrameKind::Require => "Require",
261        FrameKind::Forbid => "Forbid",
262        FrameKind::Prefer => "Prefer",
263        FrameKind::Return => "Return",
264        FrameKind::Check => "Check",
265    };
266    Symbol::qualified("bridge", name)
267}
268
269fn frame_hole_kind_symbol(kind: FrameHoleKind) -> Symbol {
270    let name = match kind {
271        FrameHoleKind::Ref => "Ref",
272        FrameHoleKind::Term => "Term",
273        FrameHoleKind::Choice => "Choice",
274        FrameHoleKind::Path => "Path",
275        FrameHoleKind::Number => "Number",
276        FrameHoleKind::Prose => "Prose",
277    };
278    Symbol::qualified("bridge", name)
279}
280
281fn render_class_symbol(class: &RenderClass) -> Symbol {
282    let name = match class {
283        RenderClass::Structural => "Structural",
284        RenderClass::Frame => "Frame",
285        RenderClass::Data => "Data",
286        RenderClass::Evidence => "Evidence",
287        RenderClass::Review => "Review",
288        RenderClass::Vote => "Vote",
289        RenderClass::Patch => "Patch",
290        RenderClass::Fetch => "Fetch",
291        RenderClass::Return => "Return",
292        RenderClass::Receipt => "Receipt",
293        RenderClass::Extension => "Extension",
294    };
295    Symbol::qualified("bridge", name)
296}
297
298fn authority_class_symbol(class: &AuthorityClass) -> Symbol {
299    let name = match class {
300        AuthorityClass::Data => "Data",
301        AuthorityClass::Normative => "Normative",
302        AuthorityClass::Callable => "Callable",
303        AuthorityClass::Evidence => "Evidence",
304    };
305    Symbol::qualified("bridge", name)
306}
307
308fn unknown_policy_symbol(policy: &UnknownPolicy) -> Symbol {
309    let name = match policy {
310        UnknownPolicy::Reject => "Reject",
311        UnknownPolicy::PreserveDataOnly => "PreserveDataOnly",
312    };
313    Symbol::qualified("bridge", name)
314}
315
316fn parse_symbol(text: &str) -> Symbol {
317    match text.split_once('/') {
318        Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
319            Symbol::qualified(namespace.to_owned(), name.to_owned())
320        }
321        _ => Symbol::new(text.to_owned()),
322    }
323}