Skip to main content

clawft_kernel/
mesh_artifact_wire.rs

1//! Wire types for the swarm-ready artifact piece protocol
2//! (ADR-099 section 6, mesh-placement-11).
3//!
4//! An artifact is identified by the root hash over its ordered piece list
5//! ([`ArtifactDescriptor::id`]), not by the node it came from. Pieces are
6//! fixed-size (64 MiB placeholder) and pinned by BLAKE3. Pieces travel in
7//! blocks, because a piece is larger than one frame.
8//!
9//! Frame 0x0B ([`FrameType::ArtifactRequest`]) carries control messages
10//! (`meta_request`, `announce`, `request`); frame 0x0C
11//! ([`FrameType::ArtifactResponse`]) carries data (`meta`, `piece`,
12//! `no_piece`, `reject`). Every frame is capped at [`MAX_ARTIFACT_FRAME`]
13//! payload bytes; there is no ceiling on the artifact's total size.
14//!
15//! The encoding is a small, strict binary layout (big-endian integers,
16//! 32-byte hashes, no trailing bytes) so piece data is never re-encoded.
17
18use crate::mesh_artifact_types::bad;
19pub use crate::mesh_artifact_types::{
20    ArtifactDescriptor, ArtifactId, ArtifactKey, Bitfield, DEFAULT_BLOCK_SIZE, DEFAULT_PIECE_SIZE,
21    MAX_ARTIFACT_FRAME, MAX_PIECE_SIZE, MAX_REASON_BYTES, MAX_REQUEST_PIECES, MIN_PIECE_SIZE,
22    WireError,
23};
24use crate::mesh_framing::{FrameType, MeshFrame};
25
26const TAG_META_REQUEST: u8 = 0x01;
27const TAG_ANNOUNCE: u8 = 0x02;
28const TAG_REQUEST: u8 = 0x03;
29const TAG_META: u8 = 0x11;
30const TAG_PIECE: u8 = 0x12;
31const TAG_NO_PIECE: u8 = 0x13;
32const TAG_REJECT: u8 = 0x14;
33
34/// One artifact-protocol message.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum ArtifactMsg {
37    /// 0x0B: ask a peer for an artifact's descriptor and its `have`.
38    MetaRequest {
39        /// What is wanted.
40        key: ArtifactKey,
41    },
42    /// 0x0B: the pieces the sender holds, verified.
43    Announce {
44        /// Artifact.
45        id: ArtifactId,
46        /// Held pieces.
47        have: Bitfield,
48    },
49    /// 0x0B: ask for pieces by index.
50    Request {
51        /// Artifact.
52        id: ArtifactId,
53        /// Piece indexes.
54        pieces: Vec<u32>,
55    },
56    /// 0x0C: the descriptor.
57    Meta {
58        /// Piece list.
59        descriptor: ArtifactDescriptor,
60    },
61    /// 0x0C: one block of piece data.
62    Piece {
63        /// Artifact.
64        id: ArtifactId,
65        /// Piece index.
66        index: u32,
67        /// Byte offset of this block within the piece.
68        offset: u64,
69        /// Block bytes.
70        data: Vec<u8>,
71    },
72    /// 0x0C: the sender cannot supply this piece.
73    NoPiece {
74        /// Artifact.
75        id: ArtifactId,
76        /// Piece index.
77        index: u32,
78    },
79    /// 0x0C: the request is refused (unknown, or not servable).
80    Reject {
81        /// What was asked for.
82        key: ArtifactKey,
83        /// Short reason.
84        reason: String,
85    },
86}
87
88impl ArtifactMsg {
89    /// Frame type this message travels in.
90    pub fn frame_type(&self) -> FrameType {
91        match self {
92            Self::MetaRequest { .. } | Self::Announce { .. } | Self::Request { .. } => {
93                FrameType::ArtifactRequest
94            }
95            _ => FrameType::ArtifactResponse,
96        }
97    }
98
99    /// Encode to a [`MeshFrame`], refusing payloads over the cap.
100    pub fn to_frame(&self) -> Result<MeshFrame, WireError> {
101        let mut w = Vec::new();
102        match self {
103            Self::MetaRequest { key } => {
104                w.push(TAG_META_REQUEST);
105                put_key(&mut w, key);
106            }
107            Self::Announce { id, have } => {
108                w.push(TAG_ANNOUNCE);
109                w.extend_from_slice(&id.0);
110                w.extend_from_slice(&have.len.to_be_bytes());
111                w.extend_from_slice(&have.bits);
112            }
113            Self::Request { id, pieces } => {
114                if pieces.len() > MAX_REQUEST_PIECES {
115                    return Err(bad("too many pieces in one request"));
116                }
117                w.push(TAG_REQUEST);
118                w.extend_from_slice(&id.0);
119                w.extend_from_slice(&(pieces.len() as u32).to_be_bytes());
120                for p in pieces {
121                    w.extend_from_slice(&p.to_be_bytes());
122                }
123            }
124            Self::Meta { descriptor: d } => {
125                w.push(TAG_META);
126                w.extend_from_slice(&d.piece_size.to_be_bytes());
127                w.extend_from_slice(&d.total_size.to_be_bytes());
128                w.extend_from_slice(&d.content_hash);
129                w.extend_from_slice(&(d.pieces.len() as u32).to_be_bytes());
130                for p in &d.pieces {
131                    w.extend_from_slice(p);
132                }
133            }
134            Self::Piece {
135                id,
136                index,
137                offset,
138                data,
139            } => {
140                w.reserve(1 + 32 + 4 + 8 + 4 + data.len());
141                w.push(TAG_PIECE);
142                w.extend_from_slice(&id.0);
143                w.extend_from_slice(&index.to_be_bytes());
144                w.extend_from_slice(&offset.to_be_bytes());
145                w.extend_from_slice(&(data.len() as u32).to_be_bytes());
146                w.extend_from_slice(data);
147            }
148            Self::NoPiece { id, index } => {
149                w.push(TAG_NO_PIECE);
150                w.extend_from_slice(&id.0);
151                w.extend_from_slice(&index.to_be_bytes());
152            }
153            Self::Reject { key, reason } => {
154                w.push(TAG_REJECT);
155                put_key(&mut w, key);
156                let r = truncate_utf8(reason, MAX_REASON_BYTES);
157                w.extend_from_slice(&(r.len() as u16).to_be_bytes());
158                w.extend_from_slice(r.as_bytes());
159            }
160        }
161        if w.len() > MAX_ARTIFACT_FRAME {
162            return Err(WireError::FrameTooLarge {
163                size: w.len(),
164                max: MAX_ARTIFACT_FRAME,
165            });
166        }
167        Ok(MeshFrame {
168            frame_type: self.frame_type(),
169            payload: w,
170        })
171    }
172
173    /// Encode to stream bytes: `[4-byte len][type][payload]`.
174    pub fn to_wire(&self) -> Result<Vec<u8>, WireError> {
175        self.to_frame()?
176            .encode()
177            .map_err(|e| bad(format!("frame encode: {e}")))
178    }
179
180    /// Decode stream bytes produced by [`Self::to_wire`]. The declared
181    /// length is checked against the cap before anything is parsed.
182    pub fn from_wire(raw: &[u8]) -> Result<Self, WireError> {
183        if raw.len() < 5 {
184            return Err(bad("short frame"));
185        }
186        let declared = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
187        let payload_len = declared.saturating_sub(1).max(raw.len().saturating_sub(5));
188        if payload_len > MAX_ARTIFACT_FRAME {
189            return Err(WireError::FrameTooLarge {
190                size: payload_len,
191                max: MAX_ARTIFACT_FRAME,
192            });
193        }
194        if declared != raw.len() - 4 {
195            return Err(bad("length prefix does not match frame"));
196        }
197        let frame = MeshFrame::decode(&raw[4..]).map_err(|e| bad(e.to_string()))?;
198        Self::from_frame(&frame)
199    }
200
201    /// Decode a [`MeshFrame`] of type 0x0B or 0x0C.
202    pub fn from_frame(frame: &MeshFrame) -> Result<Self, WireError> {
203        if frame.payload.len() > MAX_ARTIFACT_FRAME {
204            return Err(WireError::FrameTooLarge {
205                size: frame.payload.len(),
206                max: MAX_ARTIFACT_FRAME,
207            });
208        }
209        let mut r = Reader(&frame.payload);
210        let tag = r.u8()?;
211        let msg = match tag {
212            TAG_META_REQUEST => Self::MetaRequest { key: r.key()? },
213            TAG_ANNOUNCE => {
214                let id = ArtifactId(r.hash()?);
215                let len = r.u32()?;
216                let bits = r.take((len as usize).div_ceil(8))?.to_vec();
217                Self::Announce {
218                    id,
219                    have: Bitfield { len, bits },
220                }
221            }
222            TAG_REQUEST => {
223                let id = ArtifactId(r.hash()?);
224                let n = r.u32()? as usize;
225                if n > MAX_REQUEST_PIECES {
226                    return Err(bad("too many pieces in one request"));
227                }
228                let pieces = (0..n).map(|_| r.u32()).collect::<Result<_, _>>()?;
229                Self::Request { id, pieces }
230            }
231            TAG_META => {
232                let piece_size = r.u64()?;
233                let total_size = r.u64()?;
234                let content_hash = r.hash()?;
235                let n = r.u32()? as usize;
236                if n.saturating_mul(32) > r.0.len() {
237                    return Err(bad("piece list longer than frame"));
238                }
239                let pieces = (0..n).map(|_| r.hash()).collect::<Result<_, _>>()?;
240                let descriptor = ArtifactDescriptor {
241                    piece_size,
242                    total_size,
243                    content_hash,
244                    pieces,
245                };
246                descriptor.validate()?;
247                Self::Meta { descriptor }
248            }
249            TAG_PIECE => {
250                let id = ArtifactId(r.hash()?);
251                let index = r.u32()?;
252                let offset = r.u64()?;
253                let n = r.u32()? as usize;
254                let data = r.take(n)?.to_vec();
255                Self::Piece {
256                    id,
257                    index,
258                    offset,
259                    data,
260                }
261            }
262            TAG_NO_PIECE => Self::NoPiece {
263                id: ArtifactId(r.hash()?),
264                index: r.u32()?,
265            },
266            TAG_REJECT => {
267                let key = r.key()?;
268                let n = r.u16()? as usize;
269                if n > MAX_REASON_BYTES {
270                    return Err(bad("reject reason too long"));
271                }
272                let reason = String::from_utf8(r.take(n)?.to_vec())
273                    .map_err(|_| bad("reject reason not utf-8"))?;
274                Self::Reject { key, reason }
275            }
276            other => return Err(bad(format!("unknown artifact message tag 0x{other:02x}"))),
277        };
278        if !r.0.is_empty() {
279            return Err(bad("trailing bytes after artifact message"));
280        }
281        if msg.frame_type() != frame.frame_type {
282            return Err(bad("artifact message in the wrong frame type"));
283        }
284        Ok(msg)
285    }
286}
287
288fn put_key(w: &mut Vec<u8>, key: &ArtifactKey) {
289    match key {
290        ArtifactKey::Root(id) => {
291            w.push(0);
292            w.extend_from_slice(&id.0);
293        }
294        ArtifactKey::Content(h) => {
295            w.push(1);
296            w.extend_from_slice(h);
297        }
298    }
299}
300
301fn truncate_utf8(s: &str, max: usize) -> &str {
302    if s.len() <= max {
303        return s;
304    }
305    let mut end = max;
306    while !s.is_char_boundary(end) {
307        end -= 1;
308    }
309    &s[..end]
310}
311
312struct Reader<'a>(&'a [u8]);
313
314impl<'a> Reader<'a> {
315    fn take(&mut self, n: usize) -> Result<&'a [u8], WireError> {
316        if n > self.0.len() {
317            return Err(bad("truncated artifact message"));
318        }
319        let (head, tail) = self.0.split_at(n);
320        self.0 = tail;
321        Ok(head)
322    }
323    fn u8(&mut self) -> Result<u8, WireError> {
324        Ok(self.take(1)?[0])
325    }
326    fn u16(&mut self) -> Result<u16, WireError> {
327        let b = self.take(2)?;
328        Ok(u16::from_be_bytes([b[0], b[1]]))
329    }
330    fn u32(&mut self) -> Result<u32, WireError> {
331        let b = self.take(4)?;
332        Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
333    }
334    fn u64(&mut self) -> Result<u64, WireError> {
335        let mut a = [0u8; 8];
336        a.copy_from_slice(self.take(8)?);
337        Ok(u64::from_be_bytes(a))
338    }
339    fn hash(&mut self) -> Result<[u8; 32], WireError> {
340        let mut a = [0u8; 32];
341        a.copy_from_slice(self.take(32)?);
342        Ok(a)
343    }
344    fn key(&mut self) -> Result<ArtifactKey, WireError> {
345        match self.u8()? {
346            0 => Ok(ArtifactKey::Root(ArtifactId(self.hash()?))),
347            1 => Ok(ArtifactKey::Content(self.hash()?)),
348            k => Err(bad(format!("unknown key kind {k}"))),
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    fn id() -> ArtifactId {
358        ArtifactId([7; 32])
359    }
360
361    fn roundtrip(m: ArtifactMsg) {
362        let raw = m.to_wire().unwrap();
363        assert_eq!(ArtifactMsg::from_wire(&raw).unwrap(), m);
364    }
365
366    #[test]
367    fn every_message_roundtrips_in_its_frame_type() {
368        let mut have = Bitfield::new(11);
369        have.set(0, true);
370        have.set(10, true);
371        let d = ArtifactDescriptor {
372            piece_size: 1024,
373            total_size: 2048 + 5,
374            content_hash: [1; 32],
375            pieces: vec![[2; 32], [3; 32], [4; 32]],
376        };
377        roundtrip(ArtifactMsg::MetaRequest {
378            key: ArtifactKey::Content([9; 32]),
379        });
380        roundtrip(ArtifactMsg::Announce { id: id(), have });
381        roundtrip(ArtifactMsg::Request {
382            id: id(),
383            pieces: vec![0, 3, 9],
384        });
385        roundtrip(ArtifactMsg::Meta { descriptor: d });
386        roundtrip(ArtifactMsg::Piece {
387            id: id(),
388            index: 2,
389            offset: 4096,
390            data: vec![5; 100],
391        });
392        roundtrip(ArtifactMsg::NoPiece { id: id(), index: 1 });
393        roundtrip(ArtifactMsg::Reject {
394            key: ArtifactKey::Root(id()),
395            reason: "not servable".into(),
396        });
397        let req = ArtifactMsg::Request {
398            id: id(),
399            pieces: vec![],
400        };
401        assert_eq!(req.frame_type().as_byte(), 0x0B);
402        let piece = ArtifactMsg::NoPiece { id: id(), index: 0 };
403        assert_eq!(piece.frame_type().as_byte(), 0x0C);
404    }
405
406    #[test]
407    fn oversize_frames_are_refused_both_ways() {
408        let big = ArtifactMsg::Piece {
409            id: id(),
410            index: 0,
411            offset: 0,
412            data: vec![0; MAX_ARTIFACT_FRAME],
413        };
414        assert!(matches!(
415            big.to_wire(),
416            Err(WireError::FrameTooLarge { .. })
417        ));
418        // A peer that ignores the cap: the length prefix alone is refused.
419        let mut raw = vec![];
420        raw.extend_from_slice(&((MAX_ARTIFACT_FRAME + 2) as u32).to_be_bytes());
421        raw.push(0x0C);
422        raw.extend_from_slice(&[0; 64]);
423        assert!(matches!(
424            ArtifactMsg::from_wire(&raw),
425            Err(WireError::FrameTooLarge { .. })
426        ));
427    }
428
429    #[test]
430    fn malformed_frames_are_refused() {
431        let mut raw = ArtifactMsg::NoPiece { id: id(), index: 1 }
432            .to_wire()
433            .unwrap();
434        // Wrong frame type for the tag.
435        raw[4] = 0x0B;
436        assert!(ArtifactMsg::from_wire(&raw).is_err());
437        // Trailing bytes.
438        let mut raw = ArtifactMsg::NoPiece { id: id(), index: 1 }
439            .to_wire()
440            .unwrap();
441        raw.push(0);
442        assert!(ArtifactMsg::from_wire(&raw).is_err());
443        // Descriptor whose piece count does not match its size.
444        let d = ArtifactDescriptor {
445            piece_size: 1024,
446            total_size: 10_000,
447            content_hash: [0; 32],
448            pieces: vec![[0; 32]],
449        };
450        let raw = ArtifactMsg::Meta { descriptor: d }.to_wire().unwrap();
451        assert!(ArtifactMsg::from_wire(&raw).is_err());
452    }
453}