diff --git a/src/iface/mod.rs b/src/iface/mod.rs index 02c982f6..710587ae 100644 --- a/src/iface/mod.rs +++ b/src/iface/mod.rs @@ -10,6 +10,8 @@ mod interface; #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] mod neighbor; mod route; +#[cfg(feature = "proto-rpl")] +mod rpl; mod socket_meta; mod socket_set; diff --git a/src/iface/rpl/consts.rs b/src/iface/rpl/consts.rs new file mode 100644 index 00000000..70cc2acb --- /dev/null +++ b/src/iface/rpl/consts.rs @@ -0,0 +1,6 @@ +pub const SEQUENCE_WINDOW: u8 = 16; + +pub const DEFAULT_DIO_INTERVAL_MIN: u32 = 12; +pub const DEFAULT_DIO_REDUNDANCY_CONSTANT: usize = 10; +/// This is 20 in the standard, but in Contiki they use: +pub const DEFAULT_DIO_INTERVAL_DOUBLINGS: u32 = 8; diff --git a/src/iface/rpl/lollipop.rs b/src/iface/rpl/lollipop.rs new file mode 100644 index 00000000..4785c772 --- /dev/null +++ b/src/iface/rpl/lollipop.rs @@ -0,0 +1,189 @@ +//! Implementation of sequence counters defined in [RFC 6550 § 7.2]. Values from 128 and greater +//! are used as a linear sequence to indicate a restart and bootstrap the counter. Values less than +//! or equal to 127 are used as a circular sequence number space of size 128. When operating in the +//! circular region, if sequence numbers are detected to be too far apart, then they are not +//! comparable. +//! +//! [RFC 6550 § 7.2]: https://datatracker.ietf.org/doc/html/rfc6550#section-7.2 + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SequenceCounter(u8); + +impl Default for SequenceCounter { + fn default() -> Self { + // RFC6550 7.2 recommends 240 (256 - SEQUENCE_WINDOW) as the initialization value of the + // counter. + Self(240) + } +} + +impl SequenceCounter { + /// Create a new sequence counter. + /// + /// Use `Self::default()` when a new sequence counter needs to be created with a value that is + /// recommended in RFC6550 7.2, being 240. + pub fn new(value: u8) -> Self { + Self(value) + } + + /// Return the value of the sequence counter. + pub fn value(&self) -> u8 { + self.0 + } + + /// Increment the sequence counter. + /// + /// When the sequence counter is greater than or equal to 128, the maximum value is 255. + /// When the sequence counter is less than 128, the maximum value is 127. + /// + /// When an increment of the sequence counter would cause the counter to increment beyond its + /// maximum value, the counter MUST wrap back to zero. + pub fn increment(&mut self) { + let max = if self.0 >= 128 { 255 } else { 127 }; + + self.0 = match self.0.checked_add(1) { + Some(val) if val <= max => val, + _ => 0, + }; + } +} + +impl PartialEq for SequenceCounter { + fn eq(&self, other: &Self) -> bool { + let a = self.value() as usize; + let b = other.value() as usize; + + if ((128..=255).contains(&a) && (0..=127).contains(&b)) + || ((128..=255).contains(&b) && (0..=127).contains(&a)) + { + false + } else { + let result = if a > b { a - b } else { b - a }; + + if result <= super::consts::SEQUENCE_WINDOW as usize { + // RFC1982 + a == b + } else { + // This case is actually not comparable. + false + } + } + } +} + +impl PartialOrd for SequenceCounter { + fn partial_cmp(&self, other: &Self) -> Option { + use super::consts::SEQUENCE_WINDOW; + use core::cmp::Ordering; + + let a = self.value() as usize; + let b = other.value() as usize; + + if (128..256).contains(&a) && (0..128).contains(&b) { + if 256 + b - a <= SEQUENCE_WINDOW as usize { + Some(Ordering::Less) + } else { + Some(Ordering::Greater) + } + } else if (128..256).contains(&b) && (0..128).contains(&a) { + if 256 + a - b <= SEQUENCE_WINDOW as usize { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + } + } else if ((0..128).contains(&a) && (0..128).contains(&b)) + || ((128..256).contains(&a) && (128..256).contains(&b)) + { + let result = if a > b { a - b } else { b - a }; + + if result <= SEQUENCE_WINDOW as usize { + // RFC1982 + a.partial_cmp(&b) + } else { + // This case is not comparable. + None + } + } else { + unreachable!(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sequence_counter_increment() { + let mut seq = SequenceCounter::new(253); + seq.increment(); + assert_eq!(seq.value(), 254); + seq.increment(); + assert_eq!(seq.value(), 255); + seq.increment(); + assert_eq!(seq.value(), 0); + + let mut seq = SequenceCounter::new(126); + seq.increment(); + assert_eq!(seq.value(), 127); + seq.increment(); + assert_eq!(seq.value(), 0); + } + + #[test] + fn sequence_counter_comparison() { + use core::cmp::Ordering; + + assert!(SequenceCounter::new(240) != SequenceCounter::new(1)); + assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); + assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); + assert!(SequenceCounter::new(240) == SequenceCounter::new(240)); + assert!(SequenceCounter::new(240 - 17) != SequenceCounter::new(240)); + + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(5)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(250).partial_cmp(&SequenceCounter::new(5)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(5).partial_cmp(&SequenceCounter::new(250)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(127).partial_cmp(&SequenceCounter::new(129)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(121)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(121).partial_cmp(&SequenceCounter::new(120)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(241)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(241).partial_cmp(&SequenceCounter::new(240)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(120)), + Some(Ordering::Equal) + ); + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(240)), + Some(Ordering::Equal) + ); + assert_eq!( + SequenceCounter::new(130).partial_cmp(&SequenceCounter::new(241)), + None + ); + } +} diff --git a/src/iface/rpl/mod.rs b/src/iface/rpl/mod.rs new file mode 100644 index 00000000..a9f02a30 --- /dev/null +++ b/src/iface/rpl/mod.rs @@ -0,0 +1,5 @@ +#![allow(unused)] + +mod consts; +mod lollipop; +mod trickle; diff --git a/src/iface/rpl/trickle.rs b/src/iface/rpl/trickle.rs new file mode 100644 index 00000000..e60cad10 --- /dev/null +++ b/src/iface/rpl/trickle.rs @@ -0,0 +1,266 @@ +//! Implementation of the Trickle timer defined in [RFC 6206]. The algorithm allows node in a lossy +//! shared medium to exchange information in a highly robust, energy efficient, simple, and +//! scalable manner. Dynamicaly adjusting transmission windows allows Trickle to spread new +//! information fast while sending only a few messages per hour when information does not change. +//! +//! **NOTE**: the constants used for the default Trickle timer are the ones from the [Enhanced +//! Trickle]. +//! +//! [RFC 6206]: https://datatracker.ietf.org/doc/html/rfc6206 +//! [Enhanced Trickle]: https://d1wqtxts1xzle7.cloudfront.net/71402623/E-Trickle_Enhanced_Trickle_Algorithm_for20211005-2078-1ckh34a.pdf?1633439582=&response-content-disposition=inline%3B+filename%3DE_Trickle_Enhanced_Trickle_Algorithm_for.pdf&Expires=1681472005&Signature=cC7l-Pyr5r64XBNCDeSJ2ha6oqWUtO6A-KlDOyC0UVaHxDV3h3FuVHRtcNp3O9BUfRK8jeuWCYGBkCZgQT4Zgb6XwgVB-3z4TF9o3qBRMteRyYO5vjVkpPBeN7mz4Tl746SsSCHDm2NMtr7UVtLYamriU3D0rryoqLqJXmnkNoJpn~~wJe2H5PmPgIwixTwSvDkfFLSVoESaYS9ZWHZwbW-7G7OxIw8oSYhx9xMBnzkpdmT7sJNmvDzTUhoOjYrHTRM23cLVS9~oOSpT7hKtKD4h5CSmrNK4st07KnT9~tUqEcvGO3aXdd4quRZeKUcCkCbTLvhOEYg9~QqgD8xwhA__&Key-Pair-Id=APKAJLOHF5GGSLRBV4ZA + +use crate::{ + rand::Rand, + time::{Duration, Instant}, +}; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct TrickleTimer { + i_min: u32, + i_max: u32, + k: usize, + + i: Duration, + t: Duration, + t_exp: Instant, + i_exp: Instant, + counter: usize, +} + +impl TrickleTimer { + /// Creat a new Trickle timer using the default values. + /// + /// **NOTE**: the standard defines I as a random value between [Imin, Imax]. However, this + /// could result in a t value that is very close to Imax. Therefore, sending DIO messages will + /// be sporadic, which is not ideal when a network is started. It might take a long time before + /// the network is actually stable. Therefore, we don't draw a random numberm but just use Imin + /// for I. This only affects the start of the RPL tree and speeds up building it. Also, we + /// don't use the default values from the standard, but the values from the _Enhanced Trickle + /// Algorithm for Low-Power and Lossy Networks_ from Baraq Ghaleb et al. This is also what the + /// Contiki Trickle timer does. + pub(crate) fn default(now: Instant, rand: &mut Rand) -> Self { + use super::consts::{ + DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_INTERVAL_MIN, + DEFAULT_DIO_REDUNDANCY_CONSTANT, + }; + + Self::new( + DEFAULT_DIO_INTERVAL_MIN, + DEFAULT_DIO_INTERVAL_MIN + DEFAULT_DIO_INTERVAL_DOUBLINGS, + DEFAULT_DIO_REDUNDANCY_CONSTANT, + now, + rand, + ) + } + + /// Create a new Trickle timer. + pub(crate) fn new(i_min: u32, i_max: u32, k: usize, now: Instant, rand: &mut Rand) -> Self { + let mut timer = Self { + i_min, + i_max, + k, + i: Duration::ZERO, + t: Duration::ZERO, + t_exp: Instant::ZERO, + i_exp: Instant::ZERO, + counter: 0, + }; + + timer.i = Duration::from_millis(2u32.pow(timer.i_min) as u64); + timer.i_exp = now + timer.i; + timer.counter = 0; + + timer.set_t(now, rand); + + timer + } + + /// Poll the Trickle timer. Returns `true` when the Trickle timer singals that a message can be + /// transmitted. This happens when the Trickle timer expires. + pub(crate) fn poll(&mut self, now: Instant, rand: &mut Rand) -> bool { + let can_transmit = self.can_transmit() && self.t_expired(now); + + if can_transmit { + self.set_t(now, rand); + } + + if self.i_expired(now) { + self.expire(now, rand); + } + + can_transmit + } + + /// Returns the Instant at which the Trickle timer should be polled again. Polling the Trickle + /// timer before this Instant is not harmfull, however, polling after it is not correct. + pub(crate) fn poll_at(&self) -> Instant { + self.t_exp.min(self.i_exp) + } + + /// Signal the Trickle timer that a consistency has been heard, and thus increasing it's + /// counter. + pub(crate) fn hear_consistent(&mut self) { + self.counter += 1; + } + + /// Signal the Trickle timer that an inconsistency has been heard. This resets the Trickle + /// timer when the current interval is not the smallest possible. + pub(crate) fn hear_inconsistency(&mut self, now: Instant, rand: &mut Rand) { + let i = Duration::from_millis(2u32.pow(self.i_min) as u64); + if self.i > i { + self.reset(i, now, rand); + } + } + + /// Check if the Trickle timer can transmit or not. Returns `false` when the consistency + /// counter is bigger or equal to the default consistency constant. + pub(crate) fn can_transmit(&self) -> bool { + self.k != 0 && self.counter < self.k + } + + /// Reset the Trickle timer when the interval has expired. + fn expire(&mut self, now: Instant, rand: &mut Rand) { + let max_interval = Duration::from_millis(2u32.pow(self.i_max) as u64); + let i = if self.i >= max_interval { + max_interval + } else { + self.i + self.i + }; + + self.reset(i, now, rand); + } + + pub(crate) fn reset(&mut self, i: Duration, now: Instant, rand: &mut Rand) { + self.i = i; + self.i_exp = now + self.i; + self.counter = 0; + self.set_t(now, rand); + } + + pub(crate) const fn max_expiration(&self) -> Duration { + Duration::from_millis(2u32.pow(self.i_max) as u64) + } + + pub(crate) const fn min_expiration(&self) -> Duration { + Duration::from_millis(2u32.pow(self.i_min) as u64) + } + + fn set_t(&mut self, now: Instant, rand: &mut Rand) { + let t = Duration::from_micros( + self.i.total_micros() / 2 + + (rand.rand_u32() as u64 + % (self.i.total_micros() - self.i.total_micros() / 2 + 1)), + ); + + self.t = t; + self.t_exp = now + t; + } + + fn t_expired(&self, now: Instant) -> bool { + now >= self.t_exp + } + + fn i_expired(&self, now: Instant) -> bool { + now >= self.i_exp + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trickle_timer_intervals() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + let mut previous_i = trickle.i; + + while now <= Instant::from_secs(100_000) { + trickle.poll(now, &mut rand); + + if now < Instant::ZERO + trickle.max_expiration() { + // t should always be inbetween I/2 and I. + assert!(trickle.i / 2 < trickle.t); + assert!(trickle.i > trickle.t); + } + + if previous_i != trickle.i { + // When a new Interval is selected, this should be double the previous one. + assert_eq!(previous_i * 2, trickle.i); + assert_eq!(trickle.counter, 0); + previous_i = trickle.i; + } + + now += Duration::from_millis(100); + } + } + + #[test] + fn trickle_timer_hear_inconsistency() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + trickle.counter = 1; + + while now <= Instant::from_secs(10_000) { + trickle.poll(now, &mut rand); + + if now < trickle.i_exp && now < Instant::ZERO + trickle.min_expiration() { + assert_eq!(trickle.counter, 1); + } else { + // The first interval expired, so the conter is reset. + assert_eq!(trickle.counter, 0); + } + + if now == Instant::from_secs(10) { + // We set the counter to 1 such that we can test the `hear_inconsistency`. + trickle.counter = 1; + + assert_eq!(trickle.counter, 1); + + trickle.hear_inconsistency(now, &mut rand); + + assert_eq!(trickle.counter, 0); + assert_eq!(trickle.i, trickle.min_expiration()); + } + + now += Duration::from_millis(100); + } + } + + #[test] + fn trickle_timer_hear_consistency() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + trickle.counter = 1; + + let mut transmit_counter = 0; + + while now <= Instant::from_secs(10_000) { + trickle.hear_consistent(); + + if trickle.poll(now, &mut rand) { + transmit_counter += 1; + } + + if now == Instant::from_secs(10_000) { + use super::super::consts::{ + DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_REDUNDANCY_CONSTANT, + }; + assert!(!trickle.poll(now, &mut rand)); + assert!(trickle.counter > DEFAULT_DIO_REDUNDANCY_CONSTANT); + // We should never have transmitted since the counter was higher than the default + // redundancy constant. + assert_eq!(transmit_counter, 0); + } + + now += Duration::from_millis(100); + } + } +}