use super::{osc_codes, osc_event::OscEvent};
#[derive(Debug)]
pub struct OscBuffer {
data: String,
}
impl Default for OscBuffer {
fn default() -> Self { Self::new() }
}
impl OscBuffer {
#[must_use]
pub fn new() -> Self {
Self {
data: String::new(),
}
}
pub fn append_and_extract(&mut self, buffer: &[u8], n: usize) -> Vec<OscEvent> {
let text = String::from_utf8_lossy(&buffer[..n]);
self.data.push_str(&text);
let mut events = Vec::new();
while let Some(event) = self.extract_next_sequence() {
events.push(event);
}
events
}
pub fn extract_next_sequence(&mut self) -> Option<OscEvent> {
let start_idx = self.data.find(osc_codes::START)?;
let after_start_idx = start_idx + osc_codes::START.len();
let end_idx = self.data[after_start_idx..].find(osc_codes::END)?;
let params_end_idx = after_start_idx + end_idx;
let sequence_end_idx = params_end_idx + osc_codes::END.len();
let params = &self.data[after_start_idx..params_end_idx];
let event = self.parse_osc_params(params);
self.data.drain(0..sequence_end_idx);
event
}
#[must_use]
pub fn parse_osc_params(&self, params: &str) -> Option<OscEvent> {
let parts: Vec<&str> = params.split(osc_codes::DELIMITER).collect();
if parts.len() != 2 {
return None;
}
let state = parts[0].parse::<u8>().ok()?;
let progress = parts[1].parse::<f64>().ok()?;
match state {
0 => Some(OscEvent::ProgressCleared),
1 => {
let clamped = progress.clamp(0.0, 100.0);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let percentage = clamped as u8;
Some(OscEvent::ProgressUpdate(percentage))
}
2 => Some(OscEvent::BuildError),
3 => Some(OscEvent::IndeterminateProgress),
_ => None, }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_complete_sequence() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;50\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(50)]);
let input = b"\x1b]9;4;0;0\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressCleared]);
let input = b"\x1b]9;4;2;0\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::BuildError]);
let input = b"\x1b]9;4;3;0\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::IndeterminateProgress]);
}
#[test]
fn test_multiple_sequences() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;25\x1b\\\x1b]9;4;1;50\x1b\\\x1b]9;4;0;0\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(
events,
vec![
OscEvent::ProgressUpdate(25),
OscEvent::ProgressUpdate(50),
OscEvent::ProgressCleared
]
);
}
#[test]
fn test_sequences_with_text_between() {
let mut buffer = OscBuffer::new();
let input =
b"Building...\x1b]9;4;1;30\x1b\\Compiling crate...\x1b]9;4;1;60\x1b\\Done!";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(
events,
vec![OscEvent::ProgressUpdate(30), OscEvent::ProgressUpdate(60)]
);
assert!(buffer.data.contains("Done!"));
}
#[test]
fn test_split_sequence_across_buffers() {
let mut buffer = OscBuffer::new();
let input1 = b"\x1b]9;4;1;";
let events1 = buffer.append_and_extract(input1, input1.len());
assert_eq!(events1, vec![]);
let input2 = b"75\x1b\\";
let events2 = buffer.append_and_extract(input2, input2.len());
assert_eq!(events2, vec![OscEvent::ProgressUpdate(75)]);
}
#[test]
fn test_complex_split_scenarios() {
let mut buffer = OscBuffer::new();
let parts: [&[u8]; 4] = [b"\x1b]9", b";4;1;", b"42", b"\x1b\\"];
assert_eq!(buffer.append_and_extract(parts[0], parts[0].len()), vec![]);
assert_eq!(buffer.append_and_extract(parts[1], parts[1].len()), vec![]);
assert_eq!(buffer.append_and_extract(parts[2], parts[2].len()), vec![]);
assert_eq!(
buffer.append_and_extract(parts[3], parts[3].len()),
vec![OscEvent::ProgressUpdate(42)]
);
}
#[test]
fn test_invalid_sequences() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]);
let input = b"\x1b]9;4;1;abc\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]);
let input = b"\x1b]9;4;99;50\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]); }
#[test]
fn test_malformed_terminators() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;50";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]);
assert!(buffer.data.contains("9;4;1;50"));
let input2 = b"\x1b\\";
let events2 = buffer.append_and_extract(input2, input2.len());
assert_eq!(events2, vec![OscEvent::ProgressUpdate(50)]);
}
#[test]
fn test_out_of_range_values() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;150\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(100)]);
let input = b"\x1b]9;4;1;-50\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(0)]);
}
#[test]
fn test_interleaved_incomplete_sequences() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;25\x1b]9;4;1;50\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]);
}
#[test]
fn test_buffer_with_unicode() {
let mut buffer = OscBuffer::new();
let input = "🚀 Building...\x1b]9;4;1;50\x1b\\✨ Done!".as_bytes();
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(50)]);
assert!(buffer.data.contains("✨ Done!"));
}
#[test]
fn test_rapid_sequence_updates() {
let mut buffer = OscBuffer::new();
let mut all_events = Vec::new();
for i in (0..=100).step_by(10) {
let input = format!("\x1b]9;4;1;{i}\x1b\\");
let events = buffer.append_and_extract(input.as_bytes(), input.len());
all_events.extend(events);
}
assert_eq!(all_events.len(), 11); assert_eq!(all_events[0], OscEvent::ProgressUpdate(0));
assert_eq!(all_events[10], OscEvent::ProgressUpdate(100));
}
#[test]
fn test_empty_buffer_operations() {
let mut buffer = OscBuffer::new();
let events = buffer.append_and_extract(b"", 0);
assert_eq!(events, vec![]);
let input = b"Just regular text";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![]);
assert!(buffer.data.contains("Just regular text"));
}
#[test]
fn test_partial_sequence_with_corruption() {
let mut buffer = OscBuffer::new();
let partial = b"\x1b]9;4;1;33";
buffer.append_and_extract(partial, partial.len());
assert!(buffer.data.contains("\x1b]9;4;1;33"));
let text = b"some text";
buffer.append_and_extract(text, text.len());
let terminator = b"\x1b\\";
let events = buffer.append_and_extract(terminator, terminator.len());
assert_eq!(events, vec![]);
assert_eq!(buffer.data, "");
}
#[test]
fn test_partial_sequence_clean() {
let mut buffer = OscBuffer::new();
let partial = b"\x1b]9;4;1;33";
buffer.append_and_extract(partial, partial.len());
let terminator = b"\x1b\\";
let events = buffer.append_and_extract(terminator, terminator.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(33)]);
assert_eq!(buffer.data, "");
}
#[test]
fn test_decimal_progress_values() {
let mut buffer = OscBuffer::new();
let input = b"\x1b]9;4;1;33.7\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(33)]);
let input = b"\x1b]9;4;1;99.9\x1b\\";
let events = buffer.append_and_extract(input, input.len());
assert_eq!(events, vec![OscEvent::ProgressUpdate(99)]);
}
}