Skip to main content

rvf_types/
ebpf.rs

1//! EBPF_SEG (0x0F) types for the RVF computational container.
2//!
3//! Defines the 64-byte `EbpfHeader` and associated enums per ADR-030.
4//! The EBPF_SEG embeds an eBPF program for kernel-level fast-path
5//! vector distance computation (L0 cache in BPF maps).
6
7use crate::error::RvfError;
8
9/// Magic number for `EbpfHeader`: "RVBP" in big-endian.
10pub const EBPF_MAGIC: u32 = 0x5256_4250;
11
12/// eBPF program type classification.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[repr(u8)]
16pub enum EbpfProgramType {
17    /// XDP program for distance computation on packets.
18    XdpDistance = 0x00,
19    /// TC classifier for query routing.
20    TcFilter = 0x01,
21    /// Socket filter for query preprocessing.
22    SocketFilter = 0x02,
23    /// Tracepoint for performance monitoring.
24    Tracepoint = 0x03,
25    /// Kprobe for dynamic instrumentation.
26    Kprobe = 0x04,
27    /// Cgroup socket buffer filter.
28    CgroupSkb = 0x05,
29    /// Custom program type.
30    Custom = 0xFF,
31}
32
33impl TryFrom<u8> for EbpfProgramType {
34    type Error = RvfError;
35
36    fn try_from(value: u8) -> Result<Self, Self::Error> {
37        match value {
38            0x00 => Ok(Self::XdpDistance),
39            0x01 => Ok(Self::TcFilter),
40            0x02 => Ok(Self::SocketFilter),
41            0x03 => Ok(Self::Tracepoint),
42            0x04 => Ok(Self::Kprobe),
43            0x05 => Ok(Self::CgroupSkb),
44            0xFF => Ok(Self::Custom),
45            _ => Err(RvfError::InvalidEnumValue {
46                type_name: "EbpfProgramType",
47                value: value as u64,
48            }),
49        }
50    }
51}
52
53/// eBPF attach point classification.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56#[repr(u8)]
57pub enum EbpfAttachType {
58    /// XDP hook on NIC ingress.
59    XdpIngress = 0x00,
60    /// TC ingress qdisc.
61    TcIngress = 0x01,
62    /// TC egress qdisc.
63    TcEgress = 0x02,
64    /// Socket filter attachment.
65    SocketFilter = 0x03,
66    /// Cgroup ingress.
67    CgroupIngress = 0x04,
68    /// Cgroup egress.
69    CgroupEgress = 0x05,
70    /// No automatic attachment.
71    None = 0xFF,
72}
73
74impl TryFrom<u8> for EbpfAttachType {
75    type Error = RvfError;
76
77    fn try_from(value: u8) -> Result<Self, Self::Error> {
78        match value {
79            0x00 => Ok(Self::XdpIngress),
80            0x01 => Ok(Self::TcIngress),
81            0x02 => Ok(Self::TcEgress),
82            0x03 => Ok(Self::SocketFilter),
83            0x04 => Ok(Self::CgroupIngress),
84            0x05 => Ok(Self::CgroupEgress),
85            0xFF => Ok(Self::None),
86            _ => Err(RvfError::InvalidEnumValue {
87                type_name: "EbpfAttachType",
88                value: value as u64,
89            }),
90        }
91    }
92}
93
94/// 64-byte header for EBPF_SEG payloads.
95///
96/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
97/// little-endian on the wire.
98#[derive(Clone, Copy, Debug)]
99#[repr(C)]
100pub struct EbpfHeader {
101    /// Magic: `EBPF_MAGIC` (0x52564250, "RVBP").
102    pub ebpf_magic: u32,
103    /// EbpfHeader format version (currently 1).
104    pub header_version: u16,
105    /// eBPF program type (see `EbpfProgramType`).
106    pub program_type: u8,
107    /// eBPF attach point (see `EbpfAttachType`).
108    pub attach_type: u8,
109    /// Bitfield flags for the eBPF program.
110    pub program_flags: u32,
111    /// Number of BPF instructions (max 65535).
112    pub insn_count: u16,
113    /// Maximum vector dimension this program handles.
114    pub max_dimension: u16,
115    /// ELF object size (bytes).
116    pub program_size: u64,
117    /// Number of BPF maps defined.
118    pub map_count: u32,
119    /// BTF (BPF Type Format) section size.
120    pub btf_size: u32,
121    /// SHAKE-256-256 of the ELF object.
122    pub program_hash: [u8; 32],
123}
124
125// Compile-time assertion: EbpfHeader must be exactly 64 bytes.
126const _: () = assert!(core::mem::size_of::<EbpfHeader>() == 64);
127
128impl EbpfHeader {
129    /// Serialize the header to a 64-byte little-endian array.
130    pub fn to_bytes(&self) -> [u8; 64] {
131        let mut buf = [0u8; 64];
132        buf[0x00..0x04].copy_from_slice(&self.ebpf_magic.to_le_bytes());
133        buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
134        buf[0x06] = self.program_type;
135        buf[0x07] = self.attach_type;
136        buf[0x08..0x0C].copy_from_slice(&self.program_flags.to_le_bytes());
137        buf[0x0C..0x0E].copy_from_slice(&self.insn_count.to_le_bytes());
138        buf[0x0E..0x10].copy_from_slice(&self.max_dimension.to_le_bytes());
139        buf[0x10..0x18].copy_from_slice(&self.program_size.to_le_bytes());
140        buf[0x18..0x1C].copy_from_slice(&self.map_count.to_le_bytes());
141        buf[0x1C..0x20].copy_from_slice(&self.btf_size.to_le_bytes());
142        buf[0x20..0x40].copy_from_slice(&self.program_hash);
143        buf
144    }
145
146    /// Deserialize an `EbpfHeader` from a 64-byte slice.
147    pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
148        let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
149        if magic != EBPF_MAGIC {
150            return Err(RvfError::BadMagic {
151                expected: EBPF_MAGIC,
152                got: magic,
153            });
154        }
155
156        Ok(Self {
157            ebpf_magic: magic,
158            header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
159            program_type: data[0x06],
160            attach_type: data[0x07],
161            program_flags: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
162            insn_count: u16::from_le_bytes([data[0x0C], data[0x0D]]),
163            max_dimension: u16::from_le_bytes([data[0x0E], data[0x0F]]),
164            program_size: u64::from_le_bytes([
165                data[0x10], data[0x11], data[0x12], data[0x13],
166                data[0x14], data[0x15], data[0x16], data[0x17],
167            ]),
168            map_count: u32::from_le_bytes([data[0x18], data[0x19], data[0x1A], data[0x1B]]),
169            btf_size: u32::from_le_bytes([data[0x1C], data[0x1D], data[0x1E], data[0x1F]]),
170            program_hash: {
171                let mut h = [0u8; 32];
172                h.copy_from_slice(&data[0x20..0x40]);
173                h
174            },
175        })
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    fn sample_header() -> EbpfHeader {
184        EbpfHeader {
185            ebpf_magic: EBPF_MAGIC,
186            header_version: 1,
187            program_type: EbpfProgramType::XdpDistance as u8,
188            attach_type: EbpfAttachType::XdpIngress as u8,
189            program_flags: 0,
190            insn_count: 256,
191            max_dimension: 1536,
192            program_size: 4096,
193            map_count: 2,
194            btf_size: 512,
195            program_hash: [0xDE; 32],
196        }
197    }
198
199    #[test]
200    fn header_size_is_64() {
201        assert_eq!(core::mem::size_of::<EbpfHeader>(), 64);
202    }
203
204    #[test]
205    fn magic_bytes_match_ascii() {
206        let bytes_be = EBPF_MAGIC.to_be_bytes();
207        assert_eq!(&bytes_be, b"RVBP");
208    }
209
210    #[test]
211    fn round_trip_serialization() {
212        let original = sample_header();
213        let bytes = original.to_bytes();
214        let decoded = EbpfHeader::from_bytes(&bytes).expect("from_bytes should succeed");
215
216        assert_eq!(decoded.ebpf_magic, EBPF_MAGIC);
217        assert_eq!(decoded.header_version, 1);
218        assert_eq!(decoded.program_type, EbpfProgramType::XdpDistance as u8);
219        assert_eq!(decoded.attach_type, EbpfAttachType::XdpIngress as u8);
220        assert_eq!(decoded.program_flags, 0);
221        assert_eq!(decoded.insn_count, 256);
222        assert_eq!(decoded.max_dimension, 1536);
223        assert_eq!(decoded.program_size, 4096);
224        assert_eq!(decoded.map_count, 2);
225        assert_eq!(decoded.btf_size, 512);
226        assert_eq!(decoded.program_hash, [0xDE; 32]);
227    }
228
229    #[test]
230    fn bad_magic_returns_error() {
231        let mut bytes = sample_header().to_bytes();
232        bytes[0] = 0x00; // corrupt magic
233        let err = EbpfHeader::from_bytes(&bytes).unwrap_err();
234        match err {
235            RvfError::BadMagic { expected, .. } => assert_eq!(expected, EBPF_MAGIC),
236            other => panic!("expected BadMagic, got {other:?}"),
237        }
238    }
239
240    #[test]
241    fn field_offsets() {
242        let h = sample_header();
243        let base = &h as *const _ as usize;
244
245        assert_eq!(&h.ebpf_magic as *const _ as usize - base, 0x00);
246        assert_eq!(&h.header_version as *const _ as usize - base, 0x04);
247        assert_eq!(&h.program_type as *const _ as usize - base, 0x06);
248        assert_eq!(&h.attach_type as *const _ as usize - base, 0x07);
249        assert_eq!(&h.program_flags as *const _ as usize - base, 0x08);
250        assert_eq!(&h.insn_count as *const _ as usize - base, 0x0C);
251        assert_eq!(&h.max_dimension as *const _ as usize - base, 0x0E);
252        assert_eq!(&h.program_size as *const _ as usize - base, 0x10);
253        assert_eq!(&h.map_count as *const _ as usize - base, 0x18);
254        assert_eq!(&h.btf_size as *const _ as usize - base, 0x1C);
255        assert_eq!(&h.program_hash as *const _ as usize - base, 0x20);
256    }
257
258    #[test]
259    fn ebpf_program_type_try_from() {
260        assert_eq!(EbpfProgramType::try_from(0x00), Ok(EbpfProgramType::XdpDistance));
261        assert_eq!(EbpfProgramType::try_from(0x01), Ok(EbpfProgramType::TcFilter));
262        assert_eq!(EbpfProgramType::try_from(0x02), Ok(EbpfProgramType::SocketFilter));
263        assert_eq!(EbpfProgramType::try_from(0x03), Ok(EbpfProgramType::Tracepoint));
264        assert_eq!(EbpfProgramType::try_from(0x04), Ok(EbpfProgramType::Kprobe));
265        assert_eq!(EbpfProgramType::try_from(0x05), Ok(EbpfProgramType::CgroupSkb));
266        assert_eq!(EbpfProgramType::try_from(0xFF), Ok(EbpfProgramType::Custom));
267        assert!(EbpfProgramType::try_from(0x06).is_err());
268        assert!(EbpfProgramType::try_from(0x80).is_err());
269    }
270
271    #[test]
272    fn ebpf_attach_type_try_from() {
273        assert_eq!(EbpfAttachType::try_from(0x00), Ok(EbpfAttachType::XdpIngress));
274        assert_eq!(EbpfAttachType::try_from(0x01), Ok(EbpfAttachType::TcIngress));
275        assert_eq!(EbpfAttachType::try_from(0x02), Ok(EbpfAttachType::TcEgress));
276        assert_eq!(EbpfAttachType::try_from(0x03), Ok(EbpfAttachType::SocketFilter));
277        assert_eq!(EbpfAttachType::try_from(0x04), Ok(EbpfAttachType::CgroupIngress));
278        assert_eq!(EbpfAttachType::try_from(0x05), Ok(EbpfAttachType::CgroupEgress));
279        assert_eq!(EbpfAttachType::try_from(0xFF), Ok(EbpfAttachType::None));
280        assert!(EbpfAttachType::try_from(0x06).is_err());
281        assert!(EbpfAttachType::try_from(0x80).is_err());
282    }
283
284    #[test]
285    fn max_dimension_round_trip() {
286        let mut h = sample_header();
287        h.max_dimension = 2048;
288        let bytes = h.to_bytes();
289        let decoded = EbpfHeader::from_bytes(&bytes).unwrap();
290        assert_eq!(decoded.max_dimension, 2048);
291    }
292
293    #[test]
294    fn large_program_size_round_trip() {
295        let mut h = sample_header();
296        h.program_size = 1_048_576; // 1 MiB
297        h.insn_count = 65535;
298        let bytes = h.to_bytes();
299        let decoded = EbpfHeader::from_bytes(&bytes).unwrap();
300        assert_eq!(decoded.program_size, 1_048_576);
301        assert_eq!(decoded.insn_count, 65535);
302    }
303}