1use futures_timeout::TimeoutExt;
4use std::borrow::Borrow;
5
6use crate::Ipfs;
7use crate::path::{IpfsPath, PathRoot};
8use crate::repo::DataStore;
9
10#[cfg(feature = "dns")]
11mod dnslink;
12
13#[cfg(feature = "dns")]
14use connexa::prelude::transport::dns::DnsResolver;
15
16#[derive(Clone, Debug)]
18pub struct Ipns {
19 ipfs: Ipfs,
20 #[cfg(feature = "dns")]
21 resolver: DnsResolver,
22}
23
24#[derive(Clone, Copy, Debug, Default)]
25pub enum IpnsOption {
26 Local,
27 #[default]
28 DHT,
29}
30
31impl Ipns {
32 pub fn new(ipfs: Ipfs) -> Self {
33 Ipns {
34 ipfs,
35 #[cfg(feature = "dns")]
36 resolver: DnsResolver::default(),
37 }
38 }
39
40 #[cfg(feature = "dns")]
42 pub fn set_resolver(&mut self, resolver: DnsResolver) {
43 self.resolver = resolver;
44 }
45
46 pub async fn resolve(&self, path: impl Borrow<IpfsPath>) -> Result<IpfsPath, IpnsError> {
50 let path = path.borrow();
51 match path.root() {
52 PathRoot::Ipld(_) => Ok(path.clone()),
53 PathRoot::Ipns(peer) => {
54 use std::str::FromStr;
55 use std::time::Duration;
56
57 use connexa::prelude::PeerId;
58 use futures::StreamExt;
59 use ipld_core::cid::Cid;
60 use multihash::Multihash;
61
62 let mut path_iter = path.iter();
63
64 let hash = Multihash::from_bytes(&peer.to_bytes()).map_err(anyhow::Error::from)?;
65
66 let cid = Cid::new_v1(0x72, hash);
67
68 let mb = format!(
69 "/ipns/{}",
70 cid.to_string_of_base(multibase::Base::Base36Lower)
71 .map_err(anyhow::Error::from)?
72 );
73
74 let repo = self.ipfs.repo();
78 let datastore = repo.data_store();
79
80 if let Ok(Some(data)) = datastore.get(mb.as_bytes()).await
81 && let Ok(path) = rust_ipns::Record::decode(data)
82 .map_err(std::io::Error::from)
83 .and_then(|record| {
84 record.verify(*peer)?;
86 let data = record.data()?;
87 let path = String::from_utf8_lossy(data.value());
88 IpfsPath::from_str(&path)
89 .and_then(|mut internal_path| {
90 internal_path.path.push_split(path_iter.by_ref()).map_err(
91 |_| {
92 crate::path::IpfsPathError::InvalidPath(
93 path.to_string(),
94 )
95 },
96 )?;
97 Ok(internal_path)
98 })
99 .map_err(|e| {
100 std::io::Error::new(std::io::ErrorKind::InvalidData, e)
101 })
102 })
103 {
104 return Ok(path);
105 }
106
107 let stream = self.ipfs.dht_get(mb).await?;
108
109 let records = stream
111 .filter_map(|record| async move {
112 let key = &record.key.as_ref()[6..];
113 let record = rust_ipns::Record::decode(&record.value).ok()?;
114 let peer_id = PeerId::from_bytes(key).ok()?;
115 record.verify(peer_id).ok()?;
116 Some(record)
117 })
118 .collect::<Vec<_>>()
119 .timeout(Duration::from_secs(60 * 2))
120 .await
121 .unwrap_or_default();
122
123 let record = records
124 .iter()
125 .max_by(|a, b| a.compare(b).unwrap_or(std::cmp::Ordering::Equal))
126 .ok_or_else(|| anyhow::anyhow!("No records found"))?;
127
128 let data = record.data()?;
129
130 let path = String::from_utf8_lossy(data.value()).to_string();
131
132 IpfsPath::from_str(&path)
133 .map_err(IpnsError::from)
134 .and_then(|mut internal_path| {
135 internal_path
136 .path
137 .push_split(path_iter)
138 .map_err(|_| crate::path::IpfsPathError::InvalidPath(path))?;
139 Ok(internal_path)
140 })
141 }
142 #[cfg(feature = "dns")]
143 PathRoot::Dns(domain) => {
144 let path_iter = path.iter();
145 dnslink::resolve(self.resolver, domain, path_iter)
146 .await
147 .map_err(IpnsError::from)
148 }
149 }
150 }
151
152 pub async fn publish(
153 &self,
154 key: Option<&str>,
155 path: impl Borrow<IpfsPath>,
156 option: IpnsOption,
157 ) -> Result<IpfsPath, IpnsError> {
158 use connexa::prelude::dht::Quorum;
159 use ipld_core::cid::Cid;
160 use multihash::Multihash;
161 use std::str::FromStr;
162
163 let path = path.borrow();
164
165 let keypair = match key {
166 Some(key) => self
167 .ipfs
168 .keychain()
169 .get(key)
170 .await
171 .map_err(anyhow::Error::from)?,
172 None => self.ipfs.keypair().clone(),
173 };
174
175 let peer_id = keypair.public().to_peer_id();
176
177 let hash = Multihash::from_bytes(&peer_id.to_bytes()).map_err(anyhow::Error::from)?;
178
179 let cid = Cid::new_v1(0x72, hash);
180
181 let mb = format!(
182 "/ipns/{}",
183 cid.to_string_of_base(multibase::Base::Base36Lower)
184 .map_err(anyhow::Error::from)?
185 );
186
187 let repo = self.ipfs.repo();
188
189 let datastore = repo.data_store();
190
191 let record_data = datastore.get(mb.as_bytes()).await.unwrap_or_default();
192
193 let mut seq = 0;
194
195 if let Some(record) = record_data.as_ref() {
196 let record = rust_ipns::Record::decode(record)?;
197 record.verify(peer_id)?;
199
200 let data = record.data()?;
201
202 let ipfs_path = IpfsPath::from_str(&String::from_utf8_lossy(data.value()))?;
203
204 if ipfs_path.eq(path) {
205 return IpfsPath::from_str(&mb).map_err(IpnsError::from);
206 }
207
208 seq = record.sequence() + 1;
210 }
211
212 let path_bytes = path.to_string();
213
214 let record = rust_ipns::Record::new(
215 &keypair,
216 path_bytes.as_bytes(),
217 chrono::Utc::now() + chrono::Duration::try_hours(48).expect("shouldnt panic"),
218 seq,
219 std::time::Duration::from_secs(60),
221 )?;
222
223 let bytes = record.encode()?;
224
225 datastore.put(mb.as_bytes(), &bytes).await?;
226
227 match option {
228 IpnsOption::DHT => self.ipfs.dht_put(&mb, bytes, Quorum::One).await?,
229 IpnsOption::Local => {}
230 };
231
232 IpfsPath::from_str(&mb).map_err(IpnsError::from)
233 }
234}
235
236#[non_exhaustive]
237#[derive(thiserror::Error, Debug)]
238pub enum IpnsError {
239 #[error(transparent)]
240 IpfsPath(#[from] crate::path::IpfsPathError),
241 #[error(transparent)]
242 Io(#[from] std::io::Error),
243 #[error(transparent)]
244 Ipns(#[from] rust_ipns::Error),
245 #[error(transparent)]
246 Any(#[from] anyhow::Error),
247}