Skip to main content

netget/server/libp2p/
bitswap.rs

1//! Bitswap 1.2.0 (and 1.1.0, 1.0.0): the message, and the two decisions Rust makes about it
2//! before or after the model — which wanted blocks the model's content actually is
3//! ([`answer_wants`]), and which received blocks are the ones that were asked for
4//! ([`accept_blocks`]). In both, a block's identity is its hash, computed here.
5//!
6//! A Bitswap message is one uvarint-length-prefixed protobuf on a stream the sender opened;
7//! answers travel on a stream the answerer opens. Every count and size a peer announces is
8//! bounded before anything is collected for it.
9use super::ipfs::{self, Cid};
10use super::wire::{pb_bytes, pb_fields_bounded, pb_varint, PbValue};
11use anyhow::{bail, ensure, Context, Result};
12use std::collections::HashMap;
13
14pub const BITSWAP_120: &str = "/ipfs/bitswap/1.2.0";
15pub const BITSWAP_110: &str = "/ipfs/bitswap/1.1.0";
16pub const BITSWAP_100: &str = "/ipfs/bitswap/1.0.0";
17pub const BITSWAP_LEGACY: &str = "/ipfs/bitswap";
18/// Every Bitswap protocol id NetGet accepts, newest first (the order it proposes them).
19pub const PROTOCOLS: &[&str] = &[BITSWAP_120, BITSWAP_110, BITSWAP_100, BITSWAP_LEGACY];
20/// A Bitswap message is at most this long (boxo's own `MessageSizeMax`).
21pub const MAX_MESSAGE: usize = 4 * 1024 * 1024;
22/// Wantlist entries one message may carry.
23pub const MAX_WANTLIST_ENTRIES: usize = 1024;
24/// Top-level fields (blocks, presences) one message may carry.
25pub const MAX_MESSAGE_FIELDS: usize = 4096;
26/// What NetGet packs into one outgoing message, leaving room for framing.
27pub const MAX_OUTGOING: usize = MAX_MESSAGE - 64 * 1024;
28/// Blocks the model may supply in one answer.
29pub const MAX_SUPPLIED_BLOCKS: usize = 64;
30
31pub fn is_bitswap(protocol: &str) -> bool {
32    PROTOCOLS.contains(&protocol)
33}
34
35/// Which message format a protocol id speaks.
36#[derive(Clone, Copy, PartialEq, Eq, Debug)]
37pub enum Version {
38    V100,
39    V110,
40    V120,
41}
42
43impl Version {
44    pub fn of(protocol: &str) -> Version {
45        match protocol {
46            BITSWAP_120 => Version::V120,
47            BITSWAP_110 => Version::V110,
48            _ => Version::V100,
49        }
50    }
51}
52
53#[derive(Clone, Copy, PartialEq, Eq, Debug)]
54pub enum WantType {
55    Block,
56    Have,
57}
58
59impl WantType {
60    pub fn name(self) -> &'static str {
61        match self {
62            WantType::Block => "block",
63            WantType::Have => "have",
64        }
65    }
66}
67
68#[derive(Clone, Debug, PartialEq)]
69pub struct Entry {
70    pub cid: Cid,
71    pub priority: i32,
72    pub cancel: bool,
73    pub want_type: WantType,
74    pub send_dont_have: bool,
75}
76
77/// One received block: the CID its prefix and bytes name (the bytes hashed here), and the
78/// bytes. A 1.0.0 block carries no prefix and is read as CIDv0 dag-pb.
79#[derive(Clone, Debug, PartialEq)]
80pub struct Block {
81    pub cid: Cid,
82    pub data: Vec<u8>,
83}
84
85#[derive(Clone, Debug, Default, PartialEq)]
86pub struct Message {
87    pub entries: Vec<Entry>,
88    pub full: bool,
89    pub blocks: Vec<Block>,
90    /// (cid, have): HAVE when true, DONT_HAVE when false.
91    pub presences: Vec<(Cid, bool)>,
92    /// Entries or presences naming a CID NetGet cannot represent (another hash function, a
93    /// CID version it does not know), each with why. A malformed message is refused whole;
94    /// these are refused one by one.
95    pub unreadable: Vec<String>,
96}
97
98/// Decode one Bitswap message. Malformed protobuf, too many entries or fields, and a block
99/// over [`ipfs::MAX_BLOCK`] refuse the whole message.
100pub fn decode(b: &[u8]) -> Result<Message> {
101    ensure!(
102        b.len() <= MAX_MESSAGE,
103        "Bitswap message of {} bytes is over {MAX_MESSAGE}",
104        b.len()
105    );
106    let mut m = Message::default();
107    for (f, v) in pb_fields_bounded(b, MAX_MESSAGE_FIELDS)? {
108        match (f, v) {
109            (1, PbValue::Bytes(w)) => decode_wantlist(w, &mut m)?,
110            (2, PbValue::Bytes(data)) => {
111                ensure!(
112                    data.len() <= ipfs::MAX_BLOCK,
113                    "block over {} bytes",
114                    ipfs::MAX_BLOCK
115                );
116                m.blocks.push(Block {
117                    cid: Cid {
118                        version: 0,
119                        codec: ipfs::DAG_PB,
120                        digest: ipfs::sha256(data),
121                    },
122                    data: data.to_vec(),
123                });
124            }
125            (3, PbValue::Bytes(p)) => {
126                let (mut prefix, mut data) = (None, None);
127                for (bf, bv) in pb_fields_bounded(p, 8)? {
128                    match (bf, bv) {
129                        (1, PbValue::Bytes(x)) => prefix = Some(x),
130                        (2, PbValue::Bytes(x)) => data = Some(x),
131                        _ => bail!("malformed Bitswap block"),
132                    }
133                }
134                let data = data.unwrap_or_default();
135                ensure!(
136                    data.len() <= ipfs::MAX_BLOCK,
137                    "block over {} bytes",
138                    ipfs::MAX_BLOCK
139                );
140                match Cid::from_prefix(prefix.context("a Bitswap block has no prefix")?, data) {
141                    Ok(cid) => m.blocks.push(Block {
142                        cid,
143                        data: data.to_vec(),
144                    }),
145                    Err(e) => m.unreadable.push(format!("block: {e:#}")),
146                }
147            }
148            (4, PbValue::Bytes(p)) => {
149                let (mut cid, mut kind) = (None, 0);
150                for (pf, pv) in pb_fields_bounded(p, 8)? {
151                    match (pf, pv) {
152                        (1, PbValue::Bytes(x)) => cid = Some(x),
153                        (2, PbValue::Varint(k)) => kind = k,
154                        _ => bail!("malformed Bitswap block presence"),
155                    }
156                }
157                match Cid::from_bytes(cid.context("a block presence has no CID")?) {
158                    Ok(c) => m.presences.push((c, kind == 0)),
159                    Err(e) => m.unreadable.push(format!("presence: {e:#}")),
160                }
161            }
162            // pendingBytes
163            (5, PbValue::Varint(_)) => {}
164            _ => bail!("malformed Bitswap message (field {f})"),
165        }
166    }
167    Ok(m)
168}
169
170fn decode_wantlist(w: &[u8], m: &mut Message) -> Result<()> {
171    for (f, v) in pb_fields_bounded(w, MAX_WANTLIST_ENTRIES + 1)? {
172        match (f, v) {
173            (1, PbValue::Bytes(e)) => {
174                ensure!(
175                    m.entries.len() < MAX_WANTLIST_ENTRIES,
176                    "wantlist has more than {MAX_WANTLIST_ENTRIES} entries"
177                );
178                let mut cid = None;
179                let mut entry = Entry {
180                    cid: Cid {
181                        version: 0,
182                        codec: 0,
183                        digest: [0; 32],
184                    },
185                    priority: 0,
186                    cancel: false,
187                    want_type: WantType::Block,
188                    send_dont_have: false,
189                };
190                for (ef, ev) in pb_fields_bounded(e, 16)? {
191                    match (ef, ev) {
192                        (1, PbValue::Bytes(c)) => cid = Some(c),
193                        (2, PbValue::Varint(p)) => entry.priority = p as i32,
194                        (3, PbValue::Varint(c)) => entry.cancel = c != 0,
195                        (4, PbValue::Varint(t)) => {
196                            entry.want_type = if t == 1 {
197                                WantType::Have
198                            } else {
199                                WantType::Block
200                            }
201                        }
202                        (5, PbValue::Varint(s)) => entry.send_dont_have = s != 0,
203                        _ => bail!("malformed wantlist entry"),
204                    }
205                }
206                let cid = cid.context("a wantlist entry has no CID")?;
207                match Cid::from_bytes(cid) {
208                    Ok(c) => {
209                        entry.cid = c;
210                        m.entries.push(entry);
211                    }
212                    Err(e) => m.unreadable.push(format!("want: {e:#}")),
213                }
214            }
215            (2, PbValue::Varint(full)) => m.full = full != 0,
216            _ => bail!("malformed wantlist (field {f})"),
217        }
218    }
219    Ok(())
220}
221
222/// Encode a message in the format `version` speaks: 1.0.0 carries raw blocks (CIDv0 only)
223/// and no presences; 1.1.0 carries prefixed blocks and no presences; 1.2.0 carries both.
224pub fn encode(m: &Message, version: Version) -> Vec<u8> {
225    let mut out = Vec::new();
226    if !m.entries.is_empty() || m.full {
227        let mut w = Vec::new();
228        for e in &m.entries {
229            let mut entry = Vec::new();
230            let cid = if version == Version::V100 {
231                // 1.0.0 names blocks by their multihash, as CIDv0 did.
232                e.cid.multihash()
233            } else {
234                e.cid.to_bytes()
235            };
236            pb_bytes(&mut entry, 1, &cid);
237            pb_varint(&mut entry, 2, e.priority as u32 as u64);
238            if e.cancel {
239                pb_varint(&mut entry, 3, 1);
240            }
241            if version == Version::V120 {
242                if e.want_type == WantType::Have {
243                    pb_varint(&mut entry, 4, 1);
244                }
245                if e.send_dont_have {
246                    pb_varint(&mut entry, 5, 1);
247                }
248            }
249            pb_bytes(&mut w, 1, &entry);
250        }
251        if m.full {
252            pb_varint(&mut w, 2, 1);
253        }
254        pb_bytes(&mut out, 1, &w);
255    }
256    for b in &m.blocks {
257        if version == Version::V100 {
258            pb_bytes(&mut out, 2, &b.data);
259        } else {
260            let mut block = Vec::new();
261            pb_bytes(&mut block, 1, &b.cid.prefix());
262            pb_bytes(&mut block, 2, &b.data);
263            pb_bytes(&mut out, 3, &block);
264        }
265    }
266    if version == Version::V120 {
267        for (cid, have) in &m.presences {
268            let mut p = Vec::new();
269            pb_bytes(&mut p, 1, &cid.to_bytes());
270            pb_varint(&mut p, 2, if *have { 0 } else { 1 });
271            pb_bytes(&mut out, 4, &p);
272        }
273    }
274    out
275}
276
277/// Split an answer into messages no longer than [`MAX_OUTGOING`] each.
278pub fn split(m: Message) -> Vec<Message> {
279    let mut out = Vec::new();
280    let mut cur = Message {
281        presences: m.presences,
282        ..Default::default()
283    };
284    let mut size = cur.presences.len() * 48;
285    for b in m.blocks {
286        let len = b.data.len() + 64;
287        if size + len > MAX_OUTGOING && (!cur.blocks.is_empty() || !cur.presences.is_empty()) {
288            out.push(std::mem::take(&mut cur));
289            size = 0;
290        }
291        size += len;
292        cur.blocks.push(b);
293    }
294    if !cur.blocks.is_empty() || !cur.presences.is_empty() {
295        out.push(cur);
296    }
297    out
298}
299
300// ---------------------------------------------------------------------------------------
301// Serving: what the model supplied, checked against what was wanted.
302
303/// The form a supplied block's content takes on the wire.
304#[derive(Clone, Copy, PartialEq, Eq, Debug)]
305pub enum Format {
306    /// codec raw: the block is the content.
307    Raw,
308    /// codec dag-pb: a single-block UnixFS file holding the content.
309    UnixFsFile,
310}
311
312impl Format {
313    pub fn parse(s: &str) -> Result<Format> {
314        match s {
315            "raw" => Ok(Format::Raw),
316            "unixfs" | "unixfs-file" | "file" => Ok(Format::UnixFsFile),
317            other => bail!("format must be raw or unixfs, not {other:?}"),
318        }
319    }
320}
321
322/// A block the model offered: its bytes as they will travel, and the CID it claimed, if any.
323#[derive(Clone, Debug)]
324pub struct Supplied {
325    pub codec: u64,
326    pub bytes: Vec<u8>,
327    pub claimed: Option<Cid>,
328}
329
330impl Supplied {
331    pub fn new(content: &[u8], format: Format, claimed: Option<Cid>) -> Result<Supplied> {
332        let (codec, bytes) = match format {
333            Format::Raw => (ipfs::RAW, content.to_vec()),
334            Format::UnixFsFile => (ipfs::DAG_PB, ipfs::unixfs_file_block(content)),
335        };
336        ensure!(
337            bytes.len() <= ipfs::MAX_BLOCK,
338            "a block is at most {} bytes; this one would be {}",
339            ipfs::MAX_BLOCK,
340            bytes.len()
341        );
342        Ok(Supplied {
343            codec,
344            bytes,
345            claimed,
346        })
347    }
348    /// The CID these bytes actually have (version 1; a v0 want matches it too).
349    pub fn cid(&self) -> Cid {
350        Cid::v1(self.codec, &self.bytes)
351    }
352}
353
354/// What to send back for one wantlist.
355#[derive(Debug, Default)]
356pub struct Answer {
357    /// Blocks, each under the CID that was wanted (so the version the peer asked for).
358    pub blocks: Vec<Block>,
359    pub dont_have: Vec<Cid>,
360    /// Supplied blocks that were not sent, and why.
361    pub refused: Vec<String>,
362}
363
364/// Match the model's blocks to the wants. A want is satisfied only by a block whose codec
365/// and sha2-256 digest are the wanted CID's — computed from the bytes, never taken from the
366/// model. A supplied block that claims a CID its bytes do not hash to is refused. A want-have
367/// is answered with the block itself (Bitswap allows it, and it spares the model a second
368/// question whose answer has to hash the same). Wants left unsatisfied are answered
369/// DONT_HAVE where the peer asked for that.
370pub fn answer_wants(wants: &[Entry], supplied: &[Supplied]) -> Answer {
371    let mut answer = Answer::default();
372    let mut usable: Vec<(Cid, &Supplied)> = Vec::new();
373    for s in supplied {
374        let actual = s.cid();
375        match s.claimed {
376            Some(claimed) if !claimed.same_block(&actual) => answer.refused.push(format!(
377                "a block claimed as {claimed} hashes to {actual}; not sent"
378            )),
379            _ => usable.push((actual, s)),
380        }
381    }
382    let mut used = vec![false; usable.len()];
383    for want in wants.iter().filter(|w| !w.cancel) {
384        match usable.iter().position(|(c, _)| c.same_block(&want.cid)) {
385            Some(i)
386                if usable[i].1.bytes.len() <= ipfs::MAX_BLOCK
387                    && want.cid.matches(&usable[i].1.bytes) =>
388            {
389                used[i] = true;
390                answer.blocks.push(Block {
391                    cid: want.cid,
392                    data: usable[i].1.bytes.clone(),
393                });
394            }
395            _ if want.send_dont_have => answer.dont_have.push(want.cid),
396            _ => {}
397        }
398    }
399    for (i, (cid, _)) in usable.iter().enumerate() {
400        if !used[i] {
401            answer
402                .refused
403                .push(format!("a block hashing to {cid} was not wanted; not sent"));
404        }
405    }
406    answer
407}
408
409// ---------------------------------------------------------------------------------------
410// Fetching: what arrived, checked against what was asked for.
411
412/// Wants a dialler has outstanding, keyed by the block they name (codec and digest, so a v0
413/// and a v1 want for one block are one want), with the text the model asked by.
414#[derive(Default)]
415pub struct WantSet {
416    wants: HashMap<(u64, [u8; 32]), (Cid, WantType)>,
417}
418
419/// Outstanding wants one connection may hold.
420pub const MAX_OUTSTANDING_WANTS: usize = 1024;
421
422impl WantSet {
423    pub fn add(&mut self, cid: Cid, want: WantType) -> Result<()> {
424        cid.supported()?;
425        let key = (cid.codec, cid.digest);
426        if !self.wants.contains_key(&key) {
427            ensure!(
428                self.wants.len() < MAX_OUTSTANDING_WANTS,
429                "{MAX_OUTSTANDING_WANTS} wants are already outstanding"
430            );
431        }
432        self.wants.insert(key, (cid, want));
433        Ok(())
434    }
435    pub fn remove(&mut self, cid: &Cid) -> Option<(Cid, WantType)> {
436        self.wants.remove(&(cid.codec, cid.digest))
437    }
438    pub fn get(&self, cid: &Cid) -> Option<(Cid, WantType)> {
439        self.wants.get(&(cid.codec, cid.digest)).copied()
440    }
441    pub fn len(&self) -> usize {
442        self.wants.len()
443    }
444    pub fn is_empty(&self) -> bool {
445        self.wants.is_empty()
446    }
447}
448
449/// What a received message is worth reporting.
450#[derive(Debug, Default)]
451pub struct Received {
452    /// Blocks whose bytes hash to an outstanding want, reported under the CID that was asked
453    /// for. Each satisfies (and removes) its want.
454    pub blocks: Vec<Block>,
455    /// Presences for outstanding wants: (the CID asked for, have).
456    pub presences: Vec<(Cid, bool)>,
457    pub refused: Vec<String>,
458}
459
460/// Keep only the blocks that are what was asked for. A block's CID is computed from its own
461/// bytes, so a block whose bytes were altered names a different CID, which nothing wanted;
462/// it is refused, never reported.
463pub fn accept_blocks(m: &Message, wants: &mut WantSet) -> Received {
464    let mut r = Received::default();
465    for b in &m.blocks {
466        match wants.get(&b.cid) {
467            Some((asked, _)) if b.cid.supported().is_ok() && asked.matches(&b.data) => {
468                wants.remove(&asked);
469                r.blocks.push(Block {
470                    cid: asked,
471                    data: b.data.clone(),
472                });
473            }
474            _ => r.refused.push(format!(
475                "a {}-byte block hashing to {} matches nothing that was wanted; refused",
476                b.data.len(),
477                b.cid
478            )),
479        }
480    }
481    for (cid, have) in &m.presences {
482        if let Some((asked, _)) = wants.get(cid) {
483            if !*have {
484                wants.remove(&asked);
485            }
486            r.presences.push((asked, *have));
487        }
488    }
489    r.refused.extend(m.unreadable.iter().cloned());
490    r
491}