use super::noise::{self, Identity, NoiseConn};
use super::wire::{self, Io};
use super::yamux::{Opener, Stream, StreamWriter};
use super::{bitswap, kad};
use anyhow::{ensure, Context, Result};
use std::collections::{BTreeMap, HashMap};
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::sync::mpsc;
pub const DEFAULT_PROTOCOLS: &[&str] = &["/netget/chat/1.0.0"];
pub const MAX_MESSAGE: usize = 1024 * 1024;
pub const NEGOTIATION_TIMEOUT: Duration = Duration::from_secs(10);
pub const AGENT: &str = concat!("netget/", env!("CARGO_PKG_VERSION"));
pub const PROTOCOL_VERSION: &str = "ipfs/0.1.0";
pub const MAX_REMEMBERED_STREAMS: usize = 1024;
pub const PING_SIZE: usize = 32;
pub struct Config {
pub identity: Identity,
pub protocols: Vec<String>,
pub listen_addrs: Vec<Vec<u8>>,
pub bitswap: bool,
pub kad: bool,
pub directory: Directory,
}
pub type Directory = Arc<Mutex<BTreeMap<String, Vec<Vec<u8>>>>>;
impl Config {
pub fn served_protocols(&self) -> Vec<String> {
let mut out: Vec<String> = [wire::IDENTIFY, wire::IDENTIFY_PUSH, wire::PING]
.iter()
.map(|s| s.to_string())
.collect();
if self.bitswap {
out.extend(bitswap::PROTOCOLS.iter().map(|s| s.to_string()));
}
if self.kad {
out.extend(kad::PROTOCOLS.iter().map(|s| s.to_string()));
}
out.extend(self.protocols.iter().cloned());
out
}
pub fn max_message(&self, protocol: &str) -> usize {
if bitswap::is_bitswap(protocol) {
bitswap::MAX_MESSAGE
} else if kad::is_kad(protocol) {
kad::MAX_MESSAGE
} else {
MAX_MESSAGE
}
}
}
pub enum Note {
Message {
stream_id: u32,
protocol: String,
data: Vec<u8>,
},
Closed {
stream_id: u32,
protocol: String,
error: Option<String>,
},
Ended(Option<String>),
}
pub type Task = Pin<Box<dyn Future<Output = ()> + Send>>;
pub type Spawn = Arc<dyn Fn(Task) -> Task + Send + Sync>;
pub struct Peer {
pub opener: Opener,
pub remote_peer: String,
pub remote_addr: SocketAddr,
pub streams: Mutex<HashMap<u32, (String, StreamWriter)>>,
pub notes: mpsc::Sender<Note>,
}
impl Peer {
pub fn writer(&self, stream_id: u32) -> Option<(String, StreamWriter)> {
self.streams
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&stream_id)
.cloned()
}
pub fn remember(&self, stream_id: u32, protocol: String, writer: StreamWriter) {
let mut t = self.streams.lock().unwrap_or_else(|e| e.into_inner());
while t.len() >= MAX_REMEMBERED_STREAMS {
let oldest = *t.keys().min().expect("non-empty");
t.remove(&oldest);
}
t.insert(stream_id, (protocol, writer));
}
pub fn forget(&self, stream_id: u32) {
self.streams
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&stream_id);
}
}
pub async fn upgrade_inbound(mut tcp: TcpStream, cfg: &Config) -> Result<NoiseConn> {
tokio::time::timeout(NEGOTIATION_TIMEOUT, async {
wire::ms_listen(&mut tcp, |p| p == wire::NOISE).await?;
let mut conn = noise::handshake(tcp, &cfg.identity, false, None).await?;
wire::ms_listen(&mut conn, |p| p == wire::YAMUX).await?;
Ok(conn)
})
.await
.context("libp2p upgrade timed out")?
}
pub async fn upgrade_outbound(
mut tcp: TcpStream,
cfg: &Config,
expected: Option<&[u8]>,
) -> Result<NoiseConn> {
tokio::time::timeout(NEGOTIATION_TIMEOUT, async {
ensure!(
wire::ms_dial(&mut tcp, wire::NOISE).await?,
"the peer does not speak /noise"
);
let mut conn = noise::handshake(tcp, &cfg.identity, true, expected).await?;
ensure!(
wire::ms_dial(&mut conn, wire::YAMUX).await?,
"the peer does not speak /yamux/1.0.0"
);
Ok(conn)
})
.await
.context("libp2p upgrade timed out")?
}
pub async fn accept_streams(
mut incoming: mpsc::Receiver<Stream>,
cfg: Arc<Config>,
peer: Arc<Peer>,
spawn: Spawn,
) {
while let Some(stream) = incoming.recv().await {
spawn(Box::pin(inbound(stream, cfg.clone(), peer.clone()))).await;
}
}
async fn inbound(mut stream: Stream, cfg: Arc<Config>, peer: Arc<Peer>) {
let served = cfg.served_protocols();
let supported = |p: &str| served.iter().any(|x| x == p);
let writer = stream.writer();
let protocol =
match tokio::time::timeout(NEGOTIATION_TIMEOUT, wire::ms_listen(&mut stream, supported))
.await
{
Ok(Ok(p)) => p,
_ => {
writer.reset();
return;
}
};
match protocol.as_str() {
wire::IDENTIFY => {
let msg = wire::encode_identify(&wire::Identify {
protocol_version: PROTOCOL_VERSION.into(),
agent_version: AGENT.into(),
public_key: cfg.identity.public_key_proto.clone(),
listen_addrs: cfg.listen_addrs.clone(),
observed_addr: Some(wire::multiaddr_bytes(&peer.remote_addr)),
protocols: cfg.served_protocols(),
});
let _ = writer.send(wire::length_prefixed(&msg));
writer.close();
}
wire::IDENTIFY_PUSH => {
let _ = wire::read_length_prefixed(&mut stream, wire::MAX_IDENTIFY_BYTES).await;
writer.close();
}
wire::PING => {
let mut b = [0u8; PING_SIZE];
while stream.read_exact(&mut b).await.is_ok() {
if writer.send(b.to_vec()).is_err() {
break;
}
}
writer.close();
}
_ => {
peer.remember(stream.id, protocol.clone(), writer);
let max = cfg.max_message(&protocol);
read_messages(stream, protocol, peer.notes.clone(), max).await;
}
}
}
pub async fn read_messages(
mut stream: Stream,
protocol: String,
notes: mpsc::Sender<Note>,
max: usize,
) {
let error = loop {
match wire::read_length_prefixed(&mut stream, max).await {
Ok(data) => {
let note = Note::Message {
stream_id: stream.id,
protocol: protocol.clone(),
data,
};
if notes.send(note).await.is_err() {
return;
}
}
Err(e) => {
let text = e.to_string();
break (!text.contains("closed by the peer")).then_some(text);
}
}
};
if error.is_some() {
stream.writer().reset();
}
let _ = notes
.send(Note::Closed {
stream_id: stream.id,
protocol,
error,
})
.await;
}
pub struct Remote {
pub agent_version: String,
pub protocol_version: String,
pub protocols: Vec<String>,
pub listen_addrs: Vec<String>,
pub observed_addr: Option<String>,
}
pub async fn identify(peer: &Peer) -> Result<Remote> {
tokio::time::timeout(NEGOTIATION_TIMEOUT, async {
let mut s = peer.opener.open()?;
ensure!(
wire::ms_dial(&mut s, wire::IDENTIFY).await?,
"the peer does not answer identify"
);
let msg = wire::read_length_prefixed(&mut s, wire::MAX_IDENTIFY_BYTES).await?;
s.writer().close();
let id = wire::decode_identify(&msg)?;
Ok(Remote {
agent_version: id.agent_version,
protocol_version: id.protocol_version,
protocols: id.protocols,
listen_addrs: id
.listen_addrs
.iter()
.filter_map(|a| wire::multiaddr_text(a).ok())
.collect(),
observed_addr: id.observed_addr.and_then(|a| wire::multiaddr_text(&a).ok()),
})
})
.await
.context("identify timed out")?
}
pub async fn ping(peer: &Peer) -> Result<Duration> {
tokio::time::timeout(NEGOTIATION_TIMEOUT, async {
let mut s = peer.opener.open()?;
ensure!(
wire::ms_dial(&mut s, wire::PING).await?,
"the peer does not answer ping"
);
let payload: [u8; PING_SIZE] = rand::random();
let started = crate::utils::clock::Instant::now();
s.write_all(&payload).await?;
let mut back = [0u8; PING_SIZE];
s.read_exact(&mut back).await?;
let rtt = started.elapsed();
s.writer().close();
ensure!(back == payload, "the ping came back different");
Ok(rtt)
})
.await
.context("ping timed out")?
}
pub async fn open_app_stream(peer: &Arc<Peer>, protocol: &str) -> Result<Option<(u32, Task)>> {
let mut s = peer.opener.open()?;
let accepted = tokio::time::timeout(NEGOTIATION_TIMEOUT, wire::ms_dial(&mut s, protocol))
.await
.context("protocol negotiation timed out")??;
if !accepted {
s.writer().close();
return Ok(None);
}
let id = s.id;
peer.remember(id, protocol.to_string(), s.writer());
let reader: Task = Box::pin(read_messages(
s,
protocol.to_string(),
peer.notes.clone(),
MAX_MESSAGE,
));
Ok(Some((id, reader)))
}
pub async fn open_negotiated(peer: &Peer, protocols: &[&str]) -> Result<Option<(Stream, String)>> {
let mut s = peer.opener.open()?;
let accepted = tokio::time::timeout(NEGOTIATION_TIMEOUT, wire::ms_dial_any(&mut s, protocols))
.await
.context("protocol negotiation timed out")??;
match accepted {
Some(p) => Ok(Some((s, p))),
None => {
s.writer().close();
Ok(None)
}
}
}
pub fn send(peer: &Peer, stream_id: u32, data: &[u8]) -> Result<()> {
ensure!(
data.len() <= MAX_MESSAGE,
"message longer than {MAX_MESSAGE} bytes"
);
let (_, w) = peer
.writer(stream_id)
.with_context(|| format!("no open stream {stream_id}"))?;
w.send(wire::length_prefixed(data))
}