Skip to main content

sim_codec_bridge/
identity.rs

1use sim_codec_json::expr_to_json;
2use sim_kernel::{ContentId, Datum, Expr, Result, Symbol};
3
4use crate::warrant::content_id_datum;
5use crate::{BridgePacket, BridgePart};
6
7/// Builds the canonical datum used to hash a packet with `cid` cleared.
8pub fn canonical_packet_datum(packet: &BridgePacket) -> Datum {
9    let packet = packet.canonicalized();
10    Datum::Node {
11        tag: Symbol::qualified("bridge", "Packet"),
12        fields: vec![
13            (
14                Symbol::new("header"),
15                Datum::Node {
16                    tag: Symbol::qualified("bridge", "Header"),
17                    fields: vec![
18                        (Symbol::new("cid"), Datum::Nil),
19                        (Symbol::new("move"), Datum::Symbol(packet.header.move_kind)),
20                        (Symbol::new("from"), Datum::String(packet.header.from)),
21                        (
22                            Symbol::new("to"),
23                            Datum::Vector(
24                                packet.header.to.into_iter().map(Datum::String).collect(),
25                            ),
26                        ),
27                        (Symbol::new("role"), Datum::Symbol(packet.header.role)),
28                        (
29                            Symbol::new("parents"),
30                            Datum::Vector(
31                                packet
32                                    .header
33                                    .parents
34                                    .into_iter()
35                                    .map(Datum::String)
36                                    .collect(),
37                            ),
38                        ),
39                        (Symbol::new("task"), Datum::Symbol(packet.header.task)),
40                        (Symbol::new("output"), Datum::Symbol(packet.header.output)),
41                        (
42                            Symbol::new("ceiling"),
43                            Datum::Vector(
44                                packet
45                                    .header
46                                    .ceiling
47                                    .into_iter()
48                                    .map(Datum::Symbol)
49                                    .collect(),
50                            ),
51                        ),
52                        (
53                            Symbol::new("context"),
54                            Datum::Vector(
55                                packet
56                                    .header
57                                    .context
58                                    .into_iter()
59                                    .map(Datum::Symbol)
60                                    .collect(),
61                            ),
62                        ),
63                        (
64                            Symbol::new("provenance"),
65                            Datum::Node {
66                                tag: Symbol::qualified("bridge", "Provenance"),
67                                fields: vec![
68                                    (
69                                        Symbol::new("author"),
70                                        Datum::Symbol(packet.header.provenance.author),
71                                    ),
72                                    (
73                                        Symbol::new("card"),
74                                        packet
75                                            .header
76                                            .provenance
77                                            .card
78                                            .map(Datum::String)
79                                            .unwrap_or(Datum::Nil),
80                                    ),
81                                ],
82                            },
83                        ),
84                    ],
85                },
86            ),
87            (
88                Symbol::new("body"),
89                Datum::Vector(packet.body.iter().map(part_datum).collect()),
90            ),
91            (
92                Symbol::new("warrant"),
93                packet
94                    .warrant
95                    .map(|warrant| Datum::Node {
96                        tag: Symbol::qualified("bridge", "Warrant"),
97                        fields: vec![
98                            (Symbol::new("moves"), content_id_datum(&warrant.moves)),
99                            (Symbol::new("frames"), content_id_datum(&warrant.frames)),
100                            (
101                                Symbol::new("parts"),
102                                Datum::Vector(
103                                    warrant
104                                        .parts
105                                        .into_iter()
106                                        .map(|(kind, id)| Datum::Node {
107                                            tag: Symbol::qualified("bridge", "WarrantPart"),
108                                            fields: vec![
109                                                (Symbol::new("kind"), Datum::Symbol(kind)),
110                                                (Symbol::new("cid"), content_id_datum(&id)),
111                                            ],
112                                        })
113                                        .collect(),
114                                ),
115                            ),
116                        ],
117                    })
118                    .unwrap_or(Datum::Nil),
119            ),
120        ],
121    }
122}
123
124/// Computes the canonical content id for a packet with `cid` cleared.
125pub fn packet_content_id(packet: &BridgePacket) -> Result<ContentId> {
126    canonical_packet_datum(packet).content_id()
127}
128
129/// Returns the stable text form of a content id.
130pub fn content_id_string(id: &ContentId) -> String {
131    format!("{}:{}", id.algorithm.as_qualified_str(), hex(&id.bytes))
132}
133
134/// Returns a packet with its canonical cid stamped into the header.
135pub fn stamp_packet_cid(packet: &BridgePacket) -> Result<BridgePacket> {
136    let mut packet = packet.canonicalized();
137    packet.header.cid = Some(content_id_string(&packet_content_id(&packet)?));
138    Ok(packet)
139}
140
141/// Verifies that a packet's stamped cid matches its canonical content.
142pub fn verify_packet_cid(packet: &BridgePacket) -> Result<()> {
143    let expected = content_id_string(&packet_content_id(packet)?);
144    match &packet.header.cid {
145        Some(actual) if actual == &expected => Ok(()),
146        Some(actual) => Err(sim_kernel::Error::Eval(format!(
147            "BRIDGE packet cid mismatch: expected {expected}, found {actual}"
148        ))),
149        None => Err(sim_kernel::Error::Eval(
150            "BRIDGE packet has no cid to verify".to_owned(),
151        )),
152    }
153}
154
155fn part_datum(part: &BridgePart) -> Datum {
156    Datum::Node {
157        tag: Symbol::qualified("bridge", "Part"),
158        fields: vec![
159            (Symbol::new("id"), Datum::Symbol(part.id.clone())),
160            (Symbol::new("kind"), Datum::Symbol(part.kind.clone())),
161            (Symbol::new("payload"), expr_datum(&part.payload)),
162        ],
163    }
164}
165
166fn expr_datum(expr: &Expr) -> Datum {
167    Datum::Node {
168        tag: Symbol::qualified("bridge", "ExprJson"),
169        fields: vec![(
170            Symbol::new("json"),
171            Datum::String(serde_json::to_string(&expr_to_json(expr)).expect("JSON value encodes")),
172        )],
173    }
174}
175
176fn hex(bytes: &[u8]) -> String {
177    bytes.iter().map(|byte| format!("{byte:02x}")).collect()
178}