dbc-rs 0.7.1

Database CAN (DBC) parsing and editing library
Documentation
use super::Signal;
use crate::{Error, Result};

impl Signal {
    /// Decode the signal and return both raw and physical values in a single pass.
    ///
    /// This is an optimized method for multiplexer switch decoding where both the
    /// raw integer value (for switch matching) and the physical value are needed.
    /// Avoids the overhead of extracting bits twice.
    ///
    /// # Arguments
    ///
    /// * `data` - The CAN message data bytes (up to 8 bytes for classic CAN, 64 for CAN FD)
    ///
    /// # Returns
    ///
    /// * `Ok((raw_value, physical_value))` - The raw signed integer and physical (factor+offset) value
    /// * `Err(Error)` - If the signal extends beyond the data length
    #[inline]
    pub(crate) fn decode_raw(&self, data: &[u8]) -> Result<(i64, f64)> {
        let start_bit = self.start_bit as usize;
        let length = self.length as usize;

        // Bound against the signal's true physical span (byte-order aware) so
        // big-endian signals cannot read past the end of a short payload.
        if self.required_len() > data.len() {
            return Err(Error::Decoding(Error::SIGNAL_EXTENDS_BEYOND_DATA));
        }

        let raw_bits = self.byte_order.extract_bits(data, start_bit, length);

        let raw_value = if self.unsigned || length >= 64 {
            // For unsigned, or a full-width 64-bit signal, reinterpret the bits
            // directly. `1u64 << 64` would be undefined, so never take the mask path.
            raw_bits as i64
        } else {
            let sign_bit_mask = 1u64 << (length - 1);
            if (raw_bits & sign_bit_mask) != 0 {
                let mask = !((1u64 << length) - 1);
                (raw_bits | mask) as i64
            } else {
                raw_bits as i64
            }
        };

        // Compute the physical value from the correct integer domain: a 64-bit
        // unsigned value with the MSB set must not be treated as a negative i64.
        let physical_value = if self.unsigned {
            (raw_bits as f64) * self.factor + self.offset
        } else {
            (raw_value as f64) * self.factor + self.offset
        };
        Ok((raw_value, physical_value))
    }
}

#[cfg(test)]
mod tests {
    use super::Signal;
    use crate::Parser;

