use crate::errors::{Error, Result};
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 const MAX_WIRE_NAME_LEN: usize = 1024;
pub const MAX_WIRE_VALUE_LEN: usize = 8192;
pub fn validate_value_len(name: &str, value: &str) -> Result<()> {
reject_value_nul(value)?;
if value.len() >= PROP_VALUE_MAX && !name.starts_with("ro.") {
return Err(Error::InvalidArgument(format!(
"value too long: {} bytes (max {} for non-'ro.' properties)",
value.len(),
PROP_VALUE_MAX - 1
)));
}
Ok(())
}
#[cfg(feature = "builder")]
pub(crate) fn validate_short_value_len(value: &str) -> Result<()> {
reject_value_nul(value)?;
if value.len() >= PROP_VALUE_MAX {
return Err(Error::InvalidArgument(format!(
"value too long: {} bytes (max {} for an in-place update)",
value.len(),
PROP_VALUE_MAX - 1
)));
}
Ok(())
}
fn reject_value_nul(value: &str) -> Result<()> {
if value.as_bytes().contains(&0) {
return Err(Error::InvalidArgument(
"value must not contain NUL bytes".into(),
));
}
Ok(())
}
#[cfg(feature = "builder")]
pub(crate) fn validate_no_interior_nul(kind: &str, s: &str) -> Result<()> {
if s.as_bytes().contains(&0) {
return Err(Error::InvalidArgument(format!(
"{kind} must not contain NUL bytes: \"{}\"",
s.escape_default()
)));
}
Ok(())
}
pub fn validate_property_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(Error::InvalidArgument("name is empty".into()));
}
if name.starts_with('.') {
return Err(Error::InvalidArgument(format!(
"name cannot start with '.': {name}"
)));
}
if name.ends_with('.') {
return Err(Error::InvalidArgument(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(Error::InvalidArgument(format!(
"invalid char {c:?} in name: {name}"
)));
}
if c == '.' && prev_dot {
return Err(Error::InvalidArgument(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());
}
#[cfg(feature = "builder")]
#[test]
fn short_value_len_ignores_ro_exemption() {
assert!(validate_short_value_len("x".repeat(PROP_VALUE_MAX - 1).as_str()).is_ok());
assert!(validate_short_value_len("x".repeat(PROP_VALUE_MAX).as_str()).is_err());
assert!(validate_short_value_len("a\0bc").is_err());
}
#[test]
fn value_rejects_interior_nul() {
assert!(validate_value_len("foo", "a\0bc").is_err());
assert!(validate_value_len("ro.foo", "a\0bc").is_err());
assert!(validate_value_len("foo", "\0").is_err());
}
#[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_dot() {
assert!(validate_property_name(".leading.dot").is_err());
}
#[test]
fn name_allows_leading_symbols_like_aosp() {
assert!(validate_property_name("-leading.dash").is_ok());
assert!(validate_property_name("@leading.at").is_ok());
assert!(validate_property_name(":leading.colon").is_ok());
assert!(validate_property_name("_internal").is_ok());
}
#[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());
}
}