p4rs/
lib.rs

1// Copyright 2026 Oxide Computer Company
2
3//! This is the runtime support create for `x4c` generated programs.
4//!
5//! The main abstraction in this crate is the [`Pipeline`] trait. Rust code that
6//! is generated by `x4c` implements this trait. A `main_pipeline` struct is
7//! exported by the generated code that implements [`Pipeline`]. Users can wrap
8//! the `main_pipeline` object in harness code to provide higher level
9//! interfaces for table manipulation and packet i/o.
10//!
11//! ```rust
12//! use p4rs::{ packet_in, packet_out, Pipeline };
13//! use std::net::Ipv6Addr;
14//!
15//! struct Handler {
16//!     pipe: Box<dyn Pipeline>
17//! }
18//!
19//! impl Handler {
20//!     /// Create a new pipeline handler.
21//!     fn new(pipe: Box<dyn Pipeline>) -> Self {
22//!         Self{ pipe }
23//!     }
24//!
25//!     /// Handle a packet from the specified port. If the pipeline produces
26//!     /// an output result, send the processed packet to the output port
27//!     /// returned by the pipeline.
28//!     fn handle_packet(&mut self, port: u16, pkt: &[u8]) {
29//!
30//!         let mut input = packet_in::new(pkt);
31//!
32//!         let output =  self.pipe.process_packet(port, &mut input);
33//!         for (out_pkt, out_port) in &output {
34//!             let mut out = out_pkt.header_data.clone();
35//!             out.extend_from_slice(out_pkt.payload_data);
36//!             self.send_packet(*out_port, &out);
37//!         }
38//!
39//!     }
40//!
41//!     /// Add a routing table entry. Packets for the provided destination will
42//!     /// be sent out the specified port.
43//!     fn add_router_entry(&mut self, dest: Ipv6Addr, port: u16) {
44//!         self.pipe.add_table_entry(
45//!             "ingress.router.ipv6_routes", // qualified name of the table
46//!             "forward_out_port",           // action to invoke on a hit
47//!             &dest.octets(),
48//!             &port.to_le_bytes(),
49//!             0,
50//!         );
51//!     }
52//!
53//!     /// Send a packet out the specified port.
54//!     fn send_packet(&self, port: u16, pkt: &[u8]) {
55//!         // send the packet ...
56//!     }
57//! }
58//! ```
59//!
60#![allow(incomplete_features)]
61#![allow(non_camel_case_types)]
62
63use std::fmt;
64use std::net::IpAddr;
65
66pub use error::TryFromSliceError;
67use serde::{Deserialize, Serialize};
68
69use bitvec::prelude::*;
70
71pub mod error;
72//pub mod hicuts;
73//pub mod rice;
74pub mod bitmath;
75pub mod checksum;
76pub mod externs;
77pub mod table;
78
79#[usdt::provider]
80mod p4rs_provider {
81    fn match_miss(_: &str) {}
82}
83
84#[derive(Debug)]
85pub struct Bit<'a, const N: usize>(pub &'a [u8]);
86
87impl<'a, const N: usize> Bit<'a, N> {
88    //TODO measure the weight of returning TryFromSlice error versus just
89    //dropping and incrementing a counter. Relying on dtrace for more detailed
90    //debugging.
91    pub fn new(data: &'a [u8]) -> Result<Self, TryFromSliceError> {
92        let required_bytes = if N & 7 > 0 { (N >> 3) + 1 } else { N >> 3 };
93        if data.len() < required_bytes {
94            return Err(TryFromSliceError(N));
95        }
96        Ok(Self(&data[..required_bytes]))
97    }
98}
99
100impl<const N: usize> fmt::LowerHex for Bit<'_, N> {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        for x in self.0 {
103            fmt::LowerHex::fmt(&x, f)?;
104        }
105        Ok(())
106    }
107}
108
109// TODO more of these for other sizes
110impl<'a> From<Bit<'a, 16>> for u16 {
111    fn from(b: Bit<'a, 16>) -> u16 {
112        u16::from_be_bytes([b.0[0], b.0[1]])
113    }
114}
115
116// TODO more of these for other sizes
117impl std::hash::Hash for Bit<'_, 8> {
118    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
119        self.0[0].hash(state);
120    }
121}
122
123impl std::cmp::PartialEq for Bit<'_, 8> {
124    fn eq(&self, other: &Self) -> bool {
125        self.0[0] == other.0[0]
126    }
127}
128
129impl std::cmp::Eq for Bit<'_, 8> {}
130
131/// Every packet that goes through a P4 pipeline is represented as a `packet_in`
132/// instance. `packet_in` objects wrap an underlying mutable data reference that
133/// is ultimately rooted in a memory mapped region containing a ring of packets.
134#[derive(Debug)]
135pub struct packet_in<'a> {
136    /// The underlying data. Owned by an external, memory-mapped packet ring.
137    pub data: &'a [u8],
138
139    /// Extraction index. Everything before `index` has been extracted already.
140    /// Only data after `index` is eligble for extraction. Extraction is always
141    /// for contiguous segments of the underlying packet ring data.
142    pub index: usize,
143}
144
145#[derive(Debug)]
146pub struct packet_out<'a> {
147    pub header_data: Vec<u8>,
148    pub payload_data: &'a [u8],
149}
150
151#[derive(Debug, Serialize, Deserialize)]
152pub struct TableEntry {
153    pub action_id: String,
154    pub keyset_data: Vec<u8>,
155    pub parameter_data: Vec<u8>,
156}
157
158pub trait Pipeline: Send {
159    /// Process an input packet and produce a set of output packets.
160    fn process_packet<'a>(
161        &mut self,
162        port: u16,
163        pkt: &mut packet_in<'a>,
164    ) -> Vec<(packet_out<'a>, u16)>;
165
166    //TODO use struct TableEntry?
167    /// Add an entry to a table identified by table_id.
168    fn add_table_entry(
169        &mut self,
170        table_id: &str,
171        action_id: &str,
172        keyset_data: &[u8],
173        parameter_data: &[u8],
174        priority: u32,
175    );
176
177    /// Remove an entry from a table identified by table_id.
178    fn remove_table_entry(&mut self, table_id: &str, keyset_data: &[u8]);
179
180    /// Remove all entries from a table identified by table_id.
181    ///
182    /// Returns `true` if the table was cleared, or `false` if `table_id` maps
183    /// to no table in this pipeline. This return type distinguishes an unknown
184    /// table from an already-empty one, like [`Pipeline::get_table_entries`]
185    /// does with `None`.
186    // NOTE: This maps to dendrite's SoftNPU `TableOps::clear` implementation,
187    // which is the analogue of `bf_rt_table_clear` on Tofino, for example.
188    fn clear_table_entries(&mut self, table_id: &str) -> bool;
189
190    /// Get all the entries in a table.
191    fn get_table_entries(&self, table_id: &str) -> Option<Vec<TableEntry>>;
192
193    /// Get a list of table ids
194    fn get_table_ids(&self) -> Vec<&str>;
195}
196
197/// A fixed length header trait.
198pub trait Header {
199    fn new() -> Self;
200    fn size() -> usize;
201    fn set(&mut self, buf: &[u8]) -> Result<(), TryFromSliceError>;
202    fn set_valid(&mut self);
203    fn set_invalid(&mut self);
204    fn is_valid(&self) -> bool;
205    fn to_bitvec(&self) -> BitVec<u8, Msb0>;
206}
207
208impl<'a> packet_in<'a> {
209    pub fn new(data: &'a [u8]) -> Self {
210        Self { data, index: 0 }
211    }
212
213    // TODO: this function signature is a bit unforunate in the sense that the
214    // p4 compiler generates call sites based on a p4 `packet_in` extern
215    // definition. But based on that definition, there is no way for the
216    // compiler to know that this function returns a result that needs to be
217    // interrogated. In fact, the signature for packet_in::extract from the p4
218    // standard library requires the return type to be `void`, so this signature
219    // cannot return a result without the compiler having special knowledge of
220    // functions that happen to be called "extract".
221    pub fn extract<H: Header>(&mut self, h: &mut H) {
222        //TODO what if a header does not end on a byte boundary?
223        let n = H::size();
224        let start = if self.index > 0 { self.index >> 3 } else { 0 };
225        match h.set(&self.data[start..start + (n >> 3)]) {
226            Ok(_) => {}
227            Err(e) => {
228                //TODO better than this
229                println!("packet extraction failed: {}", e);
230            }
231        }
232        self.index += n;
233        h.set_valid();
234    }
235
236    // This is the same as extract except we return a new header instead of
237    // modifying an existing one.
238    pub fn extract_new<H: Header>(&mut self) -> Result<H, TryFromSliceError> {
239        let n = H::size();
240        let start = if self.index > 0 { self.index >> 3 } else { 0 };
241        self.index += n;
242        let mut x = H::new();
243        x.set(&self.data[start..start + (n >> 3)])?;
244        Ok(x)
245    }
246}
247
248//XXX: remove once classifier defined in terms of bitvecs
249pub fn bitvec_to_biguint(bv: &BitVec<u8, Msb0>) -> table::BigUintKey {
250    table::BigUintKey {
251        value: if bv.is_empty() {
252            num::BigUint::default()
253        } else {
254            bv.load_le::<u128>().into()
255        },
256        width: bv.len().div_ceil(8),
257    }
258}
259
260pub fn bitvec_to_ip6addr(bv: &BitVec<u8, Msb0>) -> std::net::IpAddr {
261    let mut arr: [u8; 16] = bv.as_raw_slice().try_into().unwrap();
262    arr.reverse();
263    std::net::IpAddr::V6(std::net::Ipv6Addr::from(arr))
264}
265
266#[repr(C, align(16))]
267pub struct AlignedU128(pub u128);
268
269pub fn int_to_bitvec(x: i128) -> BitVec<u8, Msb0> {
270    //let mut bv = BitVec::<u8, Msb0>::new();
271    let mut bv = bitvec![mut u8, Msb0; 0; 128];
272    bv.store(x);
273    bv
274}
275
276/// Resize `x` to 16 bits, zero-extending or truncating its value.
277pub fn bitvec_to_bitvec16(x: BitVec<u8, Msb0>) -> BitVec<u8, Msb0> {
278    bitvec_resize(x, 16)
279}
280
281/// Resize `x` to `width` bits.
282///
283/// Any added high-order bits are zero.
284///
285/// This backs x4c's implicit `bit<W>` to `bit<V>` widening, which P4-16
286/// expresses as an explicit cast ([8.11.1]).
287///
288/// [8.11.1]: https://p4.org/wp-content/uploads/sites/53/2024/10/P4-16-spec-v1.2.5.html#sec-explicit-casts
289pub fn bitvec_resize(x: BitVec<u8, Msb0>, width: usize) -> BitVec<u8, Msb0> {
290    let value: u128 = if x.is_empty() { 0 } else { x.load_le() };
291    let mut resized = bitvec![u8, Msb0; 0; width];
292    if width > 0 {
293        resized.store_le(value);
294    }
295    resized
296}
297
298pub fn dump_bv(x: &BitVec<u8, Msb0>) -> String {
299    if x.is_empty() {
300        "∅".into()
301    } else {
302        let v: u128 = x.load_le();
303        format!("{:x}", v)
304    }
305}
306
307pub fn extract_exact_key(
308    keyset_data: &[u8],
309    offset: usize,
310    len: usize,
311) -> table::Key {
312    table::Key::Exact(table::BigUintKey {
313        value: num::BigUint::from_bytes_le(&keyset_data[offset..offset + len]),
314        width: len,
315    })
316}
317
318pub fn extract_range_key(
319    keyset_data: &[u8],
320    offset: usize,
321    len: usize,
322) -> table::Key {
323    table::Key::Range(
324        table::BigUintKey {
325            value: num::BigUint::from_bytes_le(
326                &keyset_data[offset..offset + len],
327            ),
328            width: len,
329        },
330        table::BigUintKey {
331            value: num::BigUint::from_bytes_le(
332                &keyset_data[offset + len..offset + len + len],
333            ),
334            width: len,
335        },
336    )
337}
338
339/// Extract a ternary key from the provided keyset data. Ternary keys come in
340/// two parts. The first part is a leading bit that indicates whether we care
341/// about the value. If that leading bit is non-zero, the trailing bits of the
342/// key are interpreted as a binary value. If the leading bit is zero, the
343/// trailing bits are ignored and a Ternary::DontCare key is returned.
344pub fn extract_ternary_key(
345    keyset_data: &[u8],
346    offset: usize,
347    len: usize,
348) -> table::Key {
349    let care = keyset_data[offset];
350    if care != 0 {
351        table::Key::Ternary(table::Ternary::Value(table::BigUintKey {
352            value: num::BigUint::from_bytes_le(
353                &keyset_data[offset + 1..offset + 1 + len],
354            ),
355            width: len,
356        }))
357    } else {
358        table::Key::Ternary(table::Ternary::DontCare)
359    }
360}
361
362pub fn extract_lpm_key(
363    keyset_data: &[u8],
364    offset: usize,
365    len: usize,
366) -> table::Key {
367    let (addr, prefix_len) = match len {
368        // IPv4
369        4 => {
370            let data: [u8; 4] =
371                keyset_data[offset..offset + 4].try_into().unwrap();
372            (IpAddr::from(data), keyset_data[offset + 4])
373        }
374        // IPv6
375        16 => {
376            let data: [u8; 16] =
377                keyset_data[offset..offset + 16].try_into().unwrap();
378            (IpAddr::from(data), keyset_data[offset + 16])
379        }
380        x => {
381            panic!("lpm: data len must be 4 (ipv4) or 16 (ipv6) found {}", x);
382        }
383    };
384
385    table::Key::Lpm(table::Prefix {
386        addr,
387        len: prefix_len,
388    })
389}
390
391pub fn extract_bool_action_parameter(
392    parameter_data: &[u8],
393    offset: usize,
394) -> bool {
395    parameter_data[offset] == 1
396}
397
398/// Extract a `bit<size>` action parameter from the control-plane action data.
399///
400/// Action data lays out each parameter in declaration order as
401/// `size.div_ceil(8)` little-endian bytes, starting at `offset`. x4c keeps
402/// every numeric value little-endian inside a program (header fields are
403/// byte-reversed on parse and once again on deparse), so the control plane
404/// passes these values into this representation with no conversion necessary;
405/// the same is true for `exact` and `range` keys.
406///
407/// The returned vector has the same layout as [`bitvec_resize`] output and
408/// header fields of the same width, so `load_le` recovers the value whenever
409/// the width fits the target integer (up to 128 bits for `u128`).
410pub fn extract_bit_action_parameter(
411    parameter_data: &[u8],
412    offset: usize,
413    size: usize,
414) -> BitVec<u8, Msb0> {
415    let byte_size = size.div_ceil(8);
416    let mut bytes = parameter_data[offset..offset + byte_size].to_vec();
417    let rem = size % 8;
418    // Msb0 keeps a partial last byte in its high bits, so the value's top
419    // `rem` bits move up before the vector is truncated to `size`.
420    if rem != 0 {
421        bytes[byte_size - 1] <<= 8 - rem;
422    }
423    let mut bv = BitVec::<u8, Msb0>::from_vec(bytes);
424    bv.resize(size, false);
425    bv
426}
427
428/// Collect output ports from a bitmap, excluding the ingress port.
429///
430/// Bits at or above `radix` are ignored, which means that a bitmap cannot
431/// address a port outside the pipeline.
432///
433/// The bitmap is interpreted as a little-endian integer: bit N
434/// (i.e., the bit with numeric value 2^N) corresponds to port N.
435/// This matches the encoding used by P4 arithmetic (`128w1 << port`)
436/// via [`bitmath::shl_le`].
437pub fn bitmap_to_port_list(
438    bitmap: &BitVec<u8, Msb0>,
439    ingress_port: u16,
440    radix: u16,
441) -> Vec<u16> {
442    if bitmap.is_empty() {
443        return Vec::new();
444    }
445    let val: u128 = bitmap.load_le();
446    (0..radix.min(128))
447        .filter(|&p| val & (1u128 << p) != 0 && p != ingress_port)
448        .collect()
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use num::bigint::BigUint;
455
456    #[test]
457    fn bitvec_resize_preserves_value() {
458        for (source_width, target_width, value) in [
459            (4, 8, 1u128),
460            (4, 16, 0xf),
461            (8, 12, 0xab),
462            (12, 16, 0xabc),
463            (12, 24, 0xabc),
464            (16, 128, 0xbeef),
465            (128, 128, u128::MAX),
466        ] {
467            let mut input = bitvec![u8, Msb0; 0; source_width];
468            input.store_le(value);
469
470            let out = bitvec_resize(input, target_width);
471
472            assert_eq!(out.len(), target_width);
473            assert_eq!(
474                out.load_le::<u128>(),
475                value,
476                "{source_width} -> {target_width}",
477            );
478        }
479    }
480
481    #[test]
482    fn extract_bit_action_parameter_sub_byte_widths() {
483        for (width, value) in [
484            (12, 100u16),
485            (12, 256),
486            (12, 300),
487            (12, 4094),
488            (4, 0xa),
489            (9, 0x1ab),
490            (10, 0x2ab),
491            (11, 0x5ab),
492            (13, 0x1abc),
493            (14, 0x2abc),
494            (15, 0x5abc),
495            (16, 0xbeef),
496        ] {
497            let mut data = vec![0xffu8];
498            data.extend_from_slice(&value.to_le_bytes());
499
500            let out = extract_bit_action_parameter(&data, 1, width);
501
502            assert_eq!(out.len(), width);
503            assert_eq!(out.load_le::<u16>(), value, "bit<{width}> = {value}");
504
505            let mut expected = bitvec![u8, Msb0; 0; width];
506            expected.store_le(value);
507            assert_eq!(out, expected, "bit<{width}> layout");
508        }
509    }
510
511    #[test]
512    fn extract_bit_action_parameter_wide_unaligned() {
513        let mut data = vec![0xa5u8; 16];
514        data.push(0x0f);
515
516        let out = extract_bit_action_parameter(&data, 0, 132);
517
518        assert_eq!(out.len(), 132);
519        let mut expected = vec![0xa5u8; 16];
520        expected.push(0xf0);
521        assert_eq!(out.as_raw_slice(), expected.as_slice());
522    }
523
524    #[test]
525    fn bitvec_resize_empty_values() {
526        let out = bitvec_resize(BitVec::new(), 8);
527        assert_eq!(out.len(), 8);
528        assert_eq!(out.load_le::<u8>(), 0);
529        assert!(bitvec_resize(BitVec::new(), 0).is_empty());
530    }
531
532    #[test]
533    fn bitvec_resize_aligns_slices() {
534        for offset in 0..8 {
535            for (source_width, target_width, value) in [
536                (4, 8, 0xbu128),
537                (8, 16, 0xab),
538                (12, 16, 0xabc),
539                (16, 24, 0xbeef),
540                (128, 128, u128::MAX),
541            ] {
542                let storage = bitvec![u8, Msb0; 0; offset + source_width];
543                let mut input = storage[offset..].to_bitvec();
544                input.store_le(value);
545
546                let out = bitvec_resize(input, target_width);
547                let mut expected = bitvec![u8, Msb0; 0; target_width];
548                expected.store_le(value);
549
550                assert_eq!(
551                    out, expected,
552                    "offset {offset}: {source_width} -> {target_width}",
553                );
554            }
555        }
556    }
557
558    /// Checks [`bitvec_to_biguint`] is semantically equivalent to `load_le`,
559    /// even with non-byte-aligned widths.
560    #[test]
561    fn bitvec_to_biguint_non_byte_aligned() {
562        for width in 1..=16 {
563            let mut bv = bitvec![u8, Msb0; 0; width];
564            bv.store_le(0xbeefu16);
565
566            assert_eq!(
567                bitvec_to_biguint(&bv).value,
568                BigUint::from(bv.load_le::<u16>()),
569            );
570        }
571    }
572
573    #[test]
574    fn bitvec_to_biguint_parsed_ihl() {
575        let data = [0x45u8];
576        let ihl = data.view_bits::<Msb0>()[4..8].to_bitvec();
577        let key = bitvec_to_biguint(&ihl);
578
579        assert_eq!(key.value, BigUint::from(5u8));
580        assert_eq!(key.width, 1);
581    }
582
583    #[test]
584    fn bitvec_to_biguint_storage_offsets() {
585        for offset in 0..8 {
586            for width in 1..=128 {
587                let storage = bitvec![u8, Msb0; 1; offset + width];
588                let mut bv = storage[offset..].to_bitvec();
589                let expected = u128::MAX >> (128 - width);
590                bv.store_le(expected);
591                bv.set_uninitialized(true);
592                let key = bitvec_to_biguint(&bv);
593
594                assert_eq!(key.value, BigUint::from(expected));
595                assert_eq!(key.width, width.div_ceil(8));
596            }
597        }
598    }
599
600    #[test]
601    fn bitvec_to_biguint_empty() {
602        let key = bitvec_to_biguint(&BitVec::new());
603
604        assert_eq!(key.value, BigUint::default());
605        assert_eq!(key.width, 0);
606    }
607
608    #[test]
609    fn replication_radix_is_bounded_by_bitmap_width() {
610        let mut bitmap = bitvec![u8, Msb0; 0; 128];
611        bitmap.store_le(1u128 << 127);
612
613        assert_eq!(bitmap_to_port_list(&bitmap, 0, u16::MAX), vec![127]);
614    }
615
616    #[test]
617    fn replication_of_empty_bitmap_yields_no_ports() {
618        let bitmap: BitVec<u8, Msb0> = BitVec::new();
619
620        assert_eq!(bitmap_to_port_list(&bitmap, 0, 4), Vec::<u16>::new());
621    }
622}