common/linked_data/
link.rs

1use std::fmt::Display;
2
3use iroh::NodeAddr;
4use iroh_blobs::{ticket::BlobTicket, BlobFormat, Hash, HashAndFormat};
5use serde::{Deserialize, Serialize};
6
7use crate::crypto::PublicKey;
8
9use super::ipld::{Cid, LinkedData, Multihash, BLAKE3_HASH_CODE, LD_CBOR_CODEC, LD_RAW_CODEC};
10
11/// A Link is a wrapper around a CID (Content Identifier)
12/// representing a reference to content stored via the iroh-blobs protocol.
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14pub struct Link(Cid);
15
16impl Display for Link {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "{}", self.hash())
19    }
20}
21
22/**
23 * Links
24 * =====
25 * A Link is a reference to a block of data stored
26 *  by the iroh-blobs protocol.
27 * Links are implemented as Blake3 only multihashes wrapped in CIDs.
28 * CIDs encoding is used to communicate the expected encoding of
29 *  the referenced content, while HashAndFormat is used for the
30 *  retrieval of data over the iroh_blobs protocol.
31 * For our purposes, we keep the following assumptions:
32 *  - hashes are always blake3, and usually generated by iroh-blobs
33 *  - links may either be RAW or DAG-CBOR encoded depending on whether they
34 *   point to:
35 *    - public raw data (RAW)
36 *    - private raw data (RAW)
37 *    - private structured data (RAW, but underlying format is DAG-CBOR)
38 *    - public structured data (DAG-CBOR)
39 */
40#[allow(clippy::doc_overindented_list_items)]
41#[allow(clippy::doc_lazy_continuation)]
42impl Default for Link {
43    fn default() -> Self {
44        let hash = Hash::from_bytes([0; 32]);
45        let mh = Multihash::wrap(BLAKE3_HASH_CODE, hash.as_bytes()).expect("valid blake3 hash");
46        Link(Cid::new_v1(LD_RAW_CODEC, mh))
47    }
48}
49
50impl From<Link> for Cid {
51    fn from(val: Link) -> Self {
52        val.0
53    }
54}
55
56impl From<Cid> for Link {
57    fn from(val: Cid) -> Self {
58        let hash = val.hash();
59        let code = hash.code();
60        let codec = val.codec();
61        // panic if we don't have a blake3 hash, this means
62        //  it was not generated by our protocol
63        if code != BLAKE3_HASH_CODE {
64            panic!("invalid hash code");
65        }
66        // check we have one of our supported codecs
67        if codec != LD_RAW_CODEC && codec != LD_CBOR_CODEC {
68            panic!("unsupported codec");
69        }
70
71        Link(val)
72    }
73}
74
75impl From<Link> for LinkedData {
76    fn from(val: Link) -> Self {
77        LinkedData::Link(val.0)
78    }
79}
80
81impl From<Link> for HashAndFormat {
82    fn from(val: Link) -> Self {
83        HashAndFormat {
84            hash: val.hash(),
85            format: BlobFormat::Raw,
86        }
87    }
88}
89
90impl From<Link> for Hash {
91    fn from(val: Link) -> Self {
92        val.hash()
93    }
94}
95
96impl Link {
97    /// Create a new Link from a codec and hash
98    pub fn new(codec: u64, hash: Hash) -> Self {
99        let mh = Multihash::wrap(BLAKE3_HASH_CODE, hash.as_bytes()).expect("valid blake3 hash");
100        Link(Cid::new_v1(codec, mh))
101    }
102
103    /// Get the codec of this link
104    pub fn codec(&self) -> u64 {
105        self.0.codec()
106    }
107
108    /// Get the hash of this link
109    pub fn hash(&self) -> Hash {
110        let hash_digest = self.0.hash().digest();
111        let mut hash_bytes: [u8; 32] = [0; 32];
112        hash_bytes.copy_from_slice(hash_digest);
113        Hash::from_bytes(hash_bytes)
114    }
115
116    /// Get the underlying CID
117    pub fn cid(&self) -> &Cid {
118        &self.0
119    }
120
121    /// Create a ticket for this link with an optional blob format
122    ///
123    /// By default uses BlobFormat::Raw. For HashSeq collections, pass BlobFormat::HashSeq.
124    pub fn ticket(&self, source: PublicKey, format: Option<BlobFormat>) -> BlobTicket {
125        let node_addr = NodeAddr::new(*source);
126        BlobTicket::new(node_addr, self.hash(), format.unwrap_or(BlobFormat::Raw))
127    }
128}