ipfs_private_example/
main.rs1#![doc = include_str!("../README.md")]
22
23use std::{env, error::Error, fs, path::Path, str::FromStr};
24
25use either::Either;
26use futures::prelude::*;
27use libp2p::{
28 core::transport::upgrade::Version,
29 gossipsub, identify,
30 multiaddr::Protocol,
31 noise, ping,
32 pnet::{PnetConfig, PreSharedKey},
33 swarm::{NetworkBehaviour, SwarmEvent},
34 tcp, yamux, Multiaddr, Transport,
35};
36use tokio::{io, io::AsyncBufReadExt, select};
37use tracing_subscriber::EnvFilter;
38
39fn get_ipfs_path() -> Box<Path> {
42 env::var("IPFS_PATH")
43 .map(|ipfs_path| Path::new(&ipfs_path).into())
44 .unwrap_or_else(|_| {
45 env::var("HOME")
46 .map(|home| Path::new(&home).join(".ipfs"))
47 .expect("could not determine home directory")
48 .into()
49 })
50}
51
52fn get_psk(path: &Path) -> std::io::Result<Option<String>> {
54 let swarm_key_file = path.join("swarm.key");
55 match fs::read_to_string(swarm_key_file) {
56 Ok(text) => Ok(Some(text)),
57 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
58 Err(e) => Err(e),
59 }
60}
61
62fn strip_peer_id(addr: &mut Multiaddr) {
65 let last = addr.pop();
66 match last {
67 Some(Protocol::P2p(peer_id)) => {
68 let mut addr = Multiaddr::empty();
69 addr.push(Protocol::P2p(peer_id));
70 println!("removing peer id {addr} so this address can be dialed by rust-libp2p");
71 }
72 Some(other) => addr.push(other),
73 _ => {}
74 }
75}
76
77fn parse_legacy_multiaddr(text: &str) -> Result<Multiaddr, Box<dyn Error>> {
80 let sanitized = text
81 .split('/')
82 .map(|part| if part == "ipfs" { "p2p" } else { part })
83 .collect::<Vec<_>>()
84 .join("/");
85 let mut res = Multiaddr::from_str(&sanitized)?;
86 strip_peer_id(&mut res);
87 Ok(res)
88}
89
90#[tokio::main]
91async fn main() -> Result<(), Box<dyn Error>> {
92 let _ = tracing_subscriber::fmt()
93 .with_env_filter(EnvFilter::from_default_env())
94 .try_init();
95
96 let ipfs_path = get_ipfs_path();
97 println!("using IPFS_PATH {ipfs_path:?}");
98 let psk: Option<PreSharedKey> = get_psk(&ipfs_path)?
99 .map(|text| PreSharedKey::from_str(&text))
100 .transpose()?;
101
102 if let Some(psk) = psk {
103 println!("using swarm key with fingerprint: {}", psk.fingerprint());
104 }
105
106 let gossipsub_topic = gossipsub::IdentTopic::new("chat");
108
109 #[derive(NetworkBehaviour)]
111 struct MyBehaviour {
112 gossipsub: gossipsub::Behaviour,
113 identify: identify::Behaviour,
114 ping: ping::Behaviour,
115 }
116
117 let mut swarm = libp2p::SwarmBuilder::with_new_identity()
118 .with_tokio()
119 .with_other_transport(|key| {
120 let noise_config = noise::Config::new(key).unwrap();
121 let yamux_config = yamux::Config::default();
122
123 let base_transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true));
124 let maybe_encrypted = match psk {
125 Some(psk) => Either::Left(
126 base_transport
127 .and_then(move |socket, _| PnetConfig::new(psk).handshake(socket)),
128 ),
129 None => Either::Right(base_transport),
130 };
131 maybe_encrypted
132 .upgrade(Version::V1Lazy)
133 .authenticate(noise_config)
134 .multiplex(yamux_config)
135 })?
136 .with_dns()?
137 .with_behaviour(|key| {
138 let gossipsub_config = gossipsub::ConfigBuilder::default()
139 .max_transmit_size(262144)
140 .build()
141 .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; Ok(MyBehaviour {
143 gossipsub: gossipsub::Behaviour::new(
144 gossipsub::MessageAuthenticity::Signed(key.clone()),
145 gossipsub_config,
146 )
147 .expect("Valid configuration"),
148 identify: identify::Behaviour::new(identify::Config::new(
149 "/ipfs/0.1.0".into(),
150 key.public(),
151 )),
152 ping: ping::Behaviour::new(ping::Config::new()),
153 })
154 })?
155 .build();
156
157 println!("Subscribing to {gossipsub_topic:?}");
158 swarm
159 .behaviour_mut()
160 .gossipsub
161 .subscribe(&gossipsub_topic)
162 .unwrap();
163
164 for to_dial in std::env::args().skip(1) {
166 let addr: Multiaddr = parse_legacy_multiaddr(&to_dial)?;
167 swarm.dial(addr)?;
168 println!("Dialed {to_dial:?}")
169 }
170
171 let mut stdin = io::BufReader::new(io::stdin()).lines();
173
174 swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
176
177 loop {
179 select! {
180 Ok(Some(line)) = stdin.next_line() => {
181 if let Err(e) = swarm
182 .behaviour_mut()
183 .gossipsub
184 .publish(gossipsub_topic.clone(), line.as_bytes())
185 {
186 println!("Publish error: {e:?}");
187 }
188 },
189 event = swarm.select_next_some() => {
190 match event {
191 SwarmEvent::NewListenAddr { address, .. } => {
192 println!("Listening on {address:?}");
193 }
194 SwarmEvent::Behaviour(MyBehaviourEvent::Identify(event)) => {
195 println!("identify: {event:?}");
196 }
197 SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message {
198 propagation_source: peer_id,
199 message_id: id,
200 message,
201 })) => {
202 println!(
203 "Got message: {} with id: {} from peer: {:?}",
204 String::from_utf8_lossy(&message.data),
205 id,
206 peer_id
207 )
208 }
209 SwarmEvent::Behaviour(MyBehaviourEvent::Ping(event)) => {
210 match event {
211 ping::Event {
212 peer,
213 result: Result::Ok(rtt),
214 ..
215 } => {
216 println!(
217 "ping: rtt to {} is {} ms",
218 peer.to_base58(),
219 rtt.as_millis()
220 );
221 }
222 ping::Event {
223 peer,
224 result: Result::Err(ping::Failure::Timeout),
225 ..
226 } => {
227 println!("ping: timeout to {}", peer.to_base58());
228 }
229 ping::Event {
230 peer,
231 result: Result::Err(ping::Failure::Unsupported),
232 ..
233 } => {
234 println!("ping: {} does not support ping protocol", peer.to_base58());
235 }
236 ping::Event {
237 peer,
238 result: Result::Err(ping::Failure::Other { error }),
239 ..
240 } => {
241 println!("ping: ping::Failure with {}: {error}", peer.to_base58());
242 }
243 }
244 }
245 _ => {}
246 }
247 }
248 }
249 }
250}