    #[test]
    fn test_decode_little_endian() {
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ TestSignal : 0|16@1+ (1,0) [0|65535] \"\"").unwrap(),
        )
        .unwrap();
        // Test value 0x0102 = 258: little-endian bytes are [0x02, 0x01]
        let data = [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        assert_eq!(value, 258.0);
    }

    #[test]
    fn test_decode_big_endian_short_frame_is_rejected() {
        // Big-endian signal spanning bytes 2..=3 (start_bit 23, len 16). The
        // little-endian span heuristic `(start_bit + length - 1)/8` would compute
        // byte 4 and wrongly accept a 3-byte frame; the byte-order-aware span must
        // reject it instead of indexing out of bounds.
        let signal =
            Signal::parse(&mut Parser::new(b"SG_ BeSig : 23|16@0+ (1,0) [0|65535] \"\"").unwrap())
                .unwrap();
        let short = [0x00, 0x00, 0x00];
        assert!(signal.decode_raw(&short).is_err());
        let ok = [0x00, 0x00, 0x12, 0x34];
        assert!(signal.decode_raw(&ok).is_ok());
    }

    #[test]
    fn test_decode_signed_64bit_no_shift_ub() {
        // Full-width 64-bit signed signal must not evaluate `1u64 << 64`.
        let signal =
            Signal::parse(&mut Parser::new(b"SG_ Big : 0|64@1- (1,0) [-1|1] \"\"").unwrap())
                .unwrap();
        let data = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
        let (raw, _value) = signal.decode_raw(&data).unwrap();
        assert_eq!(raw, -1);
    }

    #[test]
    fn test_decode_unsigned_64bit_physical_value() {
        // A 64-bit unsigned value with the MSB set must not be treated as negative
        // when computing the physical value.
        let signal =
            Signal::parse(&mut Parser::new(b"SG_ Big : 0|64@1+ (1,0) [0|1] \"\"").unwrap())
                .unwrap();
        let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        assert_eq!(value, 9223372036854775808.0);
    }

    #[test]
    fn test_decode_big_endian() {
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ TestSignal : 0|16@0+ (1,0) [0|65535] \"\"").unwrap(),
        )
        .unwrap();
        // Test big-endian decoding: value 0x0100 = 256 at bit 0-15
        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        // Verify it decodes to a valid value within range
        assert!((0.0..=65535.0).contains(&value));
    }

    #[test]
    fn test_decode_little_endian_with_offset() {
        let signal =
            Signal::parse(&mut Parser::new(b"SG_ Temp : 0|8@1- (1,-40) [-40|215] \"\"").unwrap())
                .unwrap();
        // Raw value 90 with offset -40 = 50°C
        let data = [0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        assert_eq!(value, 50.0);
    }

    #[test]
    fn test_decode_big_endian_with_factor() {
        let signal =
            Signal::parse(&mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"\"").unwrap())
                .unwrap();
        // Test big-endian decoding with factor
        // Big-endian at bit 0-15: bytes [0x1F, 0x40]
        let data = [0x1F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        // Verify it decodes and applies factor correctly (value should be positive)
        assert!((0.0..=16383.75).contains(&value)); // Max u16 * 0.25
    }

    // ============================================================================
    // Specification Verification Tests
    // These tests verify against exact examples from dbc/SPECIFICATIONS.md
    // ============================================================================

    /// Verify Section 10.3: Little-Endian Signals
    /// Example from spec:
    /// Signal: SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] "km/h"
    /// Message bytes: [0x64, 0x00, ...]
    /// Raw value = 0x0064 = 100 decimal
    /// Physical = 100 × 0.1 = 10.0 km/h
    #[test]
    fn test_spec_section_10_3_little_endian_example() {
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] \"km/h\"").unwrap(),
        )
        .unwrap();

        // Spec example: bytes [0x64, 0x00]
        let data = [0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();

        // Expected: raw=100, physical=100*0.1=10.0
        assert_eq!(
            value, 10.0,
            "Spec Section 10.3: Little-endian 0x64 should decode to 10.0 km/h"
        );
    }

    /// Verify Section 10.4: Big-Endian Signals
    /// Example from spec:
    /// Signal: SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] "kPa"
    /// Message bytes: [0x03, 0xE8, ...]
    /// Raw value = 0x03E8 = 1000 decimal
    /// Physical = 1000 × 0.01 = 10.0 kPa
    #[test]
    fn test_spec_section_10_4_big_endian_example() {
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] \"kPa\"").unwrap(),
        )
        .unwrap();

        // Spec example: bytes [0x03, 0xE8]
        let data = [0x03, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();

        // Expected: raw=1000 (0x03E8), physical=1000*0.01=10.0
        assert_eq!(
            value, 10.0,
            "Spec Section 10.4: Big-endian 0x03E8 should decode to 10.0 kPa"
        );
    }

    /// Verify Section 10.5: Value Conversion with Offset
    /// Example from spec:
    /// Signal: SG_ Temperature : 16|8@1- (1,-40) [-40|87] "°C"
    /// Raw value = 127 (0x7F) → Physical = 127 × 1 + (-40) = 87°C
    /// Raw value = 0 (0x00) → Physical = 0 × 1 + (-40) = -40°C
    #[test]
    fn test_spec_section_10_5_temperature_offset_example() {
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ Temperature : 16|8@1- (1,-40) [-40|87] \"\"").unwrap(),
        )
        .unwrap();

        // Test 1: raw=127 → physical=87°C
        // Little-endian: signal at bit 16 means byte 2
        let data1 = [0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value1) = signal.decode_raw(&data1).unwrap();
        assert_eq!(
            value1, 87.0,
            "Spec Section 10.5: raw=127 should decode to 87°C"
        );

        // Test 2: raw=0 → physical=-40°C
        let data2 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value2) = signal.decode_raw(&data2).unwrap();
        assert_eq!(
            value2, -40.0,
            "Spec Section 10.5: raw=0 should decode to -40°C"
        );
    }

    /// Verify Section 10.2: Byte Order Values
    /// @0 = Big-Endian (Motorola)
    /// @1 = Little-Endian (Intel)
    #[test]
    fn test_spec_section_10_2_byte_order_values() {
        use crate::ByteOrder;

        // Verify enum values match spec
        assert_eq!(
            ByteOrder::BigEndian as u8,
            0,
            "Spec Section 10.2: @0 = Big-Endian"
        );
        assert_eq!(
            ByteOrder::LittleEndian as u8,
            1,
            "Spec Section 10.2: @1 = Little-Endian"
        );
    }

    /// Verify Section 10.5: Value Conversion Formula
    /// physical_value = raw_value × factor + offset
    #[test]
    fn test_spec_section_10_5_value_conversion_formula() {
        // Test with factor=0.25 and offset=100
        let signal = Signal::parse(
            &mut Parser::new(b"SG_ Test : 0|16@1+ (0.25,100) [0|1000] \"\"").unwrap(),
        )
        .unwrap();

        // raw=400 → physical = 400 * 0.25 + 100 = 200
        // Little-endian: 400 = 0x0190 → bytes [0x90, 0x01]
        let data = [0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
        let (_raw, value) = signal.decode_raw(&data).unwrap();
        assert_eq!(value, 200.0, "Spec Section 10.5: 400 * 0.25 + 100 = 200");
    }
}