Implement bounded Notation LLSD codec

This commit is contained in:
2026-08-09 00:51:05 +00:00
parent 7a4f1f2fb1
commit 2308e55671
11 changed files with 1081 additions and 55 deletions

View File

@@ -273,11 +273,11 @@ impl OSD {
Ok(match self {
Self::Boolean(value) => if *value { "1" } else { "0" }.to_owned(),
Self::Integer(value) => value.to_string(),
Self::Real(value) => format_real(*value),
Self::Real(value) => format_real_for_codec(*value),
Self::String(value) | Self::LlsdXml(value) => value.clone(),
Self::UUID(value) => value.to_string(),
Self::Date(value) => format_system_time(*value),
Self::Uri(value) => value.0.clone(),
Self::Date(value) => format_system_time_for_codec(*value),
Self::Uri(value) => format_uri_for_codec(&value.0),
Self::Binary(value) => base64::engine::general_purpose::STANDARD.encode(value),
_ => String::new(),
})
@@ -296,7 +296,7 @@ impl OSD {
pub fn as_date(&self) -> Result<SystemTime, Error> {
Ok(match self {
Self::Date(value) => *value,
Self::String(value) => parse_system_time(value).unwrap_or(UNIX_EPOCH),
Self::String(value) => parse_system_time_for_codec(value).unwrap_or(UNIX_EPOCH),
_ => UNIX_EPOCH,
})
}
@@ -776,7 +776,7 @@ impl OSDReal {
Ok(self.value)
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(format_real(self.value))
Ok(format_real_for_codec(self.value))
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(real_to_u32(self.value))
@@ -795,7 +795,7 @@ impl OSDString {
Ok(string_as_boolean(&self.value))
}
pub fn as_date(&self) -> Result<SystemTime, Error> {
Ok(parse_system_time(&self.value).unwrap_or(UNIX_EPOCH))
Ok(parse_system_time_for_codec(&self.value).unwrap_or(UNIX_EPOCH))
}
pub fn as_integer(&self) -> Result<i32, Error> {
Ok(string_to_f64(&self.value).map_or(0, floor_to_i32))
@@ -854,7 +854,7 @@ impl OSDDate {
Ok(i64::from(unix_seconds_u32(self.value)))
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(format_system_time(self.value))
Ok(format_system_time_for_codec(self.value))
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(unix_seconds_u32(self.value))
@@ -1414,7 +1414,7 @@ fn real_to_u64(value: f64) -> u64 {
value.round_ties_even() as u64
}
}
fn format_real(value: f64) -> String {
pub(crate) fn format_real_for_codec(value: f64) -> String {
if value.is_nan() {
"NaN".to_owned()
} else if value == f64::INFINITY {
@@ -1438,7 +1438,7 @@ fn unix_seconds_u32(value: SystemTime) -> u32 {
}
}
fn format_system_time(value: SystemTime) -> String {
pub(crate) fn format_system_time_for_codec(value: SystemTime) -> String {
let (whole, nanos) = match value.duration_since(UNIX_EPOCH) {
Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
Err(error) => {
@@ -1485,7 +1485,7 @@ fn civil_from_unix(seconds: i64) -> (i32, u32, u32, u32, u32, u32) {
)
}
fn parse_system_time(value: &str) -> Option<SystemTime> {
pub(crate) fn parse_system_time_for_codec(value: &str) -> Option<SystemTime> {
let value = value.trim();
let (date, time) = value.split_once('T').or_else(|| value.split_once(' '))?;
let mut date_parts = date.split('-');
@@ -1556,6 +1556,29 @@ fn valid_uri_reference(value: &str) -> bool {
!value.chars().any(char::is_whitespace) && !value.chars().any(char::is_control)
}
fn format_uri_for_codec(value: &str) -> String {
if !value.contains("://") {
return value.to_owned();
}
let mut output = String::with_capacity(value.len());
for character in value.chars() {
match character {
' ' => output.push_str("%20"),
'"' => output.push_str("%22"),
'<' => output.push_str("%3C"),
'>' => output.push_str("%3E"),
character if character.is_control() => {
let mut bytes = [0_u8; 4];
for byte in character.encode_utf8(&mut bytes).as_bytes() {
write!(output, "%{byte:02X}").expect("writing to String is infallible");
}
}
character => output.push(character),
}
}
output
}
fn escape_text(value: &str) -> String {
let mut output = String::with_capacity(value.len() + 2);
output.push('"');
@@ -1597,7 +1620,7 @@ fn json_text_with_defaults(value: &OSD) -> String {
OSD::Undefined => "null".to_owned(),
OSD::Boolean(value) => value.to_string(),
OSD::Integer(value) => value.to_string(),
OSD::Real(value) => format_real(*value),
OSD::Real(value) => format_real_for_codec(*value),
OSD::String(value) => escape_text(value),
OSD::LlsdXml(_) => "null".to_owned(),
OSD::UUID(_) | OSD::Date(_) | OSD::Uri(_) => {