snix_castore/proto/
url.rs1use crate::proto;
2use data_encoding::BASE64URL_NOPAD;
3use prost::Message;
4use tracing::warn;
5
6pub fn write_infused_nar_path(
8 w: &mut impl std::fmt::Write,
9 node: crate::Node,
10 nar_size: u64,
11) -> Result<(), std::fmt::Error> {
12 let proto_node = proto::Entry::from_name_and_node("".into(), node).encode_to_vec();
13
14 write!(
15 w,
16 "nar/snix-castore/{}?narsize={}",
17 BASE64URL_NOPAD.encode(&proto_node),
18 nar_size,
19 )
20}
21
22#[tracing::instrument(level = "trace")]
24pub fn parse_infused_nar_path(s: &str) -> Option<(crate::Node, u64)> {
25 let s = s.strip_prefix("nar/snix-castore/")?;
26 let (node_enc, nar_size_str) = s.split_once("?narsize=")?;
27 let nar_size: u64 = nar_size_str
28 .parse()
29 .inspect_err(|err| {
30 warn!(%err, "got invalid narsize");
31 })
32 .ok()?;
33
34 let node = parse_urlsafe_proto(node_enc)?;
35
36 Some((node, nar_size))
37}
38
39pub fn parse_urlsafe_proto(node_enc: impl AsRef<[u8]>) -> Option<crate::Node> {
41 if node_enc.as_ref().len() > BASE64URL_NOPAD.encode_len(4096) {
43 warn!("rejected too large root node");
44 return None;
45 }
46
47 let node_bytes = BASE64URL_NOPAD
48 .decode(node_enc.as_ref())
49 .inspect_err(|err| {
50 warn!(%err, "unable to decode node b64");
51 })
52 .ok()?;
53
54 let node_proto: proto::Entry = Message::decode(node_bytes.as_slice())
56 .inspect_err(|err| {
57 warn!(%err, "unable to decode node proto");
58 })
59 .ok()?;
60
61 let root_node = node_proto
62 .try_into_anonymous_node()
63 .inspect_err(|err| {
64 warn!(%err, "node validation failed");
65 })
66 .ok()?;
67
68 Some(root_node)
69}
70
71#[cfg(test)]
72mod test {
73 use rstest::rstest;
74
75 use crate::Node;
76 use crate::fixtures::{
77 DIRECTORY_COMPLICATED, HELLOWORLD_BLOB_CONTENTS, HELLOWORLD_BLOB_DIGEST,
78 };
79 use crate::proto::{parse_infused_nar_path, write_infused_nar_path};
80
81 #[rstest]
82 #[case::directory_complicated(
83 Node::Directory { digest: DIRECTORY_COMPLICATED.digest(), size: DIRECTORY_COMPLICATED.size() },
84 )]
85 #[case::blob_helloworld(
86 Node::File { digest: *HELLOWORLD_BLOB_DIGEST, size: HELLOWORLD_BLOB_CONTENTS.len() as u64, executable: false }
87 )]
88 fn roundtrip(#[case] node: Node) {
89 let nar_size: u64 = 42; let path = {
91 let mut path = String::new();
92 write_infused_nar_path(&mut path, node.clone(), nar_size).expect("write string");
93 path
94 };
95
96 assert_eq!(
97 (node, nar_size),
98 parse_infused_nar_path(&path).expect("to parse"),
99 "expected to roundtrip"
100 );
101 }
102}