From 08b3f1b1295967ff32b3eee4537555ae8f9924d5 Mon Sep 17 00:00:00 2001 From: nerdCopter <56646290+nerdCopter@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:44:35 -0600 Subject: [PATCH 1/4] refactor: import GPS conversion functions from crate library - Add import for convert_gps_coordinate, convert_gps_altitude, convert_gps_speed, convert_gps_course from bbl_parser::conversion - Remove duplicate conversion function definitions from CLI main.rs - Eliminates ~40 lines of duplicated code - Addresses Issue #9: Refactor CLI to use crate library functions --- src/main.rs | 46 ++++++---------------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5a65562..17b8b06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,11 @@ use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; +// Import conversion functions from crate library to avoid code duplication +use bbl_parser::conversion::{ + convert_gps_altitude, convert_gps_coordinate, convert_gps_course, convert_gps_speed, +}; + /// Maximum recursion depth to prevent stack overflow const MAX_RECURSION_DEPTH: usize = 100; @@ -3012,46 +3017,7 @@ fn format_failsafe_phase(phase: i32) -> String { } // GPS/GPX export functions -fn extract_major_firmware_version(firmware_revision: &str) -> u8 { - // Extract major version from firmware string like "Betaflight 4.5.1 (77d01ba3b) AT32F435M" - if let Some(start) = firmware_revision.find(' ') { - let version_part = &firmware_revision[start + 1..]; - if let Some(end) = version_part.find('.') { - if let Ok(major) = version_part[..end].parse::() { - return major; - } - } - } - // Default to 4 if parsing fails (assume modern firmware) - 4 -} - -fn convert_gps_coordinate(raw_value: i32) -> f64 { - // GPS coordinates are stored as degrees * 10000000 - raw_value as f64 / 10000000.0 -} - -fn convert_gps_altitude(raw_value: i32, firmware_revision: &str) -> f64 { - // Altitude units changed between firmware versions: - // Before Betaflight 4: centimeters (factor 0.01) - // Betaflight 4+: decimeters (factor 0.1) - let major_version = extract_major_firmware_version(firmware_revision); - if major_version >= 4 { - raw_value as f64 / 10.0 // decimeters to meters - } else { - raw_value as f64 / 100.0 // centimeters to meters - } -} - -fn convert_gps_speed(raw_value: i32) -> f64 { - // Speed is stored as cm/s * 100, convert to m/s - raw_value as f64 / 100.0 -} - -fn convert_gps_course(raw_value: i32) -> f64 { - // Course is stored as degrees * 10 - raw_value as f64 / 10.0 -} +// Note: GPS conversion functions now imported from bbl_parser::conversion module fn export_gpx_file( file_path: &Path, From 0f9d1967be5aad11eb57074f2c5dd77b85ea7c66 Mon Sep 17 00:00:00 2001 From: nerdCopter <56646290+nerdCopter@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:52:13 -0600 Subject: [PATCH 2/4] refactor: import flag formatting functions from crate library - Add imports for format_flight_mode_flags, format_state_flags, format_failsafe_phase from bbl_parser::conversion - Remove ~90 lines of duplicate formatting function definitions - Addresses Issue #9: Refactor CLI to use crate library functions --- src/main.rs | 103 ++-------------------------------------------------- 1 file changed, 3 insertions(+), 100 deletions(-) diff --git a/src/main.rs b/src/main.rs index 17b8b06..dc43911 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use std::path::{Path, PathBuf}; // Import conversion functions from crate library to avoid code duplication use bbl_parser::conversion::{ convert_gps_altitude, convert_gps_coordinate, convert_gps_course, convert_gps_speed, + format_failsafe_phase, format_flight_mode_flags, format_state_flags, }; /// Maximum recursion depth to prevent stack overflow @@ -2915,106 +2916,8 @@ fn parse_bbl_file_streaming( Ok(processed_logs) } -fn format_flight_mode_flags(flags: i32) -> String { - let mut modes = Vec::new(); - - // Based on Betaflight firmware runtime_config.h flightModeFlags_e enum - // This matches the blackbox-tools implementation exactly: - // https://github.com/betaflight/blackbox-tools/blob/master/src/blackbox_fielddefs.c - - // FLIGHT_LOG_FLIGHT_MODE_NAME array from blackbox-tools - if (flags & (1 << 0)) != 0 { - modes.push("ANGLE_MODE"); // ANGLE_MODE = (1 << 0) - } - if (flags & (1 << 1)) != 0 { - modes.push("HORIZON_MODE"); // HORIZON_MODE = (1 << 1) - } - if (flags & (1 << 2)) != 0 { - modes.push("MAG"); // MAG_MODE = (1 << 2) - } - if (flags & (1 << 3)) != 0 { - modes.push("BARO"); // ALT_HOLD_MODE = (1 << 3) (old name BARO) - } - if (flags & (1 << 4)) != 0 { - modes.push("GPS_HOME"); // GPS_HOME_MODE (disabled in current firmware) - } - if (flags & (1 << 5)) != 0 { - modes.push("GPS_HOLD"); // POS_HOLD_MODE = (1 << 5) (old name GPS_HOLD) - } - if (flags & (1 << 6)) != 0 { - modes.push("HEADFREE"); // HEADFREE_MODE = (1 << 6) - } - if (flags & (1 << 7)) != 0 { - modes.push("UNUSED"); // CHIRP_MODE = (1 << 7) (old autotune, now unused) - } - if (flags & (1 << 8)) != 0 { - modes.push("PASSTHRU"); // PASSTHRU_MODE = (1 << 8) - } - if (flags & (1 << 9)) != 0 { - modes.push("RANGEFINDER_MODE"); // RANGEFINDER_MODE (disabled in current firmware) - } - if (flags & (1 << 10)) != 0 { - modes.push("FAILSAFE_MODE"); // FAILSAFE_MODE = (1 << 10) - } - if (flags & (1 << 11)) != 0 { - modes.push("GPS_RESCUE_MODE"); // GPS_RESCUE_MODE = (1 << 11) (new in current firmware) - } - - if modes.is_empty() { - "0".to_string() - } else { - modes.join("|") // Use pipe separator to avoid breaking CSV format - } -} - -fn format_state_flags(flags: i32) -> String { - let mut states = Vec::new(); - - // Based on Betaflight firmware runtime_config.h stateFlags_t enum - // This matches the blackbox-tools implementation exactly: - // https://github.com/betaflight/blackbox-tools/blob/master/src/blackbox_fielddefs.c - - // FLIGHT_LOG_FLIGHT_STATE_NAME array from blackbox-tools - if (flags & (1 << 0)) != 0 { - states.push("GPS_FIX_HOME"); // GPS_FIX_HOME = (1 << 0) - } - if (flags & (1 << 1)) != 0 { - states.push("GPS_FIX"); // GPS_FIX = (1 << 1) - } - if (flags & (1 << 2)) != 0 { - states.push("CALIBRATE_MAG"); // GPS_FIX_EVER = (1 << 2) but old name CALIBRATE_MAG - } - if (flags & (1 << 3)) != 0 { - states.push("SMALL_ANGLE"); // Used in blackbox-tools for compatibility - } - if (flags & (1 << 4)) != 0 { - states.push("FIXED_WING"); // Used in blackbox-tools for compatibility - } - - if states.is_empty() { - "0".to_string() - } else { - states.join("|") // Use pipe separator to avoid breaking CSV format - } -} - -fn format_failsafe_phase(phase: i32) -> String { - // Based on Betaflight firmware failsafe.h failsafePhase_e enum - // This matches the blackbox-tools implementation exactly: - // https://github.com/betaflight/blackbox-tools/blob/master/src/blackbox_fielddefs.c - - // FLIGHT_LOG_FAILSAFE_PHASE_NAME array from blackbox-tools - match phase { - 0 => "IDLE".to_string(), // FAILSAFE_IDLE = 0 - 1 => "RX_LOSS_DETECTED".to_string(), // FAILSAFE_RX_LOSS_DETECTED - 2 => "LANDING".to_string(), // FAILSAFE_LANDING - 3 => "LANDED".to_string(), // FAILSAFE_LANDED - 4 => "RX_LOSS_MONITORING".to_string(), // FAILSAFE_RX_LOSS_MONITORING (new in current firmware) - 5 => "RX_LOSS_RECOVERED".to_string(), // FAILSAFE_RX_LOSS_RECOVERED (new in current firmware) - 6 => "GPS_RESCUE".to_string(), // FAILSAFE_GPS_RESCUE (new in current firmware) - _ => phase.to_string(), - } -} +// Note: Flag formatting functions now imported from bbl_parser::conversion module +// (format_flight_mode_flags, format_state_flags, format_failsafe_phase) // GPS/GPX export functions // Note: GPS conversion functions now imported from bbl_parser::conversion module From 20a8f0a4b05e633605e3c90d79313338f7393746 Mon Sep 17 00:00:00 2001 From: nerdCopter <56646290+nerdCopter@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:57:51 -0600 Subject: [PATCH 3/4] refactor: migrate crate library to use anyhow::Result This enables CLI and crate to share the same error handling pattern, which is a prerequisite for deduplicating stream and parser code. - parser/stream.rs: use anyhow::Result instead of BBLError - parser/decoder.rs: use anyhow::anyhow! for error messages - parser/event.rs: use anyhow::Result - parser/frame.rs: use anyhow::Result - parser/gps.rs: use anyhow::Result - parser/header.rs: use anyhow::Result and anyhow::anyhow! - parser/main.rs: use anyhow::Result - export.rs: use anyhow::Result Addresses Issue #9: Refactor CLI to use crate library functions --- src/export.rs | 3 +-- src/parser/decoder.rs | 6 +++--- src/parser/event.rs | 2 +- src/parser/frame.rs | 2 +- src/parser/gps.rs | 2 +- src/parser/header.rs | 10 +++------- src/parser/main.rs | 3 +-- src/parser/stream.rs | 4 ++-- 8 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/export.rs b/src/export.rs index 897ba8a..1a0f9a2 100644 --- a/src/export.rs +++ b/src/export.rs @@ -5,8 +5,7 @@ use crate::conversion::*; use crate::types::*; -use crate::Result; -use anyhow::Context; +use anyhow::{Context, Result}; use std::collections::HashMap; use std::fs::File; use std::io::{BufWriter, Write}; diff --git a/src/parser/decoder.rs b/src/parser/decoder.rs index 237e9f9..7e06a78 100644 --- a/src/parser/decoder.rs +++ b/src/parser/decoder.rs @@ -1,5 +1,5 @@ -use crate::error::{BBLError, Result}; use crate::parser::stream::BBLDataStream; +use anyhow::Result; // BBL Encoding constants - directly from JavaScript reference pub const ENCODING_SIGNED_VB: u8 = 0; @@ -46,7 +46,7 @@ pub fn decode_field_value( values[index] = 0; } _ => { - return Err(BBLError::InvalidEncoding(encoding)); + return Err(anyhow::anyhow!("Invalid encoding type: {}", encoding)); } } Ok(()) @@ -127,6 +127,6 @@ pub fn apply_predictor( let motor_output_min = sysconfig.get("motorOutput[0]").copied().unwrap_or(48); Ok(value + motor_output_min) // Force signed 32-bit like Betaflight } - _ => Err(BBLError::InvalidPredictor(predictor)), + _ => Err(anyhow::anyhow!("Invalid predictor type: {}", predictor)), } } diff --git a/src/parser/event.rs b/src/parser/event.rs index ed51721..bab70a9 100644 --- a/src/parser/event.rs +++ b/src/parser/event.rs @@ -5,7 +5,7 @@ use crate::parser::stream::BBLDataStream; use crate::types::EventFrame; -use crate::Result; +use anyhow::Result; /// Helper function to parse inflight adjustment events (types 4 and 13) /// Returns the event description string diff --git a/src/parser/frame.rs b/src/parser/frame.rs index 53a79e5..b29e6ab 100644 --- a/src/parser/frame.rs +++ b/src/parser/frame.rs @@ -1,9 +1,9 @@ -use crate::error::Result; use crate::parser::{decoder::*, event::parse_e_frame, gps::*, stream::BBLDataStream}; use crate::types::{ DecodedFrame, EventFrame, FrameDefinition, FrameHistory, FrameStats, GpsCoordinate, GpsHomeCoordinate, }; +use anyhow::Result; use std::collections::HashMap; /// Parse frames from binary data diff --git a/src/parser/gps.rs b/src/parser/gps.rs index 1ed6740..4ec01d0 100644 --- a/src/parser/gps.rs +++ b/src/parser/gps.rs @@ -12,7 +12,7 @@ use crate::parser::decoder::{ use crate::parser::frame::parse_frame_data; use crate::parser::stream::BBLDataStream; use crate::types::{FrameDefinition, GpsCoordinate, GpsHomeCoordinate}; -use crate::Result; +use anyhow::Result; use std::collections::HashMap; /// Parse H-frame (GPS home position) data from the stream diff --git a/src/parser/header.rs b/src/parser/header.rs index 59dfc87..137a5a9 100644 --- a/src/parser/header.rs +++ b/src/parser/header.rs @@ -1,5 +1,5 @@ -use crate::error::{BBLError, Result}; use crate::types::{BBLHeader, FrameDefinition}; +use anyhow::Result; use std::collections::HashMap; /// Parse BBL headers from text @@ -157,9 +157,7 @@ fn parse_predictor_info(line: &str, frame_def: &mut FrameDefinition) -> Result<( frame_def.update_predictors(&predictors); Ok(()) } - Err(_) => Err(BBLError::InvalidHeader( - "Invalid predictor values".to_string(), - )), + Err(_) => Err(anyhow::anyhow!("Invalid header: Invalid predictor values")), } } @@ -178,9 +176,7 @@ fn parse_encoding_info(line: &str, frame_def: &mut FrameDefinition) -> Result<() frame_def.update_encoding(&encodings); Ok(()) } - Err(_) => Err(BBLError::InvalidHeader( - "Invalid encoding values".to_string(), - )), + Err(_) => Err(anyhow::anyhow!("Invalid header: Invalid encoding values")), } } diff --git a/src/parser/main.rs b/src/parser/main.rs index 3aa2fbb..b19d392 100644 --- a/src/parser/main.rs +++ b/src/parser/main.rs @@ -1,6 +1,5 @@ use crate::types::*; -use crate::Result; -use anyhow::{anyhow, Context}; +use anyhow::{anyhow, Context, Result}; use std::path::Path; /// Parse BBL file and return all logs (for CLI and multi-log processing) diff --git a/src/parser/stream.rs b/src/parser/stream.rs index d1f9978..4eb299a 100644 --- a/src/parser/stream.rs +++ b/src/parser/stream.rs @@ -1,4 +1,4 @@ -use crate::error::{BBLError, Result}; +use anyhow::Result; /// BBL data stream for reading binary data pub struct BBLDataStream<'a> { @@ -30,7 +30,7 @@ impl<'a> BBLDataStream<'a> { Ok(byte) } else { self.eof = true; - Err(BBLError::UnexpectedEof) + Err(anyhow::anyhow!("EOF")) } } From 850fc27f4f10fe5ae3a5f4ca5ac9b70ebcb5367b Mon Sep 17 00:00:00 2001 From: nerdCopter <56646290+nerdCopter@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:01:38 -0600 Subject: [PATCH 4/4] fix: align crate parse_h_frame and 14-bit encoding with CLI behavior - parser/gps.rs: parse_h_frame now uses graceful fallback for unknown encodings (read_signed_vb with unwrap_or(0)) matching CLI behavior - parser/stream.rs: read_neg_14bit now uses sign-magnitude encoding matching CLI's bbl_format::sign_extend_14bit + negation - Updated tests for sign-magnitude encoding - Removed unused two's complement sign extension function This ensures crate's H-frame parsing matches the working CLI logic. Addresses Issue #9: CLI logic takes precedence --- src/parser/gps.rs | 15 +++--- src/parser/stream.rs | 111 +++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/parser/gps.rs b/src/parser/gps.rs index 4ec01d0..977b920 100644 --- a/src/parser/gps.rs +++ b/src/parser/gps.rs @@ -42,12 +42,15 @@ pub fn parse_h_frame( ENCODING_NEG_14BIT => stream.read_neg_14bit()?, ENCODING_NULL => 0, _ => { - // Unsupported H-frame encoding - return error instead of silently continuing - // This prevents stream desynchronization from being masked by default values - return Err(anyhow::anyhow!( - "Unsupported H-frame encoding {} for field '{}' - stream desynchronization possible", - field.encoding, field.name - )); + // Unsupported H-frame encoding - gracefully fall back to signed VB + // This matches CLI behavior for maximum compatibility with various logs + if debug { + println!( + "Unsupported H-frame encoding {} for field {}", + field.encoding, field.name + ); + } + stream.read_signed_vb().unwrap_or(0) } }; diff --git a/src/parser/stream.rs b/src/parser/stream.rs index 4eb299a..b6a8672 100644 --- a/src/parser/stream.rs +++ b/src/parser/stream.rs @@ -220,17 +220,23 @@ impl<'a> BBLDataStream<'a> { Ok(()) } - /// Read negative 14-bit encoding - /// Reads an unsigned variable byte and interprets it as a 14-bit two's complement signed value. - /// The value is masked to 14 bits (0x3FFF), with bit 13 serving as the sign bit. - /// Negative values (sign bit set) are sign-extended to i32. + /// Read negative 14-bit encoding (sign-magnitude format) + /// Reads an unsigned variable byte and interprets it as a 14-bit sign-magnitude value. + /// Bit 13 is the sign bit, bits 0-12 are the magnitude. + /// Returns the negated value to match blackbox_decode behavior. pub fn read_neg_14bit(&mut self) -> Result { - let unsigned = self.read_unsigned_vb()?; + let unsigned = self.read_unsigned_vb()? as u16; + Ok(-sign_extend_14bit_sign_magnitude(unsigned)) + } +} - // Mask to 14 bits and perform sign-extension - // If bit 13 is set, the value is negative and needs sign-extension - let masked = (unsigned & 0x3FFF) as u16; - Ok(sign_extend_14bit_twos_complement(masked)) +/// Sign-magnitude 14-bit encoding (matches bbl_format::sign_extend_14bit and blackbox_decode) +/// Bit 13 indicates sign, bits 0-12 are the magnitude +fn sign_extend_14bit_sign_magnitude(value: u16) -> i32 { + if (value & 0x2000) != 0 { + -((value & 0x1fff) as i32) + } else { + (value & 0x1fff) as i32 } } @@ -263,16 +269,6 @@ fn sign_extend_8bit(value: u8) -> i32 { value as i8 as i32 } -// Two's complement sign extension for 14-bit values (distinct from bbl_format.rs::sign_extend_14bit -// which uses sign-magnitude encoding) -fn sign_extend_14bit_twos_complement(value: u16) -> i32 { - if (value & 0x2000) != 0 { - (value as i32) | !0x3fff - } else { - (value & 0x3fff) as i32 - } -} - fn sign_extend_16bit(value: u16) -> i32 { value as i16 as i32 } @@ -290,70 +286,71 @@ mod tests { use super::*; #[test] - fn test_sign_extend_14bit_positive() { - // Positive values have bit 13 = 0 - assert_eq!(sign_extend_14bit_twos_complement(0x0000), 0); // 0 - assert_eq!(sign_extend_14bit_twos_complement(0x0001), 1); // 1 - assert_eq!(sign_extend_14bit_twos_complement(0x1FFF), 0x1FFF); // 8191 (max positive) + fn test_sign_extend_14bit_sign_magnitude_positive() { + // Positive values have bit 13 = 0 (sign bit clear) + assert_eq!(sign_extend_14bit_sign_magnitude(0x0000), 0); // 0 + assert_eq!(sign_extend_14bit_sign_magnitude(0x0001), 1); // 1 + assert_eq!(sign_extend_14bit_sign_magnitude(0x1FFF), 0x1FFF); // 8191 (max positive magnitude) } #[test] - fn test_sign_extend_14bit_negative() { - // Negative values have bit 13 = 1, sign extended to all upper bits - assert_eq!(sign_extend_14bit_twos_complement(0x2000), -8192); // -8192 (min negative) - assert_eq!(sign_extend_14bit_twos_complement(0x3FFF), -1); // -1 - assert_eq!(sign_extend_14bit_twos_complement(0x2001), -8191); // -8191 + fn test_sign_extend_14bit_sign_magnitude_negative() { + // Negative values have bit 13 = 1 (sign bit set), magnitude in bits 0-12 + // 0x2000 = bit 13 set, magnitude 0 -> returns -0 = 0 (actually negative zero) + assert_eq!(sign_extend_14bit_sign_magnitude(0x2000), 0); // -0 + // 0x2001 = bit 13 set, magnitude 1 -> returns -1 + assert_eq!(sign_extend_14bit_sign_magnitude(0x2001), -1); + // 0x3FFF = bit 13 set, magnitude 0x1FFF (8191) -> returns -8191 + assert_eq!(sign_extend_14bit_sign_magnitude(0x3FFF), -8191); } #[test] fn test_read_neg_14bit_positive() { // Test reading positive 14-bit value from variable byte encoding // VB encoding of 100 is [100] (single byte since 100 < 128) + // sign_extend_14bit_sign_magnitude(100) = 100 (bit 13 not set) + // read_neg_14bit returns -100 (negation) let data = vec![100u8]; let mut stream = BBLDataStream::new(&data); - assert_eq!(stream.read_neg_14bit().unwrap(), 100); + // The function returns the negation: -sign_extend_14bit_sign_magnitude(100) = -100 + assert_eq!(stream.read_neg_14bit().unwrap(), -100); } #[test] fn test_read_neg_14bit_negative() { - // Test reading negative 14-bit value - // 14-bit value -1 (0x3FFF in two's complement) - // VB encode 0x3FFF: 0x3FFF = 16383 - // 16383 in VB: 0xFF (127 + continuation), 0x7F (127, final) = 127 + 127*128 = 16383 - let data = vec![0xFF, 0x7Fu8]; + // Test reading value with sign bit set + // VB encode 0x2001 = 8193: 0x81 (1 + continuation), 0x40 (64, final) = 1 + 64*128 = 8193 + let data = vec![0x81, 0x40u8]; let mut stream = BBLDataStream::new(&data); - assert_eq!(stream.read_neg_14bit().unwrap(), -1); + // 0x2001: bit 13 set, magnitude = 1, sign_extend returns -1 + // read_neg_14bit returns -(-1) = 1 + assert_eq!(stream.read_neg_14bit().unwrap(), 1); } #[test] fn test_read_neg_14bit_boundary() { // Test boundary values - // Max positive: 0x1FFF (8191) - // VB encode 0x1FFF: 0xFF (127 + continuation), 0x3F (63, final) = 127 + 63*128 = 8191 - let data = vec![0xFF, 0x3Fu8]; + // Value 0: VB = [0], sign_extend_14bit(0) = 0, read_neg_14bit returns -0 = 0 + let data = vec![0u8]; let mut stream = BBLDataStream::new(&data); - assert_eq!(stream.read_neg_14bit().unwrap(), 8191); - - // Min negative: 0x2000 (-8192) - // VB encode 0x2000: 0x80 (0 + continuation), 0x20 (32, final) = 0 + 32*128 = 4096 - // But 0x2000 & 0x3FFF = 0x2000, and bit 13 is set, so it's negative - // Actually we need the full 14-bit value 0x2000 = 8192 - // In VB that's: 0x80, 0x40 = 0 + 64*128 = 8192 - let data = vec![0x80, 0x40u8]; + assert_eq!(stream.read_neg_14bit().unwrap(), 0); + + // Max magnitude positive (no sign): 0x1FFF = 8191 + // VB encode 0x1FFF: 0xFF, 0x3F = 127 + 63*128 = 8191 + // sign_extend returns 8191, read_neg_14bit returns -8191 + let data = vec![0xFF, 0x3Fu8]; let mut stream = BBLDataStream::new(&data); - assert_eq!(stream.read_neg_14bit().unwrap(), -8192); + assert_eq!(stream.read_neg_14bit().unwrap(), -8191); } #[test] - fn test_read_neg_14bit_masks_14_bits() { - // Verify that only lower 14 bits are used even if VB encodes more - // If VB reads a value > 0x3FFF, only lower 14 bits are used - // Encode 0x1FFFFF (large value), which masks to 0x3FFF = -1 - // VB encode 0x1FFFFF: 0xFF, 0xFF, 0x7F = 127 + 127*128 + 127*128^2 - let data = vec![0xFF, 0xFF, 0x7Fu8]; + fn test_read_neg_14bit_with_sign_bit() { + // When sign bit (bit 13) is set, sign_extend returns negative, then we negate again + // 0x2001 encodes as VB: 0x81, 0x40 = 1 + 64*128 = 8193 + // sign_extend_14bit_sign_magnitude(0x2001) = -1 (sign bit set, magnitude 1) + // read_neg_14bit returns -(-1) = 1 + let data = vec![0x81, 0x40u8]; let mut stream = BBLDataStream::new(&data); - let result = stream.read_neg_14bit().unwrap(); - // 0x1FFFFF & 0x3FFF = 0x3FFF, which is -1 in 14-bit two's complement - assert_eq!(result, -1); + assert_eq!(stream.read_neg_14bit().unwrap(), 1); } }