1use futures_timeout::TimeoutExt;
4use std::borrow::Borrow;
5
6use crate::path::{IpfsPath, PathRoot};
7use crate::repo::DataStore;
8use crate::Ipfs;
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 if let Ok(path) = rust_ipns::Record::decode(data).and_then(|record| {
82 record.verify(*peer)?;
84 let data = record.data()?;
85 let path = String::from_utf8_lossy(data.value());
86 IpfsPath::from_str(&path)
87 .and_then(|mut internal_path| {
88 internal_path.path.push_split(path_iter.by_ref()).map_err(
89 |_| crate::path::IpfsPathError::InvalidPath(path.to_string()),
90 )?;
91 Ok(internal_path)
92 })
93 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
94 }) {
95 return Ok(path);
96 }
97 }
98
99 let stream = self.ipfs.dht_get(mb).await?;
100
101 let mut records = stream
103 .filter_map(|record| async move {
104 let key = &record.key.as_ref()[6..];
105 let record = rust_ipns::Record::decode(&record.value).ok()?;
106 let peer_id = PeerId::from_bytes(key).ok()?;
107 record.verify(peer_id).ok()?;
108 Some(record)
109 })
110 .collect::<Vec<_>>()
111 .timeout(Duration::from_secs(60 * 2))
112 .await
113 .unwrap_or_default();
114
115 if records.is_empty() {
116 return Err(anyhow::anyhow!("No records found").into());
117 }
118
119 records.sort_by_key(|record| record.sequence());
120
121 let record = records.last().ok_or(anyhow::anyhow!("No records found"))?;
122
123 let data = record.data()?;
124
125 let path = String::from_utf8_lossy(data.value()).to_string();
126
127 IpfsPath::from_str(&path)
128 .map_err(IpnsError::from)
129 .and_then(|mut internal_path| {
130 internal_path
131 .path
132 .push_split(path_iter)
133 .map_err(|_| crate::path::IpfsPathError::InvalidPath(path))?;
134 Ok(internal_path)
135 })
136 }
137 #[cfg(feature = "dns")]
138 PathRoot::Dns(domain) => {
139 let path_iter = path.iter();
140 dnslink::resolve(self.resolver, domain, path_iter)
141 .await
142 .map_err(IpnsError::from)
143 }
144 }
145 }
146
147 pub async fn publish(
148 &self,
149 key: Option<&str>,
150 path: impl Borrow<IpfsPath>,
151 option: IpnsOption,
152 ) -> Result<IpfsPath, IpnsError> {
153 use connexa::prelude::dht::Quorum;
154 use ipld_core::cid::Cid;
155 use multihash::Multihash;
156 use std::str::FromStr;
157
158 let path = path.borrow();
159
160 let keypair = match key {
161 Some(key) => self.ipfs.keystore().get_keypair(key).await?,
162 None => self.ipfs.keypair().clone(),
163 };
164
165 let peer_id = keypair.public().to_peer_id();
166
167 let hash = Multihash::from_bytes(&peer_id.to_bytes()).map_err(anyhow::Error::from)?;
168
169 let cid = Cid::new_v1(0x72, hash);
170
171 let mb = format!(
172 "/ipns/{}",
173 cid.to_string_of_base(multibase::Base::Base36Lower)
174 .map_err(anyhow::Error::from)?
175 );
176
177 let repo = self.ipfs.repo();
178
179 let datastore = repo.data_store();
180
181 let record_data = datastore.get(mb.as_bytes()).await.unwrap_or_default();
182
183 let mut seq = 0;
184
185 if let Some(record) = record_data.as_ref() {
186 let record = rust_ipns::Record::decode(record)?;
187 record.verify(peer_id)?;
189
190 let data = record.data()?;
191
192 let ipfs_path = IpfsPath::from_str(&String::from_utf8_lossy(data.value()))?;
193
194 if ipfs_path.eq(path) {
195 return IpfsPath::from_str(&mb).map_err(IpnsError::from);
196 }
197
198 seq = record.sequence() + 1;
200 }
201
202 let path_bytes = path.to_string();
203
204 let record = rust_ipns::Record::new(
205 &keypair,
206 path_bytes.as_bytes(),
207 chrono::Duration::try_hours(48).expect("shouldnt panic"),
208 seq,
209 60000,
210 )?;
211
212 let bytes = record.encode()?;
213
214 datastore.put(mb.as_bytes(), &bytes).await?;
215
216 match option {
217 IpnsOption::DHT => self.ipfs.dht_put(&mb, bytes, Quorum::One).await?,
218 IpnsOption::Local => {}
219 };
220
221 IpfsPath::from_str(&mb).map_err(IpnsError::from)
222 }
223}
224
225#[non_exhaustive]
226#[derive(thiserror::Error, Debug)]
227pub enum IpnsError {
228 #[error(transparent)]
229 IpfsPath(#[from] crate::path::IpfsPathError),
230 #[error(transparent)]
231 Io(#[from] std::io::Error),
232 #[error(transparent)]
233 Any(#[from] anyhow::Error),
234}