blob: 060509c0378878720eab19b1066a7cf3ab7adf2f [file] [log] [blame]
//! Retrieve and populate information about userspace.
use kmr_common::wire::SetHalInfoRequest;
use regex::Regex;
// The OS version property is of form "12" or "12.1" or "12.1.3".
const OS_VERSION_PROPERTY: &str = "ro.build.version.release";
const OS_VERSION_REGEX: &str = r"^(?P<major>\d{1,2})(\.(?P<minor>\d{1,2}))?(\.(?P<sub>\d{1,2}))?$";
// The patchlevel properties are of form "YYYY-MM-DD".
pub const OS_PATCHLEVEL_PROPERTY: &str = "ro.build.version.security_patch";
const VENDOR_PATCHLEVEL_PROPERTY: &str = "ro.vendor.build.security_patch";
const PATCHLEVEL_REGEX: &str = r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$";
// Just use [`String`] for errors here.
type Error = String;
/// Retrieve a numeric value from a possible match.
fn extract_u32(value: Option<regex::Match>) -> Result<u32, Error> {
match value {
Some(m) => {
let s = m.as_str();
match s.parse::<u32>() {
Ok(v) => Ok(v),
Err(e) => Err(format!("failed to parse integer: {:?}", e)),
}
}
None => Err("failed to find match".to_string()),
}
}
pub fn get_property(name: &str) -> Result<String, Error> {
match rustutils::system_properties::read(name) {
Ok(Some(value)) => Ok(value),
Ok(None) => Err(format!("no value for property {}", name)),
Err(e) => Err(format!("failed to get property {}: {:?}", name, e)),
}
}
/// Extract a patchlevel in form YYYYMM from a "YYYY-MM-DD" property value.
pub fn extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error> {
let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
.map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
let captures = patchlevel_regex
.captures(prop_value)
.ok_or_else(|| "failed to match patchlevel regex".to_string())?;
let year = extract_u32(captures.name("year"))?;
let month = extract_u32(captures.name("month"))?;
if !(1..=12).contains(&month) {
return Err(format!("month out of range: {}", month));
}
// no day
Ok(year * 100 + month)
}
/// Extract a patchlevel in form YYYYMMDD from a "YYYY-MM-DD" property value.
pub fn extract_patchlevel(prop_value: &str) -> Result<u32, Error> {
let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
.map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
let captures = patchlevel_regex
.captures(prop_value)
.ok_or_else(|| "failed to match patchlevel regex".to_string())?;
let year = extract_u32(captures.name("year"))?;
let month = extract_u32(captures.name("month"))?;
if !(1..=12).contains(&month) {
return Err(format!("month out of range: {}", month));
}
let day = extract_u32(captures.name("day"))?;
if !(1..=31).contains(&day) {
return Err(format!("day out of range: {}", day));
}
Ok(year * 10000 + month * 100 + day)
}
/// Generate HAL information from property values.
fn populate_hal_info_from(
os_version_prop: &str,
os_patchlevel_prop: &str,
vendor_patchlevel_prop: &str,
) -> Result<SetHalInfoRequest, Error> {
let os_version_regex = Regex::new(OS_VERSION_REGEX)
.map_err(|e| format!("failed to compile version regexp: {:?}", e))?;
let captures = os_version_regex
.captures(os_version_prop)
.ok_or_else(|| "failed to match OS version regex".to_string())?;
let major = extract_u32(captures.name("major"))?;
let minor = extract_u32(captures.name("minor")).unwrap_or(0u32);
let sub = extract_u32(captures.name("sub")).unwrap_or(0u32);
let os_version = (major * 10000) + (minor * 100) + sub;
Ok(SetHalInfoRequest {
os_version,
os_patchlevel: extract_truncated_patchlevel(os_patchlevel_prop)?,
vendor_patchlevel: extract_patchlevel(vendor_patchlevel_prop)?,
})
}
/// Populate a [`SetHalInfoRequest`] based on property values read from the environment.
pub fn populate_hal_info() -> Result<SetHalInfoRequest, Error> {
let os_version_prop = get_property(OS_VERSION_PROPERTY)
.map_err(|e| format!("failed to retrieve property: {:?}", e))?;
let os_patchlevel_prop = get_property(OS_PATCHLEVEL_PROPERTY)
.map_err(|e| format!("failed to retrieve property: {:?}", e))?;
let vendor_patchlevel_prop = get_property(VENDOR_PATCHLEVEL_PROPERTY)
.map_err(|e| format!("failed to retrieve property: {:?}", e))?;
populate_hal_info_from(&os_version_prop, &os_patchlevel_prop, &vendor_patchlevel_prop)
}
#[cfg(test)]
mod tests {
use super::*;
use kmr_common::wire::SetHalInfoRequest;
#[test]
fn test_hal_info() {
let tests = vec![
(
"12",
"2021-02-02",
"2022-03-04",
SetHalInfoRequest {
os_version: 120000,
os_patchlevel: 202102,
vendor_patchlevel: 20220304,
},
),
(
"12.5",
"2021-02-02",
"2022-03-04",
SetHalInfoRequest {
os_version: 120500,
os_patchlevel: 202102,
vendor_patchlevel: 20220304,
},
),
(
"12.5.7",
"2021-02-02",
"2022-03-04",
SetHalInfoRequest {
os_version: 120507,
os_patchlevel: 202102,
vendor_patchlevel: 20220304,
},
),
];
for (os_version, os_patch, vendor_patch, want) in tests {
let got = populate_hal_info_from(os_version, os_patch, vendor_patch).unwrap();
assert_eq!(
got, want,
"Mismatch for input ({}, {}, {})",
os_version, os_patch, vendor_patch
);
}
}
#[test]
fn test_invalid_hal_info() {
let tests = vec![
("xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
("12.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
("12.5.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
("12", "20212-02-02", "2022-03-04", "failed to match patchlevel regex"),
("12", "2021-xx-02", "2022-03-04", "failed to match patchlevel"),
("12", "2021-13-02", "2022-03-04", "month out of range"),
("12", "2022-03-04", "2021-xx-02", "failed to match patchlevel"),
("12", "2022-03-04", "2021-13-02", "month out of range"),
("12", "2022-03-04", "2021-03-32", "day out of range"),
];
for (os_version, os_patch, vendor_patch, want_err) in tests {
let result = populate_hal_info_from(os_version, os_patch, vendor_patch);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.contains(want_err),
"Mismatch for input ({}, {}, {}), got error '{}', want '{}'",
os_version,
os_patch,
vendor_patch,
err,
want_err
);
}
}
}