mod bitfield;
mod frame;
mod incident;
pub mod irsdk_flags;
mod schema;
mod update_rate;
mod var_data;
mod variable_type;
pub use bitfield::{
BitField, engine_mandatory_repair_needed, engine_optional_repair_needed,
session_dq_scoring_invalid, tick_after_u32,
};
pub use frame::FramePacket;
pub use incident::{IncidentClassification, IncidentPenalty, IncidentReport, decode_incident};
pub use schema::{VariableInfo, VariableSchema};
pub use update_rate::UpdateRate;
pub use var_data::VarData;
pub use variable_type::{Value, VariableType};
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
prop_compose! {
fn arb_variable_info()(
name in "[a-zA-Z][a-zA-Z0-9_]*",
data_type in prop::sample::select(vec![
VariableType::Char, VariableType::Int8, VariableType::UInt8,
VariableType::Int16, VariableType::UInt16, VariableType::Int32,
VariableType::UInt32, VariableType::Float32, VariableType::Float64,
VariableType::Bool, VariableType::BitField
]),
offset in 0..1024usize,
count in 1..10usize,
units in "[a-zA-Z/^2]*",
description in "[a-zA-Z ]*"
) -> VariableInfo {
VariableInfo {
name,
data_type,
offset,
count,
count_as_time: false,
units,
description,
}
}
}
proptest! {
#[test]
fn prop_variable_schema_parsing_with_fuzzed_headers(
variables in prop::collection::btree_map(
"[a-zA-Z][a-zA-Z0-9_]*",
arb_variable_info(),
0..20
),
frame_size in 64..2048usize
) {
use std::collections::HashMap;
let mut adjusted_variables = HashMap::new();
for (name, mut var_info) in variables.into_iter() {
let max_size = var_info.data_type.size() * var_info.count;
if max_size < frame_size {
var_info.offset %= frame_size - max_size;
} else {
var_info.offset = 0;
var_info.count = 1;
}
var_info.name = name.clone();
adjusted_variables.insert(name, var_info);
}
let schema = VariableSchema {
variables: adjusted_variables,
frame_size,
};
prop_assert!(schema.frame_size <= 2048);
prop_assert!(schema.frame_size >= 64);
for var_info in schema.variables.values() {
let end_offset = var_info.offset + (var_info.data_type.size() * var_info.count);
prop_assert!(end_offset <= schema.frame_size);
prop_assert!(var_info.count > 0);
}
let validation_result = schema.validate();
prop_assert!(validation_result.is_ok());
}
#[test]
fn prop_variable_type_size_calculations_correct(var_type in prop::sample::select(vec![
VariableType::Char, VariableType::Int8, VariableType::UInt8,
VariableType::Int16, VariableType::UInt16, VariableType::Int32,
VariableType::UInt32, VariableType::Float32, VariableType::Float64,
VariableType::Bool, VariableType::BitField
])) {
let size = var_type.size();
prop_assert!(size > 0);
prop_assert!(size <= 8);
match var_type {
VariableType::Char | VariableType::Int8 | VariableType::UInt8 | VariableType::Bool => {
prop_assert_eq!(size, 1);
},
VariableType::Int16 | VariableType::UInt16 => {
prop_assert_eq!(size, 2);
},
VariableType::Int32 | VariableType::UInt32 | VariableType::Float32 | VariableType::BitField => {
prop_assert_eq!(size, 4);
},
VariableType::Float64 => {
prop_assert_eq!(size, 8);
},
}
}
#[test]
fn prop_vardata_roundtrip_preserves_data_f32(
value in any::<f32>(),
offset in 0..100usize
) {
let mut data = vec![0u8; offset + 4 + 10];
let bytes = value.to_le_bytes();
data[offset..offset + 4].copy_from_slice(&bytes);
let var_info = VariableInfo {
name: "test".to_string(),
data_type: VariableType::Float32,
offset,
count: 1,
count_as_time: false,
units: "test".to_string(),
description: "test".to_string(),
};
let result = f32::from_bytes(&data, &var_info);
prop_assert!(result.is_ok());
let parsed = result.unwrap();
if value.is_finite() {
prop_assert!((parsed - value).abs() < f32::EPSILON);
} else if value.is_nan() {
prop_assert!(parsed.is_nan());
} else {
prop_assert_eq!(parsed, value);
}
}
#[test]
fn prop_vardata_roundtrip_preserves_data_i32(
value in any::<i32>(),
offset in 0..100usize
) {
let mut data = vec![0u8; offset + 4 + 10];
let bytes = value.to_le_bytes();
data[offset..offset + 4].copy_from_slice(&bytes);
let var_info = VariableInfo {
name: "test".to_string(),
data_type: VariableType::Int32,
offset,
count: 1,
count_as_time: false,
units: "test".to_string(),
description: "test".to_string(),
};
let result = i32::from_bytes(&data, &var_info);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), value);
}
#[test]
fn prop_bitfield_parsing_handles_all_32bit_patterns(
value in any::<u32>(),
offset in 0..100usize
) {
let mut data = vec![0u8; offset + 4 + 10];
let bytes = value.to_le_bytes();
data[offset..offset + 4].copy_from_slice(&bytes);
let var_info = VariableInfo {
name: "test".to_string(),
data_type: VariableType::BitField,
offset,
count: 1,
count_as_time: false,
units: "test".to_string(),
description: "test".to_string(),
};
let result = BitField::from_bytes(&data, &var_info);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap().value(), value);
}
#[test]
fn prop_tick_comparison_handles_wraparound(
tick1 in any::<u32>(),
tick2 in any::<u32>()
) {
let diff = tick2.wrapping_sub(tick1);
let is_tick2_newer = diff < u32::MAX / 2;
if tick1 == tick2 {
prop_assert_eq!(diff, 0);
} else if diff == 1 {
prop_assert!(is_tick2_newer);
}
}
#[test]
fn prop_bitfield_flag_operations(
value in any::<u32>(),
bit_index in 0..32u32
) {
let bitfield = BitField::new(value);
let expected_bit_set = (value & (1 << bit_index)) != 0;
prop_assert_eq!(bitfield.is_set(bit_index), expected_bit_set);
let flag = 1 << bit_index;
prop_assert_eq!(bitfield.has_flag(flag), expected_bit_set);
}
}
#[test]
fn variable_type_size_returns_correct_values() {
assert_eq!(VariableType::Char.size(), 1);
assert_eq!(VariableType::Int8.size(), 1);
assert_eq!(VariableType::UInt8.size(), 1);
assert_eq!(VariableType::Bool.size(), 1);
assert_eq!(VariableType::Int16.size(), 2);
assert_eq!(VariableType::UInt16.size(), 2);
assert_eq!(VariableType::Int32.size(), 4);
assert_eq!(VariableType::UInt32.size(), 4);
assert_eq!(VariableType::Float32.size(), 4);
assert_eq!(VariableType::BitField.size(), 4);
assert_eq!(VariableType::Float64.size(), 8);
}
#[test]
fn bitfield_constructor_works() {
let bitfield = BitField::new(0x12345678);
assert_eq!(bitfield.value(), 0x12345678);
}
#[test]
fn bitfield_flag_operations_basic() {
let bitfield = BitField::new(0b1010);
assert!(bitfield.is_set(1));
assert!(!bitfield.is_set(0));
assert!(bitfield.is_set(3));
assert!(!bitfield.is_set(2));
assert!(bitfield.has_flag(0b0010));
assert!(!bitfield.has_flag(0b0001));
assert!(bitfield.has_flag(0b1000));
assert!(!bitfield.has_flag(0b0100));
}
#[test]
fn test_incident_decoding_rep_only() {
use crate::irsdk_flags::incident as inc;
let bits = BitField::new(inc::REP_CONTACT_WITH_WORLD as u32);
let decoded = decode_incident(bits);
assert!(matches!(decoded.report, IncidentReport::ContactWithWorld));
assert!(matches!(decoded.penalty, IncidentPenalty::None));
}
#[test]
fn test_incident_decoding_pen_only() {
use crate::irsdk_flags::incident as inc;
let bits = BitField::new(((inc::PEN_0X as u32) << 8) & inc::PEN_MASK);
let decoded = decode_incident(bits);
assert!(matches!(decoded.report, IncidentReport::NoReport));
assert!(matches!(decoded.penalty, IncidentPenalty::ZeroX));
}
#[test]
fn test_engine_warnings_new_bits_present() {
use crate::irsdk_flags::engine_warnings as ew;
let flags = BitField::new(ew::MAND_REP_NEEDED | ew::OPT_REP_NEEDED);
assert!(flags.has_flag(ew::MAND_REP_NEEDED));
assert!(flags.has_flag(ew::OPT_REP_NEEDED));
}
#[test]
fn test_engine_repair_helpers() {
use crate::irsdk_flags::engine_warnings as ew;
let flags = BitField::new(ew::MAND_REP_NEEDED | ew::OPT_REP_NEEDED);
assert!(engine_mandatory_repair_needed(flags));
assert!(engine_optional_repair_needed(flags));
let none = BitField::new(0);
assert!(!engine_mandatory_repair_needed(none));
assert!(!engine_optional_repair_needed(none));
}
#[test]
fn test_session_dq_scoring_invalid_helper() {
use crate::irsdk_flags::session_flags as sf;
let flags = BitField::new(sf::DQ_SCORING_INVALID);
assert!(session_dq_scoring_invalid(flags));
let none = BitField::new(0);
assert!(!session_dq_scoring_invalid(none));
}
}