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