//! Bounded LLSD notation lexer, parser, serializer, and public helper functions. #![allow(clippy::missing_errors_doc)] // Public Result shapes are fixed by the mapped API. #![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters. #![allow(clippy::unnecessary_wraps)] // Result return shapes are fixed by the mapped API. #![allow(clippy::unused_self)] // Parser error helpers retain local cursor context call sites. use crate::{Error, OSD}; use base64::Engine as _; use libremetaverse_types::UUID; use libremetaverse_types::compat::{StringReader, StringWriter, Uri, Utf16CodeUnit}; use std::collections::HashMap; use std::mem::size_of; const INDENT: &str = " "; const MAX_INPUT_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES; const fn is_notation_whitespace(unit: u16) -> bool { unit == b' ' as u16 || unit == b'\t' as u16 || unit == b'\n' as u16 || unit == b'\r' as u16 } pub(crate) fn deserialize_string(input: String) -> Result { if input.len() > MAX_INPUT_BYTES { return Err(parse_error( 0, "notation LLSD input exceeds allocation limit", )); } let mut parser = Parser::new(&input); if parser.peek_non_whitespace().is_none() { return Ok(OSD::Undefined); } let value = parser.parse_value(0)?; value.validate_limits( OSD::DEFAULT_MAX_DEPTH, OSD::DEFAULT_MAX_NODES, OSD::DEFAULT_MAX_BINARY_BYTES, )?; Ok(value) } pub(crate) fn deserialize_reader(reader: StringReader) -> Result { deserialize_string(reader.0) } pub(crate) fn serialize(value: OSD) -> Result { serialize_internal(value, false) } pub(crate) fn serialize_formatted(value: OSD) -> Result { serialize_internal(value, true) } pub(crate) fn serialize_stream(value: OSD) -> Result { Ok(StringWriter(serialize(value)?)) } pub(crate) fn serialize_stream_formatted(value: OSD) -> Result { Ok(StringWriter(serialize_formatted(value)?)) } fn serialize_internal(value: OSD, formatted: bool) -> Result { value.validate_limits( OSD::DEFAULT_MAX_DEPTH, OSD::DEFAULT_MAX_NODES, OSD::DEFAULT_MAX_BINARY_BYTES, )?; let mut encoder = Encoder::new(); if formatted { encoder.write_formatted(&value, "", 0)?; } else { encoder.write_compact(&value, 0)?; } Ok(encoder.finish()) } pub(crate) fn buffer_characters_equal( reader: StringReader, buffer: Vec, offset: i32, ) -> Result { if reader.0.len() > MAX_INPUT_BYTES || buffer.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } let mut index = usize::try_from(offset).map_err(|_| Error::Argument)?; if index > buffer.len() { return Err(Error::Argument); } let input: Vec<_> = reader.0.encode_utf16().collect(); let mut input_index = 0; while index < buffer.len() && input .get(input_index) .is_some_and(|unit| *unit == buffer[index].0) { index += 1; input_index += 1; } i32::try_from(index).map_err(|_| Error::Argument) } pub(crate) fn peek_and_skip_whitespace(reader: StringReader) -> Result { if reader.0.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } Ok(reader .0 .encode_utf16() .find(|unit| !is_notation_whitespace(*unit)) .map_or(-1, i32::from)) } pub(crate) fn read_and_skip_whitespace(reader: StringReader) -> Result { peek_and_skip_whitespace(reader) } pub(crate) fn get_length_in_brackets(reader: StringReader) -> Result { if reader.0.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } let mut parser = Parser::new(&reader.0); let length = parser.parse_parenthesized_length("invalid notation LLSD length")?; i32::try_from(length).map_err(|_| Error::Argument) } pub(crate) fn get_string_delimited_by( reader: StringReader, delimiter: Utf16CodeUnit, ) -> Result { if reader.0.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } let mut parser = Parser::new(&reader.0); parser.parse_delimited(delimiter.0) } pub(crate) fn escape_character(value: String, delimiter: Utf16CodeUnit) -> Result { if value.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } let delimiter = char::from_u32(u32::from(delimiter.0)).ok_or(Error::Argument)?; let mut output = String::with_capacity(value.len().saturating_add(2)); for character in value.chars() { if character == '\\' || character == delimiter { output.push('\\'); } output.push(character); if output.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } } Ok(output) } pub(crate) fn unescape_character(value: String, delimiter: Utf16CodeUnit) -> Result { if value.len() > MAX_INPUT_BYTES { return Err(Error::Argument); } let delimiter = char::from_u32(u32::from(delimiter.0)).ok_or(Error::Argument)?; let collapsed = value.replace("\\\\", "\\"); Ok(collapsed.replace(&format!("\\{delimiter}"), &delimiter.to_string())) } struct Parser { units: Vec, position: usize, nodes: usize, allocated: usize, } impl Parser { fn new(input: &str) -> Self { Self { units: input.encode_utf16().collect(), position: 0, nodes: 0, allocated: 0, } } fn parse_value(&mut self, depth: usize) -> Result { if depth > OSD::DEFAULT_MAX_DEPTH { return Err(self.error("notation LLSD nesting depth exceeded")); } self.nodes = self .nodes .checked_add(1) .ok_or_else(|| self.error("notation LLSD node count overflow"))?; if self.nodes > OSD::DEFAULT_MAX_NODES { return Err(self.error("notation LLSD node limit exceeded")); } self.skip_whitespace(); let marker_position = self.position; let marker = self.read_unit("missing notation LLSD value marker")?; match marker { unit if unit == u16::from(b'!') => Ok(OSD::Undefined), unit if unit == u16::from(b'1') => Ok(OSD::Boolean(true)), unit if unit == u16::from(b'0') => Ok(OSD::Boolean(false)), unit if unit == u16::from(b't') => { self.consume_boolean_suffix("true", "invalid notation LLSD true value")?; Ok(OSD::Boolean(true)) } unit if unit == u16::from(b'T') => { self.consume_boolean_suffix("TRUE", "invalid notation LLSD true value")?; Ok(OSD::Boolean(true)) } unit if unit == u16::from(b'f') => { self.consume_boolean_suffix("false", "invalid notation LLSD false value")?; Ok(OSD::Boolean(false)) } unit if unit == u16::from(b'F') => { self.consume_boolean_suffix("FALSE", "invalid notation LLSD false value")?; Ok(OSD::Boolean(false)) } unit if unit == u16::from(b'i') => self.parse_integer(), unit if unit == u16::from(b'r') => self.parse_real(), unit if unit == u16::from(b'u') => self.parse_uuid(), unit if unit == u16::from(b'b') => self.parse_binary(), unit if unit == u16::from(b's') => self.parse_sized_string(), unit if unit == u16::from(b'\'') || unit == u16::from(b'"') => { Ok(OSD::String(self.parse_delimited(unit)?)) } unit if unit == u16::from(b'l') => self.parse_uri(), unit if unit == u16::from(b'd') => self.parse_date(), unit if unit == u16::from(b'[') => self.parse_array(depth), unit if unit == u16::from(b'{') => self.parse_map(depth), _ => Err(self.error_at(marker_position, "unknown notation LLSD type marker")), } } fn consume_boolean_suffix( &mut self, expected: &str, context: &'static str, ) -> Result<(), Error> { let expected: Vec<_> = expected.encode_utf16().collect(); let mut matched = 1; while matched < expected.len() && self .units .get(self.position) .is_some_and(|unit| *unit == expected[matched]) { matched += 1; self.position += 1; } if matched > 1 && matched < expected.len() { Err(self.error(context)) } else { Ok(()) } } fn parse_integer(&mut self) -> Result { let start = self.position; if self.peek_unit() == Some(u16::from(b'-')) { self.position += 1; } while self.peek_unit().is_some_and(is_ascii_digit) { self.position += 1; } let text = self.decode_range(start, self.position, "invalid notation LLSD integer")?; let value = text .parse::() .map_err(|_| self.error_at(start, "invalid notation LLSD integer"))?; Ok(OSD::Integer(value)) } fn parse_real(&mut self) -> Result { let start = self.position; while self.peek_unit().is_some_and(|unit| { is_ascii_digit(unit) || matches!(unit, 0x2e | 0x65 | 0x45 | 0x2b | 0x2d) }) { self.position += 1; } let text = self.decode_range(start, self.position, "invalid notation LLSD real")?; let value = text .parse::() .map_err(|_| self.error_at(start, "invalid notation LLSD real"))?; Ok(OSD::Real(value)) } fn parse_uuid(&mut self) -> Result { let start = self.position; let end = start .checked_add(36) .ok_or_else(|| self.error("notation LLSD UUID length overflow"))?; let text = self.decode_range(start, end, "truncated notation LLSD UUID")?; self.position = end; let uuid = UUID::new_with_string(text) .map_err(|_| self.error_at(start, "invalid notation LLSD UUID"))?; Ok(OSD::UUID(uuid)) } fn parse_binary(&mut self) -> Result { let marker_position = self.position.saturating_sub(1); let value = if self.peek_unit() == Some(u16::from(b'(')) { let length = self.parse_parenthesized_length("invalid notation LLSD binary length")?; let delimiter = self.read_quote("missing notation LLSD raw binary delimiter")?; let start = self.position; let end = start .checked_add(length) .ok_or_else(|| self.error("notation LLSD binary length overflow"))?; let units = self .units .get(start..end) .ok_or_else(|| self.error("truncated notation LLSD raw binary"))?; let bytes: Vec = units .iter() .copied() .map(|unit| { u8::try_from(unit).map_err(|_| { self.error_at(start, "invalid notation LLSD raw binary character") }) }) .collect::>()?; self.position = end; self.expect_unit(delimiter, "missing notation LLSD raw binary end delimiter")?; bytes } else { let base_start = self.position; let base = self.read_ascii_digits(2, "invalid notation LLSD binary base")?; let delimiter = self.read_quote("missing notation LLSD binary delimiter")?; let encoded = self.parse_delimited(delimiter)?; match base.as_str() { "64" => { decode_base64(&encoded).map_err(|context| self.error_at(base_start, context))? } "16" => { decode_base16(&encoded).map_err(|context| self.error_at(base_start, context))? } _ => { return Err( self.error_at(marker_position, "unsupported notation LLSD binary base") ); } } }; self.add_allocation( value.len(), "notation LLSD binary allocation limit exceeded", )?; Ok(OSD::Binary(value)) } fn parse_sized_string(&mut self) -> Result { let length = self.parse_parenthesized_length("invalid notation LLSD string length")?; let delimiter = self.read_quote("missing notation LLSD string delimiter")?; let start = self.position; let end = start .checked_add(length) .ok_or_else(|| self.error("notation LLSD string length overflow"))?; let value = self.decode_range(start, end, "truncated notation LLSD string")?; self.position = end; self.expect_unit(delimiter, "missing notation LLSD string end delimiter")?; self.add_allocation(value.len(), "notation LLSD text allocation limit exceeded")?; Ok(OSD::String(value)) } fn parse_uri(&mut self) -> Result { let delimiter = self.read_quote("missing notation LLSD URI delimiter")?; let start = self.position; let value = self.parse_delimited(delimiter)?; if value .chars() .any(|character| character == '\0' || character.is_control()) { return Err(self.error_at(start, "invalid notation LLSD URI")); } Ok(OSD::Uri(Uri(value))) } fn parse_date(&mut self) -> Result { let delimiter = self.read_quote("missing notation LLSD date delimiter")?; let start = self.position; let value = self.parse_delimited(delimiter)?; let date = crate::model::parse_system_time_for_codec(&value) .ok_or_else(|| self.error_at(start, "invalid notation LLSD date"))?; Ok(OSD::Date(date)) } fn parse_array(&mut self, depth: usize) -> Result { let mut values = Vec::new(); self.skip_whitespace(); if self.consume_unit(u16::from(b']')) { return Ok(OSD::Array(values)); } loop { if values.len() >= OSD::DEFAULT_MAX_NODES { return Err(self.error("notation LLSD array node limit exceeded")); } values.push(self.parse_value(depth + 1)?); self.skip_whitespace(); if self.consume_unit(u16::from(b']')) { break; } self.expect_unit(u16::from(b','), "invalid notation LLSD array delimiter")?; self.skip_whitespace(); if self.consume_unit(u16::from(b']')) { break; } } self.add_allocation( values.len().saturating_mul(size_of::()), "notation LLSD array allocation limit exceeded", )?; Ok(OSD::Array(values)) } fn parse_map(&mut self, depth: usize) -> Result { let mut values = HashMap::new(); self.skip_whitespace(); if self.consume_unit(u16::from(b'}')) { return Ok(OSD::Map(values)); } loop { if values.len() >= OSD::DEFAULT_MAX_NODES { return Err(self.error("notation LLSD map node limit exceeded")); } let key_position = self.position; let OSD::String(key) = self.parse_value(depth + 1)? else { return Err(self.error_at(key_position, "invalid notation LLSD map key")); }; self.skip_whitespace(); self.expect_unit(u16::from(b':'), "invalid notation LLSD map key delimiter")?; let value = self.parse_value(depth + 1)?; values.insert(key, value); self.skip_whitespace(); if self.consume_unit(u16::from(b'}')) { break; } self.expect_unit(u16::from(b','), "invalid notation LLSD map delimiter")?; self.skip_whitespace(); if self.consume_unit(u16::from(b'}')) { break; } } self.add_allocation( values.len().saturating_mul(size_of::<(String, OSD)>()), "notation LLSD map allocation limit exceeded", )?; Ok(OSD::Map(values)) } fn parse_parenthesized_length(&mut self, context: &'static str) -> Result { self.skip_whitespace(); let start = self.position; self.expect_unit(u16::from(b'('), context)?; let digits_start = self.position; while self.peek_unit().is_some_and(is_ascii_digit) { self.position += 1; } if self.position == digits_start { return Err(self.error_at(start, context)); } let digits = self.decode_range(digits_start, self.position, context)?; self.expect_unit(u16::from(b')'), context)?; let length = digits .parse::() .map_err(|_| self.error_at(start, context))?; if length > MAX_INPUT_BYTES { return Err(self.error_at(start, context)); } Ok(length) } fn parse_delimited(&mut self, delimiter: u16) -> Result { let start = self.position; let mut output = Vec::new(); let mut escaped = false; loop { let position = self.position; let unit = self.read_unit("unterminated notation LLSD string")?; if escaped { output.push(match unit { unit if unit == u16::from(b'a') => 0x07, unit if unit == u16::from(b'b') => 0x08, unit if unit == u16::from(b'f') => 0x0c, unit if unit == u16::from(b'n') => 0x0a, unit if unit == u16::from(b'r') => 0x0d, unit if unit == u16::from(b't') => 0x09, unit if unit == u16::from(b'v') => 0x0b, _ => unit, }); escaped = false; } else if unit == u16::from(b'\\') { escaped = true; } else if unit == delimiter { break; } else { output.push(unit); } if output.len() > MAX_INPUT_BYTES { return Err(self.error_at(position, "notation LLSD text allocation limit exceeded")); } } let value = String::from_utf16_lossy(&output); self.add_allocation(value.len(), "notation LLSD text allocation limit exceeded")?; if self.position < start { return Err(self.error("notation LLSD parser position overflow")); } Ok(value) } fn read_quote(&mut self, context: &'static str) -> Result { let position = self.position; let delimiter = self.read_unit(context)?; if matches!(delimiter, 0x22 | 0x27) { Ok(delimiter) } else { Err(self.error_at(position, context)) } } fn read_ascii_digits(&mut self, count: usize, context: &'static str) -> Result { let start = self.position; let end = start .checked_add(count) .ok_or_else(|| self.error(context))?; let units = self .units .get(start..end) .ok_or_else(|| self.error(context))?; if !units.iter().copied().all(is_ascii_digit) { return Err(self.error_at(start, context)); } let value = String::from_utf16(units).map_err(|_| self.error_at(start, context))?; self.position = end; Ok(value) } fn decode_range( &self, start: usize, end: usize, context: &'static str, ) -> Result { let units = self .units .get(start..end) .ok_or_else(|| self.error_at(start, context))?; Ok(String::from_utf16_lossy(units)) } fn peek_non_whitespace(&mut self) -> Option { self.skip_whitespace(); self.peek_unit() } fn skip_whitespace(&mut self) { while self.peek_unit().is_some_and(is_notation_whitespace) { self.position += 1; } } fn peek_unit(&self) -> Option { self.units.get(self.position).copied() } fn read_unit(&mut self, context: &'static str) -> Result { let unit = self.peek_unit().ok_or_else(|| self.error(context))?; self.position += 1; Ok(unit) } fn consume_unit(&mut self, expected: u16) -> bool { if self.peek_unit() == Some(expected) { self.position += 1; true } else { false } } fn expect_unit(&mut self, expected: u16, context: &'static str) -> Result<(), Error> { let position = self.position; if self.read_unit(context)? == expected { Ok(()) } else { Err(self.error_at(position, context)) } } fn add_allocation(&mut self, amount: usize, context: &'static str) -> Result<(), Error> { self.allocated = self .allocated .checked_add(amount) .ok_or_else(|| self.error(context))?; if self.allocated > OSD::DEFAULT_MAX_BINARY_BYTES { Err(self.error(context)) } else { Ok(()) } } const fn error(&self, context: &'static str) -> Error { self.error_at(self.position, context) } const fn error_at(&self, position: usize, context: &'static str) -> Error { parse_error(position, context) } } struct Encoder { output: String, } impl Encoder { fn new() -> Self { Self { output: String::with_capacity(128), } } fn finish(self) -> String { self.output } fn write_compact(&mut self, value: &OSD, depth: usize) -> Result<(), Error> { self.check_depth(depth)?; match value { OSD::Undefined => self.push('!'), OSD::Boolean(value) => self.push(if *value { 't' } else { 'f' }), OSD::Integer(value) => { self.push('i')?; self.push_str(&value.to_string()) } OSD::Real(value) => { self.push('r')?; self.push_str(&crate::model::format_real_for_codec(*value)) } OSD::UUID(value) => { self.push('u')?; self.push_str(&value.to_string()) } OSD::String(value) => self.write_quoted(value, '\''), OSD::Binary(value) => { self.push_str("b64\"")?; self.push_str(&base64::engine::general_purpose::STANDARD.encode(value))?; self.push('"') } OSD::Date(value) => { self.push_str("d\"")?; self.push_str(&crate::model::format_system_time_for_codec(*value))?; self.push('"') } OSD::Uri(Uri(value)) => { self.push('l')?; self.write_quoted(value, '"') } OSD::Array(values) => { self.push('[')?; for (index, value) in values.iter().enumerate() { if index > 0 { self.push(',')?; } self.write_compact(value, depth + 1)?; } self.push(']') } OSD::Map(values) => { self.push('{')?; for (index, (key, value)) in sorted_entries(values).into_iter().enumerate() { if index > 0 { self.push(',')?; } self.write_quoted(key, '\'')?; self.push(':')?; self.write_compact(value, depth + 1)?; } self.push('}') } OSD::LlsdXml(_) => Err(parse_error( self.output.len(), "LLSD XML fragments have no notation LLSD representation", )), } } fn write_formatted(&mut self, value: &OSD, indent: &str, depth: usize) -> Result<(), Error> { self.check_depth(depth)?; match value { OSD::Array(values) => { let child_indent = format!("{indent}{INDENT}"); self.write_array_formatted(values, &child_indent, depth) } OSD::Map(values) => { let child_indent = format!("{indent}{INDENT}"); self.write_map_formatted(values, &child_indent, depth) } _ => self.write_compact(value, depth), } } fn write_array_formatted( &mut self, values: &[OSD], indent: &str, depth: usize, ) -> Result<(), Error> { self.push('\n')?; self.push_str(indent)?; self.push('[')?; for (index, value) in values.iter().enumerate() { if !matches!(value, OSD::Array(_) | OSD::Map(_)) { self.push('\n')?; } self.push_str(indent)?; self.push_str(INDENT)?; self.write_formatted(value, indent, depth + 1)?; if index + 1 < values.len() { self.push(',')?; } } self.push('\n')?; self.push_str(indent)?; self.push(']') } fn write_map_formatted( &mut self, values: &HashMap, indent: &str, depth: usize, ) -> Result<(), Error> { self.push('\n')?; self.push_str(indent)?; self.push_str("{\n")?; let entries = sorted_entries(values); for (index, (key, value)) in entries.iter().enumerate() { self.push_str(indent)?; self.push_str(INDENT)?; self.write_quoted(key, '\'')?; self.push(':')?; self.write_formatted(value, indent, depth + 1)?; if index + 1 < entries.len() { self.push('\n')?; self.push_str(indent)?; self.push_str(INDENT)?; self.push_str(",\n")?; } } self.push('\n')?; self.push_str(indent)?; self.push('}') } fn write_quoted(&mut self, value: &str, delimiter: char) -> Result<(), Error> { self.push(delimiter)?; for character in value.chars() { if character == '\\' || character == delimiter { self.push('\\')?; } self.push(character)?; } self.push(delimiter) } fn check_depth(&self, depth: usize) -> Result<(), Error> { if depth > OSD::DEFAULT_MAX_DEPTH { Err(parse_error( self.output.len(), "notation LLSD nesting depth exceeded", )) } else { Ok(()) } } fn push(&mut self, character: char) -> Result<(), Error> { let length = self .output .len() .checked_add(character.len_utf8()) .ok_or(Error::Argument)?; if length > MAX_INPUT_BYTES { return Err(Error::Argument); } self.output.push(character); Ok(()) } fn push_str(&mut self, value: &str) -> Result<(), Error> { let length = self .output .len() .checked_add(value.len()) .ok_or(Error::Argument)?; if length > MAX_INPUT_BYTES { return Err(Error::Argument); } self.output.push_str(value); Ok(()) } } fn sorted_entries(values: &HashMap) -> Vec<(&String, &OSD)> { let mut entries: Vec<_> = values.iter().collect(); entries.sort_unstable_by_key(|(key, _)| *key); entries } fn decode_base16(value: &str) -> Result, &'static str> { if !value.len().is_multiple_of(2) { return Err("invalid notation LLSD base16 length"); } value .as_bytes() .chunks_exact(2) .map(|pair| { let high = hex_value(pair[0]).ok_or("invalid notation LLSD base16 digit")?; let low = hex_value(pair[1]).ok_or("invalid notation LLSD base16 digit")?; Ok((high << 4) | low) }) .collect() } fn decode_base64(value: &str) -> Result, &'static str> { let compact: Vec = value .bytes() .filter(|byte| !matches!(byte, b' ' | b'\t' | b'\n' | b'\r')) .collect(); base64::engine::general_purpose::STANDARD .decode(compact) .map_err(|_| "invalid notation LLSD base64 value") } const fn hex_value(value: u8) -> Option { match value { b'0'..=b'9' => Some(value - b'0'), b'a'..=b'f' => Some(value - b'a' + 10), b'A'..=b'F' => Some(value - b'A' + 10), _ => None, } } const fn is_ascii_digit(unit: u16) -> bool { unit >= b'0' as u16 && unit <= b'9' as u16 } const fn parse_error(position: usize, context: &'static str) -> Error { Error::Parse { position, context } } #[cfg(test)] mod tests { use super::*; use std::time::{Duration, UNIX_EPOCH}; #[test] fn compact_and_formatted_round_trip_every_variant() { let value = OSD::Array(vec![ OSD::Undefined, OSD::Boolean(true), OSD::Integer(-42), OSD::Real(2.5), OSD::UUID( UUID::new_with_string("97f4aeca-88a1-42a1-b385-b97b18abb255".into()).unwrap(), ), OSD::String("quote '\\ and 𐄷".into()), OSD::Binary(vec![0, 1, 255]), OSD::Date(UNIX_EPOCH + Duration::from_millis(1_199_134_150_100)), OSD::Uri(Uri("https://example.test/a path".into())), OSD::Map(HashMap::from([("nested".into(), OSD::Array(vec![]))])), ]); let compact = serialize(value.clone()).unwrap(); assert_eq!(serialize_stream(value.clone()).unwrap().0, compact); assert_eq!(deserialize_string(compact).unwrap(), value); let formatted = serialize_formatted(value.clone()).unwrap(); assert_eq!( serialize_stream_formatted(value.clone()).unwrap().0, formatted ); assert!(formatted.contains('\n')); assert_eq!(deserialize_string(formatted).unwrap(), value); assert_eq!( serialize_formatted(OSD::Array(vec![OSD::Integer(1), OSD::Integer(2)])).unwrap(), "\n [\n i1,\n i2\n ]" ); } #[test] fn accepted_string_binary_boolean_and_escape_variants_are_exact() { assert_eq!( deserialize_string("true".into()).unwrap(), OSD::Boolean(true) ); assert_eq!(deserialize_string("T".into()).unwrap(), OSD::Boolean(true)); assert_eq!( deserialize_string(r#"s(2)"𐄷""#.into()).unwrap(), OSD::String("𐄷".into()) ); assert_eq!( deserialize_string(r#"b16"0001fF""#.into()).unwrap(), OSD::Binary(vec![0, 1, 255]) ); assert_eq!( deserialize_string("b64\"AA E=\r\n\"".into()).unwrap(), OSD::Binary(vec![0, 1]) ); assert_eq!( deserialize_string("b(3)\"A\\0\"".into()).unwrap(), OSD::Binary(vec![b'A', b'\\', b'0']) ); assert_eq!( deserialize_string(r"'a\n\t\'\\b'".into()).unwrap(), OSD::String("a\n\t'\\b".into()) ); assert_eq!( escape_character("a\\'b".into(), Utf16CodeUnit(u16::from(b'\''))).unwrap(), "a\\\\\\'b" ); assert_eq!( unescape_character("a\\\\\\'b".into(), Utf16CodeUnit(u16::from(b'\''))).unwrap(), "a\\'b" ); assert_eq!( get_length_in_brackets(StringReader(" \t(123)remaining".into())).unwrap(), 123 ); assert_eq!( get_string_delimited_by( StringReader(r"line\nquote\'tail'ignored".into()), Utf16CodeUnit(u16::from(b'\'')), ) .unwrap(), "line\nquote'tail" ); assert_eq!( peek_and_skip_whitespace(StringReader(" \r\nX".into())).unwrap(), i32::from(b'X') ); } #[test] fn malformed_inputs_report_positions_and_depth_is_bounded() { for (input, context) in [ ("i", "invalid notation LLSD integer"), ("r", "invalid notation LLSD real"), ("u123", "truncated notation LLSD UUID"), ("b64\"%%%\"", "invalid notation LLSD base64 value"), ("b16\"0\"", "invalid notation LLSD base16 length"), ("s(4)\"abc\"", "missing notation LLSD string end delimiter"), ("'unterminated", "unterminated notation LLSD string"), ("[i1 i2]", "invalid notation LLSD array delimiter"), ("{'key'i1}", "invalid notation LLSD map key delimiter"), ] { let Error::Parse { position, context: actual, } = deserialize_string(input.into()).unwrap_err() else { panic!("malformed notation did not return a positional parse error"); }; assert!(position <= input.encode_utf16().count()); assert_eq!(actual, context); } let mut nested = "[".repeat(OSD::DEFAULT_MAX_DEPTH + 1); nested.push('!'); nested.push_str(&"]".repeat(OSD::DEFAULT_MAX_DEPTH + 1)); assert!(matches!( deserialize_string(nested), Err(Error::Parse { context: "notation LLSD nesting depth exceeded", .. }) )); } }