ipld_dagpb/
codec.rs

1use core::convert::{TryFrom, TryInto};
2use std::collections::BTreeMap;
3
4use bytes::Bytes;
5use ipld_core::{cid::Cid, ipld::Ipld};
6use quick_protobuf::sizeofs::{sizeof_len, sizeof_varint};
7use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer, WriterBackend};
8
9use crate::Error;
10
11/// A protobuf ipld link.
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub struct PbLink {
14    /// Content identifier.
15    pub cid: Cid,
16    /// Name of the link.
17    pub name: Option<String>,
18    /// Size of the data.
19    pub size: Option<u64>,
20}
21
22/// A protobuf ipld node.
23#[derive(Debug, PartialEq, Eq, Clone, Default)]
24pub struct PbNode {
25    /// List of protobuf ipld links.
26    pub links: Vec<PbLink>,
27    /// Binary data blob.
28    pub data: Option<Bytes>,
29}
30
31/// A protobuf that references an ipld node.
32#[derive(Debug, PartialEq, Eq, Clone, Default)]
33pub(crate) struct PbNodeRef<'a> {
34    links: Vec<PbLink>,
35    data: Option<&'a [u8]>,
36}
37
38impl PbNode {
39    /// Deserializes a `PbNode` from bytes.
40    pub fn from_bytes(buf: Bytes) -> Result<Self, Error> {
41        let mut reader = BytesReader::from_bytes(&buf);
42        let node = PbNodeRef::from_reader(&mut reader, &buf)?;
43        let data = node.data.map(|d| buf.slice_ref(d));
44
45        Ok(PbNode {
46            links: node.links,
47            data,
48        })
49    }
50
51    /// Serializes a `PbNode` to bytes.
52    pub fn into_bytes(mut self) -> Vec<u8> {
53        // Links must be strictly sorted by name before encoding, leaving stable
54        // ordering where the names are the same (or absent).
55        self.links.sort_by(|a, b| {
56            let a = a.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]);
57            let b = b.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]);
58            a.cmp(b)
59        });
60
61        let mut buf = Vec::with_capacity(self.get_size());
62        let mut writer = Writer::new(&mut buf);
63        self.write_message(&mut writer)
64            .expect("protobuf to be valid");
65        buf
66    }
67}
68
69impl PbNodeRef<'_> {
70    /// Serializes a `PbNode` to bytes.
71    pub fn into_bytes(mut self) -> Vec<u8> {
72        // Links must be strictly sorted by name before encoding, leaving stable
73        // ordering where the names are the same (or absent).
74        self.links.sort_by(|a, b| {
75            let a = a.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]);
76            let b = b.name.as_ref().map(|s| s.as_bytes()).unwrap_or(&[][..]);
77            a.cmp(b)
78        });
79
80        let mut buf = Vec::with_capacity(self.get_size());
81        let mut writer = Writer::new(&mut buf);
82        self.write_message(&mut writer)
83            .expect("protobuf to be valid");
84        buf
85    }
86}
87
88impl From<PbNode> for Ipld {
89    fn from(node: PbNode) -> Self {
90        let mut map = BTreeMap::<String, Ipld>::new();
91        let links = node
92            .links
93            .into_iter()
94            .map(|link| link.into())
95            .collect::<Vec<Ipld>>();
96        map.insert("Links".to_string(), links.into());
97        if let Some(data) = node.data {
98            map.insert("Data".to_string(), Ipld::Bytes(data.to_vec()));
99        }
100        map.into()
101    }
102}
103
104impl From<PbLink> for Ipld {
105    fn from(link: PbLink) -> Self {
106        let mut map = BTreeMap::<String, Ipld>::new();
107        map.insert("Hash".to_string(), link.cid.into());
108
109        if let Some(name) = link.name {
110            map.insert("Name".to_string(), name.into());
111        }
112        if let Some(size) = link.size {
113            map.insert("Tsize".to_string(), size.into());
114        }
115        map.into()
116    }
117}
118
119impl<'a> TryFrom<&'a Ipld> for PbNodeRef<'a> {
120    type Error = Error;
121
122    fn try_from(ipld: &'a Ipld) -> core::result::Result<Self, Self::Error> {
123        let mut node = PbNodeRef::default();
124
125        match ipld {
126            Ipld::Map(map) => {
127                if map.is_empty() {
128                    return Err(Error::FromIpld(
129                        "DAG-PB must contain links or data".to_string(),
130                    ));
131                }
132
133                for (key, value) in map {
134                    match (key.as_str(), value) {
135                        ("Links", Ipld::List(links)) => {
136                            let mut prev_name = "".to_string();
137                            for link in links.iter() {
138                                match link {
139                                    Ipld::Map(_) => {
140                                        let pb_link: PbLink = link.try_into()?;
141                                        // Make sure the links are sorted correctly.
142                                        if let Some(ref name) = pb_link.name {
143                                            if name.as_bytes() < prev_name.as_bytes() {
144                                                // This error message isn't ideal, but the important thing is
145                                                // that it errors.
146                                                return Err(Error::LinksWrongOrder);
147                                            }
148                                            prev_name.clone_from(name)
149                                        }
150                                        node.links.push(pb_link)
151                                    }
152                                    other => {
153                                        return Err(Error::FromIpld(format!(
154                                            "Link entries must be an IPLD map, found: {:?}",
155                                            other
156                                        )))
157                                    }
158                                }
159                            }
160                        }
161                        ("Data", Ipld::Bytes(data)) => {
162                            node.data = Some(&data[..]);
163                        }
164                        (_, _) => {
165                            return Err(Error::FromIpld(
166                                "IPLD cannot be converted into DAG-PB".to_string(),
167                            ))
168                        }
169                    }
170                }
171            }
172            other => {
173                return Err(Error::FromIpld(format!(
174                    "Node must be an IPLD map, found: {:?}",
175                    other
176                )))
177            }
178        }
179
180        Ok(node)
181    }
182}
183
184impl TryFrom<&Ipld> for PbLink {
185    type Error = Error;
186
187    fn try_from(ipld: &Ipld) -> core::result::Result<PbLink, Self::Error> {
188        if let Ipld::Map(map) = ipld {
189            let mut cid = None;
190            let mut name = None;
191            let mut size = None;
192            for (key, value) in map {
193                match key.as_str() {
194                    "Hash" => {
195                        cid = if let Ipld::Link(cid) = value {
196                            Some(*cid)
197                        } else {
198                            return Err(Error::FromIpld(format!(
199                                "`Hash` must be an IPLD link, found: {:?}",
200                                value
201                            )));
202                        };
203                    }
204                    "Name" => {
205                        name = if let Ipld::String(name) = value {
206                            Some(name.clone())
207                        } else {
208                            return Err(Error::FromIpld(format!(
209                                "`Name` must be an IPLD string, found: {:?}",
210                                value
211                            )));
212                        }
213                    }
214                    "Tsize" => {
215                        size = if let Ipld::Integer(size) = value {
216                            Some(u64::try_from(*size).map_err(|_| {
217                                Error::FromIpld(
218                                    "`Tsize` must fit into a 64-bit integer".to_string(),
219                                )
220                            })?)
221                        } else {
222                            return Err(Error::FromIpld(format!(
223                                "`Tsize` must be an IPLD integer, found: {:?}",
224                                value
225                            )))?;
226                        }
227                    }
228                    other => {
229                        return Err(Error::FromIpld(format!(
230                            "Only `Hash`, `Name` and `Tsize` are allowed as keys, found: `{}`",
231                            other
232                        )));
233                    }
234                }
235            }
236
237            // Name and size are optional, CID is not.
238            match cid {
239                Some(cid) => Ok(PbLink { cid, name, size }),
240                None => Err(Error::FromIpld("`Hash` must be set".to_string())),
241            }
242        } else {
243            Err(Error::FromIpld("Links must be an IPLD map".to_string()))
244        }
245    }
246}
247
248impl<'a> MessageRead<'a> for PbLink {
249    fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result<Self> {
250        let mut cid = None;
251        let mut name = None;
252        let mut size = None;
253
254        while !r.is_eof() {
255            match r.next_tag(bytes) {
256                Ok(10) => {
257                    let bytes = r.read_bytes(bytes)?;
258                    cid = Some(
259                        Cid::try_from(bytes)
260                            .map_err(|e| quick_protobuf::Error::Message(e.to_string()))?,
261                    );
262                }
263                Ok(18) => name = Some(r.read_string(bytes)?.to_string()),
264                Ok(24) => size = Some(r.read_uint64(bytes)?),
265                Ok(_) => {
266                    return Err(quick_protobuf::Error::Message(
267                        "unexpected bytes".to_string(),
268                    ))
269                }
270                Err(e) => return Err(e),
271            }
272        }
273        Ok(PbLink {
274            cid: cid.ok_or_else(|| quick_protobuf::Error::Message("missing Hash".into()))?,
275            name,
276            size,
277        })
278    }
279}
280
281impl MessageWrite for PbLink {
282    fn get_size(&self) -> usize {
283        let mut size = 0;
284        let l = self.cid.encoded_len();
285        size += 1 + sizeof_len(l);
286
287        if let Some(ref name) = self.name {
288            size += 1 + sizeof_len(name.len());
289        }
290
291        if let Some(tsize) = self.size {
292            size += 1 + sizeof_varint(tsize);
293        }
294        size
295    }
296
297    fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> quick_protobuf::Result<()> {
298        let bytes = self.cid.to_bytes();
299        w.write_with_tag(10, |w| w.write_bytes(&bytes))?;
300
301        if let Some(ref name) = self.name {
302            w.write_with_tag(18, |w| w.write_string(name))?;
303        }
304        if let Some(size) = self.size {
305            w.write_with_tag(24, |w| w.write_uint64(size))?;
306        }
307        Ok(())
308    }
309}
310
311impl<'a> MessageRead<'a> for PbNodeRef<'a> {
312    fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result<Self> {
313        let mut msg = Self::default();
314        let mut links_before_data = false;
315        while !r.is_eof() {
316            match r.next_tag(bytes) {
317                Ok(18) => {
318                    // Links and data might be in any order, but they may not be interleaved.
319                    if links_before_data {
320                        return Err(quick_protobuf::Error::Message(
321                            "duplicate Links section".to_string(),
322                        ));
323                    }
324                    msg.links.push(r.read_message::<PbLink>(bytes)?)
325                }
326                Ok(10) => {
327                    msg.data = Some(r.read_bytes(bytes)?);
328                    if !msg.links.is_empty() {
329                        links_before_data = true
330                    }
331                }
332                Ok(_) => {
333                    return Err(quick_protobuf::Error::Message(
334                        "unexpected bytes".to_string(),
335                    ))
336                }
337                Err(e) => return Err(e),
338            }
339        }
340        Ok(msg)
341    }
342}
343
344impl MessageWrite for PbNode {
345    fn get_size(&self) -> usize {
346        let mut size = 0;
347        if let Some(ref data) = self.data {
348            size += 1 + sizeof_len(data.len());
349        }
350
351        size += self
352            .links
353            .iter()
354            .map(|s| 1 + sizeof_len((s).get_size()))
355            .sum::<usize>();
356
357        size
358    }
359
360    fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> quick_protobuf::Result<()> {
361        for s in &self.links {
362            w.write_with_tag(18, |w| w.write_message(s))?;
363        }
364
365        if let Some(ref data) = self.data {
366            w.write_with_tag(10, |w| w.write_bytes(data))?;
367        }
368
369        Ok(())
370    }
371}
372
373impl MessageWrite for PbNodeRef<'_> {
374    fn get_size(&self) -> usize {
375        let mut size = 0;
376        if let Some(data) = self.data {
377            size += 1 + sizeof_len(data.len());
378        }
379
380        size += self
381            .links
382            .iter()
383            .map(|s| 1 + sizeof_len((s).get_size()))
384            .sum::<usize>();
385
386        size
387    }
388
389    fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> quick_protobuf::Result<()> {
390        for s in &self.links {
391            w.write_with_tag(18, |w| w.write_message(s))?;
392        }
393
394        if let Some(data) = self.data {
395            w.write_with_tag(10, |w| w.write_bytes(data))?;
396        }
397
398        Ok(())
399    }
400}