pub const PROP_VALUE_MAX: usize = 92;
pub const PROP_NAME_MAX: usize = 32;
pub const PROP_MSG_SETPROP: u32 = 1;
pub const PROP_MSG_SETPROP2: u32 = 0x00020001;
pub const PROP_SUCCESS: i32 = 0;
pub const PROP_ERROR: i32 = -1;
pub fn validate_value_len(name: &str, value: &str) -> Result<(), String> {
if value.len() >= PROP_VALUE_MAX && !name.starts_with("ro.") {
return Err(format!(
"value too long: {} bytes (max {} for non-'ro.' properties)",
value.len(),
PROP_VALUE_MAX
));
}
Ok(())
}
pub fn validate_property_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("name is empty".into());
}
let first = name.chars().next().expect("non-empty");
if !(first.is_ascii_alphanumeric() || first == '_') {
return Err(format!("name must start with alphanumeric or '_': {name}"));
}
if name.ends_with('.') {
return Err(format!("name cannot end with '.': {name}"));
}
let mut prev_dot = false;
for c in name.chars() {
let ok = c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '-' | '@' | ':');
if !ok {
return Err(format!("invalid char {c:?} in name: {name}"));
}
if c == '.' && prev_dot {
return Err(format!("consecutive '.' in name: {name}"));
}
prev_dot = c == '.';
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_len_short_ok() {
assert!(validate_value_len("foo", "x".repeat(PROP_VALUE_MAX - 1).as_str()).is_ok());
}
#[test]
fn value_len_at_cap_rejected_for_non_ro() {
assert!(validate_value_len("foo", "x".repeat(PROP_VALUE_MAX).as_str()).is_err());
}
#[test]
fn value_len_at_cap_ok_for_ro() {
assert!(validate_value_len("ro.foo", "x".repeat(PROP_VALUE_MAX * 10).as_str()).is_ok());
}
#[test]
fn name_basic_ok() {
assert!(validate_property_name("ro.build.version.sdk").is_ok());
assert!(validate_property_name("_internal.flag").is_ok());
assert!(validate_property_name("a").is_ok());
}
#[test]
fn name_rejects_empty() {
assert!(validate_property_name("").is_err());
}
#[test]
fn name_rejects_leading_non_alnum() {
assert!(validate_property_name(".leading.dot").is_err());
assert!(validate_property_name("-leading.dash").is_err());
assert!(validate_property_name("@leading.at").is_err());
assert!(validate_property_name(":leading.colon").is_err());
}
#[test]
fn name_rejects_trailing_dot() {
assert!(validate_property_name("trailing.dot.").is_err());
}
#[test]
fn name_rejects_consecutive_dots() {
assert!(validate_property_name("double..dot").is_err());
}
#[test]
fn name_rejects_invalid_chars() {
assert!(validate_property_name("has space").is_err());
assert!(validate_property_name("has/slash").is_err());
}
}