use crate::{Result, TelemetryError};
pub fn preprocess_iracing_yaml(yaml: &str) -> Result<String> {
let mut result = String::with_capacity(yaml.len());
let mut in_quotes = false;
let mut prev_char = ' ';
for ch in yaml.chars() {
match ch {
'"' if prev_char != '\\' => {
in_quotes = !in_quotes;
result.push(ch);
}
'\x00'..='\x08' | '\x0B'..='\x0C' | '\x0E'..='\x1F' => {
continue;
}
_ => {
result.push(ch);
}
}
prev_char = ch;
}
if result.trim().is_empty() {
return Err(TelemetryError::Parse {
context: "YAML preprocessing".to_string(),
details: "YAML is empty after preprocessing".to_string(),
});
}
Ok(result)
}
pub fn extract_yaml_from_memory(data: &[u8], offset: i32, length: i32) -> Result<String> {
if offset < 0 {
return Err(TelemetryError::Parse {
context: "YAML extraction".to_string(),
details: format!("Invalid offset: {}", offset),
});
}
if length <= 0 {
return Ok(String::new());
}
let offset = offset as usize;
let length = length as usize;
if offset + length > data.len() {
return Err(TelemetryError::Parse {
context: "YAML extraction".to_string(),
details: format!(
"YAML extends beyond buffer bounds: offset={}, len={}, buffer_size={}",
offset,
length,
data.len()
),
});
}
let yaml_data = &data[offset..offset + length];
let yaml_len = yaml_data.iter().position(|&b| b == 0).unwrap_or(length);
let yaml_str =
std::str::from_utf8(&yaml_data[..yaml_len]).map_err(|e| TelemetryError::Parse {
context: "YAML UTF-8 conversion".to_string(),
details: e.to_string(),
})?;
Ok(yaml_str.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preprocess_removes_control_characters() {
let input = "WeekendInfo:\n\x00\x01\x02 TrackName: test\x03";
let result = preprocess_iracing_yaml(input).unwrap();
assert!(!result.contains('\x00'));
assert!(!result.contains('\x01'));
assert!(!result.contains('\x02'));
assert!(!result.contains('\x03'));
assert!(result.contains("WeekendInfo"));
assert!(result.contains("TrackName"));
}
#[test]
fn test_preprocess_keeps_valid_whitespace() {
let input = "Key:\n\r\t Value";
let result = preprocess_iracing_yaml(input).unwrap();
assert!(result.contains('\n'));
assert!(result.contains('\r'));
assert!(result.contains('\t'));
}
#[test]
fn test_extract_yaml_from_memory_with_null_terminator() {
let data = b"SessionInfo:\n TrackName: test\0padding";
let result = extract_yaml_from_memory(data, 0, data.len() as i32).unwrap();
assert_eq!(result, "SessionInfo:\n TrackName: test");
}
#[test]
fn test_extract_yaml_from_memory_without_null() {
let data = b"SessionInfo:\n TrackName: test";
let result = extract_yaml_from_memory(data, 0, data.len() as i32).unwrap();
assert_eq!(result, "SessionInfo:\n TrackName: test");
}
#[test]
fn test_extract_yaml_bounds_check() {
let data = b"test";
let result = extract_yaml_from_memory(data, 0, 100);
assert!(result.is_err());
}
}