twine_lib/twine/
strand.rs

1use super::{Tagged, Tixel, TwineBlock};
2use crate::errors::VerificationError;
3use crate::Ipld;
4use crate::{
5  as_cid::AsCid,
6  crypto::{get_hasher, PublicKey},
7  schemas::StrandSchemaVersion,
8  specification::Subspec,
9  verify::Verified,
10};
11use ipld_core::{cid::Cid, codec::Codec, serde::from_ipld};
12use multihash_codetable::Code;
13use semver::Version;
14use serde::de::DeserializeOwned;
15use serde_ipld_dagcbor::codec::DagCborCodec;
16use serde_ipld_dagjson::codec::DagJsonCodec;
17use std::{fmt::Display, sync::Arc};
18
19/// A Strand represents the metadata for the chain of Tixels
20///
21/// A Strand is verified for integrity and authenticity via
22/// the CID and public key it contains.
23///
24/// The most common way to obtain a Strand will be from a [`crate::twine::Twine`],
25/// a [`crate::resolver::Resolver`], or using [`Strand::from_tagged_dag_json`]
26/// or [`Strand::from_block`].
27///
28/// A Strand contains an Arc to its underlying data, so it is
29/// efficient to clone and pass around.
30///
31/// # See also
32///
33/// - [`Tixel`]
34/// - [`crate::twine::Twine`]
35#[derive(Debug, PartialEq, Eq, Hash, Clone)]
36pub struct Strand(pub(crate) Arc<Verified<StrandSchemaVersion>>);
37
38impl Strand {
39  /// Create a new Strand from its underlying data
40  ///
41  /// It is uncommon to use this directly.
42  pub fn try_new<C>(container: C) -> Result<Self, VerificationError>
43  where
44    C: TryInto<StrandSchemaVersion>,
45    VerificationError: From<<C as TryInto<StrandSchemaVersion>>::Error>,
46  {
47    let container = container.try_into()?;
48    Ok(Self(Arc::new(Verified::try_new(container)?)))
49  }
50
51  /// Get the CID of the Strand
52  pub fn cid(&self) -> Cid {
53    *self.0.cid()
54  }
55
56  /// Get the public key of the Strand
57  pub fn key(&self) -> PublicKey {
58    self.0.key()
59  }
60
61  /// Get the radix value of the skiplist
62  pub fn radix(&self) -> u8 {
63    self.0.radix()
64  }
65
66  /// Get the spec string of the Strand
67  pub fn spec_str(&self) -> &str {
68    self.0.spec_str()
69  }
70
71  /// Get the twine version of this record
72  pub fn version(&self) -> Version {
73    self.0.version()
74  }
75
76  /// Get the subspec of this record if it exists
77  pub fn subspec(&self) -> Option<Subspec> {
78    self.0.subspec()
79  }
80
81  /// Get the details of the Strand
82  ///
83  /// Details describe the Strand in a human-readable way
84  /// and can contain any data, usually described by the subspec.
85  pub fn details(&self) -> &Ipld {
86    self.0.details()
87  }
88
89  /// Deserialize the details as a specific type
90  pub fn extract_details<T: DeserializeOwned>(&self) -> Result<T, VerificationError> {
91    let details = self.details();
92    Ok(from_ipld(details.clone()).map_err(|e| VerificationError::Payload(e.to_string()))?)
93  }
94
95  /// Get the expiry date of the Strand
96  pub fn expiry(&self) -> Option<chrono::DateTime<chrono::Utc>> {
97    self.0.expiry()
98  }
99
100  /// Verify a Tixel using this Strand's public key
101  pub fn verify_tixel(&self, tixel: &Tixel) -> Result<(), VerificationError> {
102    self.0.verify_tixel(tixel)
103  }
104
105  /// Get the hasher ([`Code`]) used to compute the CID
106  pub fn hasher(&self) -> Code {
107    self.0.hasher()
108  }
109}
110
111impl From<Strand> for Cid {
112  fn from(t: Strand) -> Self {
113    t.cid()
114  }
115}
116
117impl AsCid for Strand {
118  fn as_cid(&self) -> &Cid {
119    self.0.cid()
120  }
121}
122
123impl TwineBlock for Strand {
124  fn cid(&self) -> &Cid {
125    self.as_cid()
126  }
127
128  fn from_tagged_dag_json<S: Display>(json: S) -> Result<Self, VerificationError> {
129    let t: Tagged<Strand> = DagJsonCodec::decode_from_slice(json.to_string().as_bytes())?;
130    Ok(t.unpack())
131  }
132
133  fn from_bytes_unchecked(hasher: Code, bytes: Vec<u8>) -> Result<Self, VerificationError> {
134    let mut twine: StrandSchemaVersion = DagCborCodec::decode_from_slice(bytes.as_slice())?;
135    // if v1... recompute cid
136    if let StrandSchemaVersion::V1(_) = twine {
137      twine.compute_cid(hasher);
138    }
139    Ok(Self(Arc::new(Verified::try_new(twine)?)))
140  }
141
142  fn from_block<T: AsRef<[u8]>>(cid: Cid, bytes: T) -> Result<Self, VerificationError> {
143    let hasher = get_hasher(&cid)?;
144    let twine = Self::from_bytes_unchecked(hasher, bytes.as_ref().to_vec())?;
145    twine.verify_cid(&cid)?;
146    Ok(twine)
147  }
148
149  fn tagged_dag_json(&self) -> String {
150    format!(
151      "{{\"cid\":{},\"data\":{}}}",
152      String::from_utf8(DagJsonCodec::encode_to_vec(&self.cid()).unwrap()).unwrap(),
153      String::from_utf8(DagJsonCodec::encode_to_vec(&self.0).unwrap()).unwrap()
154    )
155  }
156
157  fn bytes(&self) -> Arc<[u8]> {
158    DagCborCodec::encode_to_vec(&self.0)
159      .unwrap()
160      .as_slice()
161      .into()
162  }
163
164  fn content_bytes(&self) -> Arc<[u8]> {
165    self.0.content_bytes()
166  }
167}
168
169impl Display for Strand {
170  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171    write!(f, "{}", self.tagged_dag_json_pretty())
172  }
173}