Skip to main content

netget/server/libp2p/
yamux.rs

1//! yamux (the hashicorp spec, `/yamux/1.0.0`) over a Noise channel. One task reads frames
2//! and routes them to streams; one task owns the writer and every stream's send window, so
3//! data the peer has no window for waits there rather than being sent past its limit.
4use super::noise::{NoiseConn, NoiseReader, NoiseWriter};
5use super::wire::Io;
6use anyhow::{bail, ensure, Result};
7use std::collections::{HashMap, VecDeque};
8use std::future::Future;
9use std::sync::atomic::{AtomicI64, AtomicU32, Ordering};
10use std::sync::{Arc, Mutex};
11use tokio::sync::mpsc;
12
13/// Each side's starting receive window per stream (the spec's 256 KiB).
14pub const INITIAL_WINDOW: u32 = 256 * 1024;
15/// Streams the peer may have open on one connection; past it, new ones are reset.
16pub const MAX_STREAMS: usize = 256;
17/// One data frame carries at most this much.
18pub const MAX_FRAME_DATA: usize = 64 * 1024;
19
20const T_DATA: u8 = 0;
21const T_WINDOW: u8 = 1;
22const T_PING: u8 = 2;
23const T_GO_AWAY: u8 = 3;
24const F_SYN: u16 = 1;
25const F_ACK: u16 = 2;
26const F_FIN: u16 = 4;
27const F_RST: u16 = 8;
28
29fn header(kind: u8, flags: u16, id: u32, len: u32) -> [u8; 12] {
30    let mut h = [0u8; 12];
31    h[1] = kind;
32    h[2..4].copy_from_slice(&flags.to_be_bytes());
33    h[4..8].copy_from_slice(&id.to_be_bytes());
34    h[8..12].copy_from_slice(&len.to_be_bytes());
35    h
36}
37
38enum Cmd {
39    Open(u32),
40    Ack(u32),
41    Data(u32, Vec<u8>),
42    Fin(u32),
43    Rst(u32),
44    Grant(u32, u32),
45    PeerWindow(u32, u32),
46    Pong(u32),
47    GoAway(u32),
48}
49
50struct Inbound {
51    tx: mpsc::UnboundedSender<Vec<u8>>,
52    /// Bytes the peer may still send before it must wait for a window update.
53    window: Arc<AtomicI64>,
54}
55
56type Table = Arc<Mutex<HashMap<u32, Inbound>>>;
57
58/// One multiplexed stream. Reading pulls the peer's data. Our half stays open until
59/// [`StreamWriter::close`], so a reply can follow the peer's own half-close.
60pub struct Stream {
61    pub id: u32,
62    rx: mpsc::UnboundedReceiver<Vec<u8>>,
63    buf: Vec<u8>,
64    pos: usize,
65    consumed: u32,
66    window: Arc<AtomicI64>,
67    writer: StreamWriter,
68}
69
70/// The write side of a stream, which can be held apart from its reader.
71#[derive(Clone)]
72pub struct StreamWriter {
73    pub id: u32,
74    cmd: mpsc::UnboundedSender<Cmd>,
75}
76
77impl StreamWriter {
78    pub fn send(&self, data: Vec<u8>) -> Result<()> {
79        for chunk in data.chunks(MAX_FRAME_DATA) {
80            self.cmd
81                .send(Cmd::Data(self.id, chunk.to_vec()))
82                .map_err(|_| anyhow::anyhow!("connection closed"))?;
83        }
84        Ok(())
85    }
86    /// Half-close: the peer reads EOF after everything sent so far.
87    pub fn close(&self) {
88        let _ = self.cmd.send(Cmd::Fin(self.id));
89    }
90    pub fn reset(&self) {
91        let _ = self.cmd.send(Cmd::Rst(self.id));
92    }
93}
94
95impl Stream {
96    pub fn writer(&self) -> StreamWriter {
97        self.writer.clone()
98    }
99    /// The next chunk of data, or None once the peer has closed its half.
100    pub async fn next_chunk(&mut self) -> Option<Vec<u8>> {
101        if self.pos < self.buf.len() {
102            let rest = self.buf[self.pos..].to_vec();
103            self.pos = self.buf.len();
104            return Some(rest);
105        }
106        let chunk = self.rx.recv().await?;
107        self.consumed(chunk.len());
108        Some(chunk)
109    }
110    fn consumed(&mut self, n: usize) {
111        self.consumed += n as u32;
112        if self.consumed >= INITIAL_WINDOW / 2 {
113            let delta = self.consumed;
114            self.consumed = 0;
115            self.window.fetch_add(i64::from(delta), Ordering::SeqCst);
116            let _ = self.writer.cmd.send(Cmd::Grant(self.id, delta));
117        }
118    }
119}
120
121impl Io for Stream {
122    async fn read_exact(&mut self, out: &mut [u8]) -> Result<()> {
123        let mut done = 0;
124        while done < out.len() {
125            if self.pos == self.buf.len() {
126                match self.rx.recv().await {
127                    Some(chunk) => {
128                        self.consumed(chunk.len());
129                        self.buf = chunk;
130                        self.pos = 0;
131                    }
132                    None => bail!("stream closed by the peer"),
133                }
134                continue;
135            }
136            let n = (out.len() - done).min(self.buf.len() - self.pos);
137            out[done..done + n].copy_from_slice(&self.buf[self.pos..self.pos + n]);
138            self.pos += n;
139            done += n;
140        }
141        Ok(())
142    }
143    fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> impl Future<Output = Result<()>> + Send + 'a {
144        let r = self.writer.send(buf.to_vec());
145        async move { r }
146    }
147}
148
149/// Opens outbound streams; cheap to clone.
150#[derive(Clone)]
151pub struct Opener {
152    cmd: mpsc::UnboundedSender<Cmd>,
153    next_id: Arc<AtomicU32>,
154    table: Table,
155}
156
157impl Opener {
158    pub fn open(&self) -> Result<Stream> {
159        let id = self.next_id.fetch_add(2, Ordering::SeqCst);
160        let (tx, rx) = mpsc::unbounded_channel();
161        let window = Arc::new(AtomicI64::new(i64::from(INITIAL_WINDOW)));
162        {
163            let mut t = self.table.lock().unwrap_or_else(|e| e.into_inner());
164            ensure!(t.len() < MAX_STREAMS, "too many open streams");
165            t.insert(
166                id,
167                Inbound {
168                    tx,
169                    window: window.clone(),
170                },
171            );
172        }
173        self.cmd
174            .send(Cmd::Open(id))
175            .map_err(|_| anyhow::anyhow!("connection closed"))?;
176        Ok(Stream {
177            id,
178            rx,
179            buf: Vec::new(),
180            pos: 0,
181            consumed: 0,
182            window,
183            writer: StreamWriter {
184                id,
185                cmd: self.cmd.clone(),
186            },
187        })
188    }
189    /// Tell the peer we are leaving, then close the connection.
190    pub fn go_away(&self) {
191        let _ = self.cmd.send(Cmd::GoAway(0));
192    }
193}
194
195/// A yamux session: the opener, the peer's new streams, and the two futures that run it
196/// (which the caller spawns and registers).
197pub struct Session {
198    pub opener: Opener,
199    pub incoming: mpsc::Receiver<Stream>,
200    pub reader: std::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>>,
201    pub writer: std::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>>,
202}
203
204/// Start yamux on a secured connection; `client` is the side that dialled it (odd stream ids).
205pub fn session(conn: NoiseConn, client: bool) -> Session {
206    let (cmd, cmd_rx) = mpsc::unbounded_channel();
207    let table: Table = Arc::new(Mutex::new(HashMap::new()));
208    let (incoming_tx, incoming) = mpsc::channel(MAX_STREAMS);
209    let opener = Opener {
210        cmd: cmd.clone(),
211        next_id: Arc::new(AtomicU32::new(if client { 1 } else { 2 })),
212        table: table.clone(),
213    };
214    Session {
215        opener,
216        incoming,
217        reader: Box::pin(read_loop(conn.reader, cmd, table, incoming_tx, client)),
218        writer: Box::pin(write_loop(conn.writer, cmd_rx)),
219    }
220}
221
222async fn read_loop(
223    mut r: NoiseReader,
224    cmd: mpsc::UnboundedSender<Cmd>,
225    table: Table,
226    incoming: mpsc::Sender<Stream>,
227    client: bool,
228) -> Result<()> {
229    let result = async {
230        loop {
231            let mut h = [0u8; 12];
232            r.read_exact(&mut h).await?;
233            ensure!(h[0] == 0, "unsupported yamux version {}", h[0]);
234            let kind = h[1];
235            let flags = u16::from_be_bytes([h[2], h[3]]);
236            let id = u32::from_be_bytes([h[4], h[5], h[6], h[7]]);
237            let len = u32::from_be_bytes([h[8], h[9], h[10], h[11]]);
238            match kind {
239                T_PING => {
240                    if flags & F_SYN != 0 {
241                        let _ = cmd.send(Cmd::Pong(len));
242                    }
243                    continue;
244                }
245                T_GO_AWAY => return Ok(()),
246                T_DATA | T_WINDOW => {}
247                k => bail!("unknown yamux frame type {k}"),
248            }
249            if flags & F_SYN != 0 {
250                // A stream the peer opens has the peer's parity: odd from a dialler.
251                let theirs = (id % 2 == 1) != client;
252                let mut t = table.lock().unwrap_or_else(|e| e.into_inner());
253                if !theirs || id == 0 || t.contains_key(&id) {
254                    drop(t);
255                    bail!("invalid stream id {id} in SYN");
256                }
257                if t.len() >= MAX_STREAMS {
258                    drop(t);
259                    let _ = cmd.send(Cmd::Rst(id));
260                } else {
261                    let (tx, rx) = mpsc::unbounded_channel();
262                    let window = Arc::new(AtomicI64::new(i64::from(INITIAL_WINDOW)));
263                    t.insert(
264                        id,
265                        Inbound {
266                            tx,
267                            window: window.clone(),
268                        },
269                    );
270                    drop(t);
271                    let _ = cmd.send(Cmd::Ack(id));
272                    let stream = Stream {
273                        id,
274                        rx,
275                        buf: Vec::new(),
276                        pos: 0,
277                        consumed: 0,
278                        window,
279                        writer: StreamWriter {
280                            id,
281                            cmd: cmd.clone(),
282                        },
283                    };
284                    if incoming.try_send(stream).is_err() {
285                        table.lock().unwrap_or_else(|e| e.into_inner()).remove(&id);
286                    }
287                }
288            }
289            if kind == T_WINDOW {
290                if len > 0 {
291                    let _ = cmd.send(Cmd::PeerWindow(id, len));
292                }
293            } else if len > 0 {
294                let mut data = vec![0u8; len as usize];
295                // The peer may not send more than the window it was given.
296                let window = table
297                    .lock()
298                    .unwrap_or_else(|e| e.into_inner())
299                    .get(&id)
300                    .map(|s| s.window.clone());
301                if let Some(w) = &window {
302                    ensure!(
303                        w.fetch_sub(i64::from(len), Ordering::SeqCst) >= i64::from(len),
304                        "peer sent {len} bytes past stream {id}'s receive window"
305                    );
306                } else {
307                    ensure!(
308                        len as usize <= INITIAL_WINDOW as usize,
309                        "data frame larger than any window"
310                    );
311                }
312                r.read_exact(&mut data).await?;
313                if let Some(s) = table.lock().unwrap_or_else(|e| e.into_inner()).get(&id) {
314                    let _ = s.tx.send(data);
315                }
316            }
317            if flags & (F_FIN | F_RST) != 0 {
318                // Dropping the sender is the reader's EOF.
319                table.lock().unwrap_or_else(|e| e.into_inner()).remove(&id);
320            }
321        }
322    }
323    .await;
324    table.lock().unwrap_or_else(|e| e.into_inner()).clear();
325    let _ = cmd.send(Cmd::GoAway(0));
326    result
327}
328
329#[derive(Default)]
330struct Outbound {
331    window: u32,
332    pending: VecDeque<Vec<u8>>,
333    fin: bool,
334}
335
336async fn write_loop(mut w: NoiseWriter, mut cmds: mpsc::UnboundedReceiver<Cmd>) -> Result<()> {
337    let mut streams: HashMap<u32, Outbound> = HashMap::new();
338    let fresh = || Outbound {
339        window: INITIAL_WINDOW,
340        ..Default::default()
341    };
342    while let Some(c) = cmds.recv().await {
343        let flush_id = match c {
344            Cmd::Open(id) => {
345                streams.insert(id, fresh());
346                w.write_all(&header(T_WINDOW, F_SYN, id, 0)).await?;
347                None
348            }
349            Cmd::Ack(id) => {
350                streams.insert(id, fresh());
351                w.write_all(&header(T_WINDOW, F_ACK, id, 0)).await?;
352                None
353            }
354            Cmd::Data(id, data) => {
355                if let Some(s) = streams.get_mut(&id) {
356                    if !s.fin {
357                        s.pending.push_back(data);
358                    }
359                }
360                Some(id)
361            }
362            Cmd::Fin(id) => {
363                if let Some(s) = streams.get_mut(&id) {
364                    s.fin = true;
365                }
366                Some(id)
367            }
368            Cmd::Rst(id) => {
369                streams.remove(&id);
370                w.write_all(&header(T_WINDOW, F_RST, id, 0)).await?;
371                None
372            }
373            Cmd::Grant(id, delta) => {
374                w.write_all(&header(T_WINDOW, 0, id, delta)).await?;
375                None
376            }
377            Cmd::PeerWindow(id, delta) => {
378                if let Some(s) = streams.get_mut(&id) {
379                    s.window = s.window.saturating_add(delta);
380                }
381                Some(id)
382            }
383            Cmd::Pong(v) => {
384                w.write_all(&header(T_PING, F_ACK, 0, v)).await?;
385                None
386            }
387            Cmd::GoAway(code) => {
388                let _ = w.write_all(&header(T_GO_AWAY, 0, 0, code)).await;
389                w.shutdown().await;
390                return Ok(());
391            }
392        };
393        let Some(id) = flush_id else { continue };
394        let Some(s) = streams.get_mut(&id) else {
395            continue;
396        };
397        let mut out = Vec::new();
398        while s.window > 0 {
399            let Some(front) = s.pending.front_mut() else {
400                break;
401            };
402            let n = front.len().min(s.window as usize);
403            out.extend_from_slice(&header(T_DATA, 0, id, n as u32));
404            out.extend(front.drain(..n));
405            s.window -= n as u32;
406            if front.is_empty() {
407                s.pending.pop_front();
408            }
409        }
410        if s.fin && s.pending.is_empty() {
411            out.extend_from_slice(&header(T_WINDOW, F_FIN, id, 0));
412            streams.remove(&id);
413        }
414        if !out.is_empty() {
415            w.write_all(&out).await?;
416        }
417    }
418    w.shutdown().await;
419    Ok(())
420}