rustyfit 0.9.0

The #![no_std] Rust implementation of The Flexible and Interoperable Data Transfer (FIT) Protocol for decoding and encoding Garmin FIT files, supporting FIT Protocol V2.
Documentation
use crate::{
    profile::{lookup, typedef::MesgNum},
    proto::Value,
};

pub(super) struct Accumulator {
    /// Value's unique identifier is a combination of `mesg_num` and `field_num`.
    /// Only messages defined in the `Profile.xlsx` can be accumulated, so max capacity
    /// can be calculated. Since it's relatively small, Array is used to avoid allocation.
    values: [AccuValue; lookup::TOTAL_ACCUMULATE],
    len: usize,
}

impl Accumulator {
    pub(super) const fn new() -> Self {
        Self {
            values: [const {
                AccuValue {
                    mesg_num: MesgNum(0),
                    field_num: 0,
                    value: 0,
                    last: 0,
                }
            }; lookup::TOTAL_ACCUMULATE],
            len: 0,
        }
    }

    pub(super) fn collect(&mut self, mesg_num: MesgNum, field_num: u8, value: &Value) {
        match value {
            Value::Uint8(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Int8(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Uint16(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Int16(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Uint32(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Int32(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Float32(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Float64(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Int64(v) => self.collect_u64(mesg_num, field_num, *v as u64),
            Value::Uint64(v) => self.collect_u64(mesg_num, field_num, *v),
            Value::VecInt8(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecUint8(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecInt16(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecUint16(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecInt32(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecUint32(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecFloat32(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecFloat64(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecInt64(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x as u64);
                }
            }
            Value::VecUint64(v) => {
                if let Some(&x) = v.last() {
                    self.collect_u64(mesg_num, field_num, x);
                }
            }
            _ => {}
        }
    }

    fn collect_u64(&mut self, mesg_num: MesgNum, field_num: u8, value: u64) {
        if let Some(v) = self
            .values
            .iter_mut()
            .take(self.len)
            .find(|v| v.mesg_num == mesg_num && v.field_num == field_num)
        {
            v.value = value;
            v.last = value;
            return;
        }
        self.values[self.len] = AccuValue {
            mesg_num,
            field_num,
            value,
            last: value,
        };
        self.len += 1;
    }

    pub(super) fn accumulate(
        &mut self,
        mesg_num: MesgNum,
        field_num: u8,
        value: u64,
        bits: u8,
    ) -> u64 {
        if let Some(v) = self
            .values
            .iter_mut()
            .take(self.len)
            .find(|v| v.mesg_num == mesg_num && v.field_num == field_num)
        {
            let mask: u64 = (1 << bits) - 1;
            v.value += (value.wrapping_sub(v.last)) & mask;
            v.last = value;
            return v.value;
        }
        self.values[self.len] = AccuValue {
            mesg_num,
            field_num,
            value,
            last: value,
        };
        self.len += 1;
        value
    }

    pub(super) fn reset(&mut self) {
        self.len = 0;
    }
}

#[cfg_attr(test, derive(PartialEq, Debug))]
struct AccuValue {
    mesg_num: MesgNum,
    field_num: u8,
    value: u64,
    last: u64,
}

#[cfg(test)]
mod tests {
    use crate::{
        decoder::{Accumulator, accumulator::AccuValue},
        profile::typedef::MesgNum,
        proto::Value,
    };
    use alloc::{string::String, vec};

    #[test]
    fn test_collect() {
        let expected = &[AccuValue {
            mesg_num: MesgNum(1),
            field_num: 1,
            value: 2,
            last: 2,
        }];

        let tt = [
            Value::Int8(2),
            Value::Uint8(2),
            Value::Int16(2),
            Value::Uint16(2),
            Value::Int32(2),
            Value::Uint32(2),
            Value::Float32(2.0),
            Value::Float64(2.0),
            Value::Int64(2),
            Value::Uint64(2),
            Value::VecInt8(vec![1, 2]),
            Value::VecUint8(vec![1, 2]),
            Value::VecInt16(vec![1, 2]),
            Value::VecUint16(vec![1, 2]),
            Value::VecInt32(vec![1, 2]),
            Value::VecUint32(vec![1, 2]),
            Value::VecFloat32(vec![1.0, 2.0]),
            Value::VecFloat64(vec![1.0, 2.0]),
            Value::VecInt64(vec![1, 2]),
            Value::VecUint64(vec![1, 2]),
        ];

        for tc in tt {
            let mut accumu = Accumulator::new();
            accumu.collect(MesgNum(1), 1, &tc);
            assert_eq!(expected, &accumu.values[..accumu.len], "case: {:?}", tc);
        }

        let tt = [
            Value::Invalid,
            Value::String(String::new()),
            Value::VecString(vec![]),
        ];

        for tc in tt {
            let mut accumu = Accumulator::new();
            accumu.collect(MesgNum(1), 1, &tc);
            assert!(accumu.values[..accumu.len].is_empty(), "case: {:?}", tc);
        }
    }

    #[test]
    fn test_collect_64() {
        let mut accumu = Accumulator::new();

        accumu.collect_u64(MesgNum(0), 0, 10);
        assert_eq!(
            &[AccuValue {
                mesg_num: MesgNum(0),
                field_num: 0,
                value: 10,
                last: 10
            }],
            &accumu.values[..accumu.len],
        );

        accumu.collect_u64(MesgNum(0), 0, 11);
        assert_eq!(
            &[AccuValue {
                mesg_num: MesgNum(0),
                field_num: 0,
                value: 11,
                last: 11
            }],
            &accumu.values[..accumu.len],
        );

        accumu.collect_u64(MesgNum(0), 1, 11);
        assert_eq!(
            &[
                AccuValue {
                    mesg_num: MesgNum(0),
                    field_num: 0,
                    value: 11,
                    last: 11
                },
                AccuValue {
                    mesg_num: MesgNum(0),
                    field_num: 1,
                    value: 11,
                    last: 11
                }
            ],
            &accumu.values[..accumu.len],
        );
    }

    #[test]
    fn test_accumulate() {
        let mut accumu = Accumulator::new();

        accumu.collect_u64(MesgNum(1), 1, 1);
        let val = accumu.accumulate(MesgNum(2), 2, 2, 8);
        assert_eq!(2, val, "accumulate non-existing value");

        let val = accumu.accumulate(MesgNum(1), 1, 10, 8);
        assert_eq!(10, val, "accumulate first value");
        assert_eq!(
            &[
                AccuValue {
                    mesg_num: MesgNum(1),
                    field_num: 1,
                    value: 10,
                    last: 10,
                },
                AccuValue {
                    mesg_num: MesgNum(2),
                    field_num: 2,
                    value: 2,
                    last: 2,
                }
            ],
            &accumu.values[..accumu.len],
            "first value is updated, non-existing value is appended"
        );
    }

    #[test]
    fn reset() {
        let mut accumu = Accumulator::new();
        accumu.collect_u64(MesgNum(1), 1, 1);
        assert_eq!(1, accumu.len);
        accumu.reset();
        assert_eq!(0, accumu.len);
    }
}