From edb7ecf1c82d3a58dcf153d74e02b62d54f3acaf Mon Sep 17 00:00:00 2001 From: Colin S <3526918+cbs228@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:14:59 -0500 Subject: [PATCH 1/3] sameold: remove slice conversion from Window `SliceRingBuffer` is unsound and has multiple security advisories [1] [2]. There has been no apparent progress on fixes since May 2025 [3]. The crate's large volume of interdependent `unsafe` blocks will likely make it very tricky to maintain. While a memory-mapped deque is a very cool idea, it is time to move on for now. Let's replace `SliceRingBuffer` with a more traditional double-ended queue. These queues don't covert to slice, but we don't really *need* a slice. At present, slice is mostly used to align the end of `Window` (with the most recent samples) to the filter taps. We can do that just as easily with a `DoubleEndedIterator`. * Make `Window` convertible to an iterator of numbers, via `IntoIterator`. Replace all uses of slices with iterators. * Remove slice conversions and accessors from Window. They're still technically accessible via `inner()`, but nothing uses them. [1]: https://rustsec.org/advisories/RUSTSEC-2025-0044 [2]: https://rustsec.org/advisories/RUSTSEC-2020-0158 [3]: https://github.com/LiquidityC/slice_ring_buffer/issues/12 --- crates/sameold/src/receiver/dcblock.rs | 4 +- crates/sameold/src/receiver/demod.rs | 5 +- crates/sameold/src/receiver/equalize.rs | 35 +++++---- crates/sameold/src/receiver/filter.rs | 95 ++++++++++++++----------- 4 files changed, 77 insertions(+), 62 deletions(-) diff --git a/crates/sameold/src/receiver/dcblock.rs b/crates/sameold/src/receiver/dcblock.rs index f572cec..302d6b2 100644 --- a/crates/sameold/src/receiver/dcblock.rs +++ b/crates/sameold/src/receiver/dcblock.rs @@ -168,7 +168,7 @@ mod tests { output_history.push_scalar(uut.filter(100.0f32 + clk)); clk = -1.0 * clk; } - assert_approx_eq!(output_history.as_slice()[0], 1.0f32, 1.0e-2); - assert_approx_eq!(output_history.as_slice()[1], -1.0f32, 1.0e-2); + assert_approx_eq!(output_history.inner()[0], 1.0f32, 1.0e-2); + assert_approx_eq!(output_history.inner()[1], -1.0f32, 1.0e-2); } } diff --git a/crates/sameold/src/receiver/demod.rs b/crates/sameold/src/receiver/demod.rs index 3fe717f..4825ca8 100644 --- a/crates/sameold/src/receiver/demod.rs +++ b/crates/sameold/src/receiver/demod.rs @@ -155,9 +155,8 @@ impl FskDemod { // * `< 0` for space fn demod_now(&self) -> f32 { // matched filter - let window = self.window_input.as_slice(); - let mark = self.coeff_mark.filter(window); - let space = self.coeff_space.filter(window); + let mark = self.coeff_mark.filter(&self.window_input); + let space = self.coeff_space.filter(&self.window_input); // non-coherently sum matched filter powers to obtain // the symbol estimate diff --git a/crates/sameold/src/receiver/equalize.rs b/crates/sameold/src/receiver/equalize.rs index 9c7d128..8a529b0 100644 --- a/crates/sameold/src/receiver/equalize.rs +++ b/crates/sameold/src/receiver/equalize.rs @@ -133,6 +133,9 @@ impl Equalizer { let feedforward_wind = Window::new(nfeedforward); let feedback_wind = Window::new(nfeedback); + assert_eq!(feedforward_coeff.inner().len(), feedforward_wind.len()); + assert_eq!(feedback_coeff.inner().len(), feedback_wind.len()); + Self { relaxation, regularization, @@ -314,14 +317,16 @@ impl Equalizer { self.relaxation, self.regularization, error, - self.feedforward_wind.as_ref(), + &self.feedforward_wind, self.feedforward_coeff.as_mut(), ); + + assert_eq!(self.feedback_wind.inner().len(), self.feedback_coeff.len()); nlms_update( self.relaxation, self.regularization, -error, - self.feedback_wind.as_ref(), + &self.feedback_wind, self.feedback_coeff.as_mut(), ); } @@ -346,17 +351,14 @@ enum EqualizerState { // variable gain. The gain is computed based on the power // of the input. The given `filter` taps are updated based // on the input samples in `window`. -fn nlms_update( - relaxation: f32, - regularization: f32, - error: f32, - window: &[f32], - filter: &mut [f32], -) { - assert_eq!(window.len(), filter.len()); - - let gain = nlms_gain(relaxation, regularization, window); - for (coeff, data) in filter.iter_mut().zip(window.iter()) { +fn nlms_update(relaxation: f32, regularization: f32, error: f32, window: W, filter: &mut [f32]) +where + W: IntoIterator, + W::IntoIter: DoubleEndedIterator + Clone, +{ + let window = window.into_iter(); + let gain = nlms_gain(relaxation, regularization, window.clone()); + for (coeff, data) in filter.iter_mut().zip(window) { *coeff += gain * error * data; } } @@ -371,7 +373,10 @@ fn nlms_update( // // where `norm(x, 2)` is the L² norm of `x` #[inline] -fn nlms_gain(relaxation: f32, regularization: f32, window: &[f32]) -> f32 { +fn nlms_gain<'a, W>(relaxation: f32, regularization: f32, window: W) -> f32 +where + W: IntoIterator, +{ let mut sumsq = 0.0f32; for w in window { sumsq += w * w; @@ -473,7 +478,7 @@ mod tests { RELAXATION, REGULARIZATION, err, - inverse_wind.as_ref(), + &inverse_wind, inverse_coeff.as_mut(), ); } diff --git a/crates/sameold/src/receiver/filter.rs b/crates/sameold/src/receiver/filter.rs index 1477509..cee8c77 100644 --- a/crates/sameold/src/receiver/filter.rs +++ b/crates/sameold/src/receiver/filter.rs @@ -116,25 +116,26 @@ where self.0.len() } - /// Perform FIR filtering with the given sample history slice + /// Perform FIR filtering with the given sample history /// /// Computes the current output sample of the filter assuming - /// the given `history`. `history` should be a slice where - /// `history[N-1]` is the most recent sample and `history[0]` - /// is the least recent/oldest sample. + /// the given `history`. `history` must be a + /// `DoubledEndedIterator` which outputs the oldest sample + /// first and the newest sample last. The newest sample is + /// used for feedforward lag 0. `history` SHOULD contain at + /// least `self.len()` samples, but no error is raised if it + /// is shorter. /// - /// The caller should maintain a deque of history. New samples - /// should be pushed to the end of the queue, and old samples - /// should age off the head of the queue. The queue *should* - /// contain `self.len()` samples, but it is not an error if - /// it contains more or less. - pub fn filter(&self, history: I) -> Out + /// The caller is encouraged to use a deque for `history`, + /// but this is not enforced. + pub fn filter(&self, history: W) -> Out where - I: AsRef<[In]>, + W: IntoIterator, + W::IntoIter: DoubleEndedIterator, In: Copy + Scalar + std::ops::Mul, Out: Copy + Scalar + Zero + std::ops::AddAssign, { - multiply_accumulate(history.as_ref(), self.as_ref()) + multiply_accumulate(history, self.as_ref()) } /// Reset to identity filter @@ -275,7 +276,7 @@ where /// /// Appends the `input` slice to the right side of the Window. /// The last sample of `input` becomes the right-most / most recent - /// sample of the Window slice. + /// sample of the Window. /// /// If the length of `input` exceeds the length of the Window, /// then the right-most chunk of `input` will be taken. @@ -301,8 +302,8 @@ where /// Append a scalar to the sample window /// /// Appends the `input` scalar to the right side of the Window. - /// It becomes the last / most recent sample of the Window - /// slice. Returns the sample that was formerly the oldest + /// It becomes the last / most recent sample of Window. + /// Returns the sample that was formerly the oldest /// sample in the Window. #[inline] pub fn push_scalar(&mut self, input: T) -> T { @@ -311,19 +312,26 @@ where out } - /// Obtain the inner SliceRingBuffer - pub fn inner(&self) -> &SliceRingBuffer { - &self.0 + /// Iterator over window contents + /// + /// The iterator outputs the least recent sample first. + /// The most recent sample is output last. + pub fn iter(&self) -> <&Window as IntoIterator>::IntoIter { + self.into_iter() } - /// Obtain current window contents, as a slice + /// Convert window contents to a vector /// - /// The zeroth sample of the slice is the least recent - /// sample in the window. The last sample of the slice - /// is the most recent sample in the window. - #[inline] - pub fn as_slice(&self) -> &[T] { - self.0.as_slice() + /// Copy the current contents of the window to a freshly-allocated + /// vector. Vector elements are ordered from least recent sample + /// to most recent sample. + pub fn to_vec(&self) -> Vec { + self.iter().collect() + } + + /// Obtain the inner SliceRingBuffer + pub fn inner(&self) -> &SliceRingBuffer { + &self.0 } /// Most recent element pushed into the Window @@ -339,12 +347,16 @@ where } } -impl AsRef<[T]> for Window +impl<'a, T> IntoIterator for &'a Window where T: Copy + Scalar + Zero, { - fn as_ref(&self) -> &[T] { - self.as_slice() + type Item = T; + + type IntoIter = std::iter::Copied>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().into_iter().copied() } } @@ -363,7 +375,7 @@ where // stored *reversed* in `rev_coeff`, with `rev_coeff[N-1]` being // the zeroth filter coefficient. // -// The two slices need not be the same length. If `history` is shorter +// The two inputs need not be the same length. If `history` is shorter // than `rev_coeff`, then the sample history is assumed to be zero // outside of its range. // @@ -373,19 +385,18 @@ where // // The output value is returned. Any compatible arithmetic types may // be used, including complex numbers. -fn multiply_accumulate(history: &[In], rev_coeff: &[Coeff]) -> Out +fn multiply_accumulate(history: W, rev_coeff: &[Coeff]) -> Out where + W: IntoIterator, + W::IntoIter: DoubleEndedIterator, In: Copy + Scalar + std::ops::Mul, Coeff: Copy + Scalar, Out: Copy + Scalar + Zero + std::ops::AddAssign, { - let mul_len = usize::min(history.len(), rev_coeff.len()); - let history = &history[history.len() - mul_len..]; - let rev_coeff = &rev_coeff[rev_coeff.len() - mul_len..]; - + let history = history.into_iter(); let mut out = Out::zero(); - for (hi, co) in history.iter().zip(rev_coeff.iter()) { - out += *hi * *co; + for (hi, co) in history.rev().zip(rev_coeff.iter().rev()) { + out += hi * *co; } out } @@ -446,15 +457,15 @@ mod tests { fn test_window() { let mut wind: Window = Window::new(4); assert_eq!(4, wind.len()); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec()); wind.push(&[1.0f32]); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.to_vec()); wind.push(&[2.0f32]); - assert_eq!(&[0.0f32, 0.0f32, 1.0f32, 2.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 1.0f32, 2.0f32], wind.to_vec()); wind.push(&[-1.0f32, -2.0f32, 1.0f32, 2.0f32, 3.0f32, 4.0f32]); - assert_eq!(&[1.0f32, 2.0f32, 3.0f32, 4.0f32], wind.as_slice()); + assert_eq!(vec![1.0f32, 2.0f32, 3.0f32, 4.0f32], wind.to_vec()); assert_eq!(4.0f32, wind.back()); assert_eq!(1.0f32, wind.front()); assert_eq!(4, wind.len()); @@ -462,10 +473,10 @@ mod tests { // push individual samples works too assert_eq!(1.0f32, wind.push_scalar(10.0f32)); assert_eq!(4, wind.len()); - assert_eq!(&[2.0f32, 3.0f32, 4.0f32, 10.0f32], wind.as_slice()); + assert_eq!(vec![2.0f32, 3.0f32, 4.0f32, 10.0f32], wind.to_vec()); wind.reset(); assert_eq!(4, wind.len()); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec()); } } From ce1c4a717694337f09bda7a715110724d6dfc108 Mon Sep 17 00:00:00 2001 From: Colin S <3526918+cbs228@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:21:56 -0500 Subject: [PATCH 2/3] sameold: stop flipping coefficients around Previously, `FilterCoeff` were stored in reverse order, with feedforward lag 0 stored *last*. This was convenient when `Window` coerced to slice. Now that we iterate over `Window` in reverse order, reversing the filter taps does not serve us. It is also confusing: the taps are in "normal" order in `FilterCoeff::from_slice()`, but later reads via `as_slice()` show them reversed. Ick. Put filter taps in their proper order. --- crates/sameold/src/receiver/equalize.rs | 2 +- crates/sameold/src/receiver/filter.rs | 41 ++++++++----------------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/crates/sameold/src/receiver/equalize.rs b/crates/sameold/src/receiver/equalize.rs index 8a529b0..8abc8ea 100644 --- a/crates/sameold/src/receiver/equalize.rs +++ b/crates/sameold/src/receiver/equalize.rs @@ -358,7 +358,7 @@ where { let window = window.into_iter(); let gain = nlms_gain(relaxation, regularization, window.clone()); - for (coeff, data) in filter.iter_mut().zip(window) { + for (coeff, data) in filter.iter_mut().zip(window.rev()) { *coeff += gain * error * data; } } diff --git a/crates/sameold/src/receiver/filter.rs b/crates/sameold/src/receiver/filter.rs index cee8c77..e87b666 100644 --- a/crates/sameold/src/receiver/filter.rs +++ b/crates/sameold/src/receiver/filter.rs @@ -85,18 +85,12 @@ where /// Creates FIR filter coefficients with the specified impulse /// response `h`. The coefficients `h` use the same representation /// as GNU Octave's `filter()` function. - /// - /// Internally, the coefficients are stored reversed. This improves - /// performance against most types of queues for the input signal. pub fn from_slice(h: S) -> Self where S: AsRef<[T]>, { let inp = h.as_ref(); - FilterCoeff(DVector::from_iterator( - inp.len(), - inp.iter().rev().map(|d| *d), - )) + FilterCoeff(DVector::from_iterator(inp.len(), inp.iter().copied())) } /// Create an identity filter @@ -107,7 +101,7 @@ where len, std::iter::repeat(T::zero()).take(len), )); - out.0[len - 1] = T::one(); + out.0[0] = T::one(); out } @@ -143,42 +137,31 @@ where /// The filter coefficients are reset to a "no-op" identity /// filter. pub fn identity(&mut self) { - let len = self.0.len(); for coeff in self.0.iter_mut() { *coeff = T::zero(); } - self.0[len - 1] = T::one(); + self.0[0] = T::one(); } /// Return filter coefficients as slice - /// - /// The filter coefficients are in *reverse* order - /// from their Octave representation. #[inline] pub fn as_slice(&self) -> &[T] { self.0.as_slice() } /// Return filter coefficients as mutable slice - /// - /// The filter coefficients are in *reverse* order - /// from their Octave representation. #[inline] pub fn as_mut_slice(&mut self) -> &mut [T] { self.0.as_mut_slice() } /// Obtain filter coefficients - /// - /// The coefficients are output in reverse order. #[inline] pub fn inner(&self) -> &DVector { &self.0 } /// Obtain filter coefficients (mutable) - /// - /// The coefficients are output in reverse order. #[inline] pub fn inner_mut(&mut self) -> &mut DVector { &mut self.0 @@ -372,11 +355,11 @@ where // `history` contains the sample history. The most recent sample // is stored in `history[N-1]`, and the least recent sample in the // history is stored in `history[0]`. The filter coefficients are -// stored *reversed* in `rev_coeff`, with `rev_coeff[N-1]` being -// the zeroth filter coefficient. +// stored in `coeff`, with `coeff[0]` being the zeroth filter +// coefficient. // // The two inputs need not be the same length. If `history` is shorter -// than `rev_coeff`, then the sample history is assumed to be zero +// than `coeff`, then the sample history is assumed to be zero // outside of its range. // // To perform FIR filtering, new samples are shifted onto the end of @@ -385,7 +368,7 @@ where // // The output value is returned. Any compatible arithmetic types may // be used, including complex numbers. -fn multiply_accumulate(history: W, rev_coeff: &[Coeff]) -> Out +fn multiply_accumulate(history: W, coeff: &[Coeff]) -> Out where W: IntoIterator, W::IntoIter: DoubleEndedIterator, @@ -395,7 +378,7 @@ where { let history = history.into_iter(); let mut out = Out::zero(); - for (hi, co) in history.rev().zip(rev_coeff.iter().rev()) { + for (hi, co) in history.rev().zip(coeff.iter()) { out += hi * *co; } out @@ -418,11 +401,11 @@ mod tests { // simple multiplies; we clip to the end let out = multiply_accumulate(&[20.0f32, 1.0f32], &[1.0f32]); assert_eq!(1.0f32, out); - let out = multiply_accumulate(&[1.0f32], &[20.0f32, 1.0f32]); + let out = multiply_accumulate(&[1.0f32], &[1.0f32, 20.0f32]); assert_eq!(1.0f32, out); // more complicated multiply - let out = multiply_accumulate(&[20.0f32, 20.0f32], &[-1.0f32, 1.0f32]); + let out = multiply_accumulate(&[20.0f32, 20.0f32], &[1.0f32, -1.0f32]); assert_approx_eq!(0.0f32, out); } @@ -442,13 +425,13 @@ mod tests { #[test] fn test_filter_identity() { - const EXPECT: &[f32] = &[0.0f32, 0.0f32, 0.0f32, 1.0f32]; + const EXPECT: &[f32] = &[1.0f32, 0.0f32, 0.0f32, 0.0f32]; let mut filter = FilterCoeff::::from_identity(4); assert_eq!(EXPECT, filter.as_ref()); assert_eq!(10.0f32, filter.filter(&[10.0f32])); - filter[2] = 5.0f32; + filter[3] = 5.0f32; filter.identity(); assert_eq!(EXPECT, filter.as_ref()); } From 3d40e6578b3adf062a6c2a6964d41e1e3e8dbd5f Mon Sep 17 00:00:00 2001 From: Colin S <3526918+cbs228@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:20:56 -0500 Subject: [PATCH 3/3] sameold: drop slice-ring-buffer Replace `SliceRingBuffer` with a standard `VecDeque` in our FIR filter `Window` implementation. The crate dependency is dropped. --- Cargo.lock | 43 --------------------------- crates/sameold/Cargo.toml | 1 - crates/sameold/src/receiver/filter.rs | 40 ++++++++++++------------- 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91f43cd..1a71041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,15 +334,6 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -621,7 +612,6 @@ dependencies = [ "num-complex", "num-traits", "sameplace", - "slice-ring-buffer", ] [[package]] @@ -662,17 +652,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slice-ring-buffer" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ae312bda09b2368f79f985fdb4df4a0b5cbc75546b511303972d195f8c27d6" -dependencies = [ - "libc", - "mach2", - "winapi", -] - [[package]] name = "strsim" version = "0.11.1" @@ -834,22 +813,6 @@ dependencies = [ "safe_arch", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -859,12 +822,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.52.0" diff --git a/crates/sameold/Cargo.toml b/crates/sameold/Cargo.toml index a220bd8..f91d941 100644 --- a/crates/sameold/Cargo.toml +++ b/crates/sameold/Cargo.toml @@ -18,7 +18,6 @@ log = "0.4" nalgebra = "^0.33.2" num-complex = "^0.4" num-traits = "^0.2" -slice-ring-buffer = "^0.3" [dev-dependencies] assert_approx_eq = "1.1.0" diff --git a/crates/sameold/src/receiver/filter.rs b/crates/sameold/src/receiver/filter.rs index e87b666..336e9bc 100644 --- a/crates/sameold/src/receiver/filter.rs +++ b/crates/sameold/src/receiver/filter.rs @@ -62,12 +62,12 @@ //! wind.push(&[1.0f32, 2.0f32, 3.0f32, 4.0f32]); //! ``` +use std::collections::VecDeque; use std::convert::AsRef; use nalgebra::base::Scalar; use nalgebra::DVector; use num_traits::{One, Zero}; -use slice_ring_buffer::SliceRingBuffer; /// FIR filter coefficients #[derive(Debug, Clone, PartialEq, PartialOrd, Eq)] @@ -215,7 +215,7 @@ where /// Implements a fixed-size lookback window for FIR filters /// or other purposes. #[derive(Clone, Debug)] -pub struct Window(SliceRingBuffer) +pub struct Window(VecDeque) where T: Copy + Scalar + Zero; @@ -226,27 +226,19 @@ where { /// Create empty window, filling it with zeros /// - /// Creates a new `Window` with the given `len`gth, with - /// `len > 0`. + /// Creates a new `Window` with the given `len`gth pub fn new(len: usize) -> Self { - assert!(len > 0); - - let mut out = Self(SliceRingBuffer::with_capacity(len)); - for _i in 0..len { - out.0.push_front(T::zero()); - } - assert_eq!(len, out.0.len()); - out + let mut q = VecDeque::with_capacity(len); + q.resize(len, T::zero()); + Self(q) } /// Reset to zero initial conditions /// /// Clear the window, filling it with zeros pub fn reset(&mut self) { - let len = self.0.len(); - self.0.clear(); - for _i in 0..len { - self.0.push_front(T::zero()); + for s in &mut self.0 { + *s = T::zero() } } @@ -279,7 +271,7 @@ where std::mem::drop(self.0.drain(0..input.len())); // add new - self.0.extend_from_slice(input.as_ref()); + self.0.extend(input.as_ref()); } /// Append a scalar to the sample window @@ -290,7 +282,7 @@ where /// sample in the Window. #[inline] pub fn push_scalar(&mut self, input: T) -> T { - let out = self.0.pop_front().unwrap(); + let out = self.0.pop_front().unwrap_or(T::zero()); self.0.push_back(input); out } @@ -313,7 +305,7 @@ where } /// Obtain the inner SliceRingBuffer - pub fn inner(&self) -> &SliceRingBuffer { + pub fn inner(&self) -> &VecDeque { &self.0 } @@ -336,10 +328,10 @@ where { type Item = T; - type IntoIter = std::iter::Copied>; + type IntoIter = std::iter::Copied>; fn into_iter(self) -> Self::IntoIter { - self.0.as_slice().into_iter().copied() + self.0.iter().copied() } } @@ -443,6 +435,8 @@ mod tests { assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec()); wind.push(&[1.0f32]); assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.to_vec()); + wind.push(&[]); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.to_vec()); wind.push(&[2.0f32]); assert_eq!(vec![0.0f32, 0.0f32, 1.0f32, 2.0f32], wind.to_vec()); @@ -458,6 +452,10 @@ mod tests { assert_eq!(4, wind.len()); assert_eq!(vec![2.0f32, 3.0f32, 4.0f32, 10.0f32], wind.to_vec()); + // exactly enough to fill + wind.push(&[5.0f32, 4.0f32, 3.0f32, 2.0f32]); + assert_eq!(vec![5.0f32, 4.0f32, 3.0f32, 2.0f32], wind.to_vec()); + wind.reset(); assert_eq!(4, wind.len()); assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec());