p4rs/
lib.rs

1// Copyright 2022 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. Normally
160    /// there will be a single output packet. However, if the pipeline sets
161    /// `egress_metadata_t.broadcast` there may be multiple output packets.
162    fn process_packet<'a>(
163        &mut self,
164        port: u16,
165        pkt: &mut packet_in<'a>,
166    ) -> Vec<(packet_out<'a>, u16)>;
167
168    //TODO use struct TableEntry?
169    /// Add an entry to a table identified by table_id.
170    fn add_table_entry(
171        &mut self,
172        table_id: &str,
173        action_id: &str,
174        keyset_data: &[u8],
175        parameter_data: &[u8],
176        priority: u32,
177    );
178
179    /// Remove an entry from a table identified by table_id.
180    fn remove_table_entry(&mut self, table_id: &str, keyset_data: &[u8]);
181
182    /// Get all the entries in a table.
183    fn get_table_entries(&self, table_id: &str) -> Option<Vec<TableEntry>>;
184
185    /// Get a list of table ids
186    fn get_table_ids(&self) -> Vec<&str>;
187}
188
189/// A fixed length header trait.
190pub trait Header {
191    fn new() -> Self;
192    fn size() -> usize;
193    fn set(&mut self, buf: &[u8]) -> Result<(), TryFromSliceError>;
194    fn set_valid(&mut self);
195    fn set_invalid(&mut self);
196    fn is_valid(&self) -> bool;
197    fn to_bitvec(&self) -> BitVec<u8, Msb0>;
198}
199
200impl<'a> packet_in<'a> {
201    pub fn new(data: &'a [u8]) -> Self {
202        Self { data, index: 0 }
203    }
204
205    // TODO: this function signature is a bit unforunate in the sense that the
206    // p4 compiler generates call sites based on a p4 `packet_in` extern
207    // definition. But based on that definition, there is no way for the
208    // compiler to know that this function returns a result that needs to be
209    // interrogated. In fact, the signature for packet_in::extract from the p4
210    // standard library requires the return type to be `void`, so this signature
211    // cannot return a result without the compiler having special knowledge of
212    // functions that happen to be called "extract".
213    pub fn extract<H: Header>(&mut self, h: &mut H) {
214        //TODO what if a header does not end on a byte boundary?
215        let n = H::size();
216        let start = if self.index > 0 { self.index >> 3 } else { 0 };
217        match h.set(&self.data[start..start + (n >> 3)]) {
218            Ok(_) => {}
219            Err(e) => {
220                //TODO better than this
221                println!("packet extraction failed: {}", e);
222            }
223        }
224        self.index += n;
225        h.set_valid();
226    }
227
228    // This is the same as extract except we return a new header instead of
229    // modifying an existing one.
230    pub fn extract_new<H: Header>(&mut self) -> Result<H, TryFromSliceError> {
231        let n = H::size();
232        let start = if self.index > 0 { self.index >> 3 } else { 0 };
233        self.index += n;
234        let mut x = H::new();
235        x.set(&self.data[start..start + (n >> 3)])?;
236        Ok(x)
237    }
238}
239
240//XXX: remove once classifier defined in terms of bitvecs
241pub fn bitvec_to_biguint(bv: &BitVec<u8, Msb0>) -> table::BigUintKey {
242    let mut bytes = bv.as_raw_slice().to_vec();
243
244    // Align the last bits
245    if let Some(last) = bytes.last_mut() {
246        *last >>= (8 - bv.len() % 8) % 8;
247    }
248
249    table::BigUintKey {
250        value: num::BigUint::from_bytes_le(&bytes),
251        width: bytes.len(),
252    }
253}
254
255pub fn bitvec_to_ip6addr(bv: &BitVec<u8, Msb0>) -> std::net::IpAddr {
256    let mut arr: [u8; 16] = bv.as_raw_slice().try_into().unwrap();
257    arr.reverse();
258    std::net::IpAddr::V6(std::net::Ipv6Addr::from(arr))
259}
260
261#[repr(C, align(16))]
262pub struct AlignedU128(pub u128);
263
264pub fn int_to_bitvec(x: i128) -> BitVec<u8, Msb0> {
265    //let mut bv = BitVec::<u8, Msb0>::new();
266    let mut bv = bitvec![mut u8, Msb0; 0; 128];
267    bv.store(x);
268    bv
269}
270
271pub fn bitvec_to_bitvec16(mut x: BitVec<u8, Msb0>) -> BitVec<u8, Msb0> {
272    x.resize(16, false);
273    x
274}
275
276pub fn dump_bv(x: &BitVec<u8, Msb0>) -> String {
277    if x.is_empty() {
278        "∅".into()
279    } else {
280        let v: u128 = x.load_le();
281        format!("{:x}", v)
282    }
283}
284
285pub fn extract_exact_key(
286    keyset_data: &[u8],
287    offset: usize,
288    len: usize,
289) -> table::Key {
290    table::Key::Exact(table::BigUintKey {
291        value: num::BigUint::from_bytes_le(&keyset_data[offset..offset + len]),
292        width: len,
293    })
294}
295
296pub fn extract_range_key(
297    keyset_data: &[u8],
298    offset: usize,
299    len: usize,
300) -> table::Key {
301    table::Key::Range(
302        table::BigUintKey {
303            value: num::BigUint::from_bytes_le(
304                &keyset_data[offset..offset + len],
305            ),
306            width: len,
307        },
308        table::BigUintKey {
309            value: num::BigUint::from_bytes_le(
310                &keyset_data[offset + len..offset + len + len],
311            ),
312            width: len,
313        },
314    )
315}
316
317/// Extract a ternary key from the provided keyset data. Ternary keys come in
318/// two parts. The first part is a leading bit that indicates whether we care
319/// about the value. If that leading bit is non-zero, the trailing bits of the
320/// key are interpreted as a binary value. If the leading bit is zero, the
321/// trailing bits are ignored and a Ternary::DontCare key is returned.
322pub fn extract_ternary_key(
323    keyset_data: &[u8],
324    offset: usize,
325    len: usize,
326) -> table::Key {
327    let care = keyset_data[offset];
328    if care != 0 {
329        table::Key::Ternary(table::Ternary::Value(table::BigUintKey {
330            value: num::BigUint::from_bytes_le(
331                &keyset_data[offset + 1..offset + 1 + len],
332            ),
333            width: len,
334        }))
335    } else {
336        table::Key::Ternary(table::Ternary::DontCare)
337    }
338}
339
340pub fn extract_lpm_key(
341    keyset_data: &[u8],
342    offset: usize,
343    len: usize,
344) -> table::Key {
345    let (addr, len) = match len {
346        // IPv4
347        4 => {
348            let data: [u8; 4] =
349                keyset_data[offset..offset + 4].try_into().unwrap();
350            (IpAddr::from(data), keyset_data[offset + 4])
351        }
352        // IPv6
353        16 => {
354            let data: [u8; 16] =
355                keyset_data[offset..offset + 16].try_into().unwrap();
356            (IpAddr::from(data), keyset_data[offset + 16])
357        }
358        x => {
359            panic!("lpm: data len must be 4 (ipv4) or 16 (ipv6) found {}", x);
360        }
361    };
362
363    table::Key::Lpm(table::Prefix { addr, len })
364}
365
366pub fn extract_bool_action_parameter(
367    parameter_data: &[u8],
368    offset: usize,
369) -> bool {
370    parameter_data[offset] == 1
371}
372
373pub fn extract_bit_action_parameter(
374    parameter_data: &[u8],
375    offset: usize,
376    size: usize,
377) -> BitVec<u8, Msb0> {
378    let mut byte_size = size >> 3;
379    if !size.is_multiple_of(8) {
380        byte_size += 1;
381    }
382    let mut b: BitVec<u8, Msb0> =
383        BitVec::from_slice(&parameter_data[offset..offset + byte_size]);
384    b.resize(size, false);
385    b
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use num::bigint::BigUint;
392
393    /// Checks [`bitvec_to_biguint`] is semantically equivalent to `load_le`
394    /// even with non-byte-aligned widths.
395    #[test]
396    fn bitvec_to_biguint_non_byte_aligned() {
397        for width in 1..=16 {
398            let mut bv = bitvec![u8, Msb0; 0; width];
399            bv.store_le(0xbeefu16);
400
401            assert_eq!(
402                bitvec_to_biguint(&bv).value,
403                BigUint::from(bv.load_le::<u16>()),
404            );
405        }
406    }
407}