1 //! Retrieve and populate information about userspace.
2
3 use kmr_wire::SetHalInfoRequest;
4 use regex::Regex;
5
6 // The OS version property is of form "12" or "12.1" or "12.1.3".
7 const OS_VERSION_PROPERTY: &str = "ro.build.version.release";
8 const OS_VERSION_REGEX: &str = r"^(?P<major>\d{1,2})(\.(?P<minor>\d{1,2}))?(\.(?P<sub>\d{1,2}))?$";
9
10 // The patchlevel properties are of form "YYYY-MM-DD".
11 pub const OS_PATCHLEVEL_PROPERTY: &str = "ro.build.version.security_patch";
12 const VENDOR_PATCHLEVEL_PROPERTY: &str = "ro.vendor.build.security_patch";
13 const PATCHLEVEL_REGEX: &str = r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$";
14
15 // Just use [`String`] for errors here.
16 type Error = String;
17
18 /// Retrieve a numeric value from a possible match.
extract_u32(value: Option<regex::Match>) -> Result<u32, Error>19 fn extract_u32(value: Option<regex::Match>) -> Result<u32, Error> {
20 match value {
21 Some(m) => {
22 let s = m.as_str();
23 match s.parse::<u32>() {
24 Ok(v) => Ok(v),
25 Err(e) => Err(format!("failed to parse integer: {:?}", e)),
26 }
27 }
28 None => Err("failed to find match".to_string()),
29 }
30 }
31
get_property(name: &str) -> Result<String, Error>32 pub fn get_property(name: &str) -> Result<String, Error> {
33 match rustutils::system_properties::read(name) {
34 Ok(Some(value)) => Ok(value),
35 Ok(None) => Err(format!("no value for property {}", name)),
36 Err(e) => Err(format!("failed to get property {}: {:?}", name, e)),
37 }
38 }
39
40 /// Extract a patchlevel in form YYYYMM from a "YYYY-MM-DD" property value.
extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error>41 pub fn extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error> {
42 let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
43 .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
44
45 let captures = patchlevel_regex
46 .captures(prop_value)
47 .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
48 let year = extract_u32(captures.name("year"))?;
49 let month = extract_u32(captures.name("month"))?;
50 if !(1..=12).contains(&month) {
51 return Err(format!("month out of range: {}", month));
52 }
53 // no day
54 Ok(year * 100 + month)
55 }
56
57 /// Extract a patchlevel in form YYYYMMDD from a "YYYY-MM-DD" property value.
extract_patchlevel(prop_value: &str) -> Result<u32, Error>58 pub fn extract_patchlevel(prop_value: &str) -> Result<u32, Error> {
59 let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
60 .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
61
62 let captures = patchlevel_regex
63 .captures(prop_value)
64 .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
65 let year = extract_u32(captures.name("year"))?;
66 let month = extract_u32(captures.name("month"))?;
67 if !(1..=12).contains(&month) {
68 return Err(format!("month out of range: {}", month));
69 }
70 let day = extract_u32(captures.name("day"))?;
71 if !(1..=31).contains(&day) {
72 return Err(format!("day out of range: {}", day));
73 }
74 Ok(year * 10000 + month * 100 + day)
75 }
76
77 /// Generate HAL information from property values.
populate_hal_info_from( os_version_prop: &str, os_patchlevel_prop: &str, vendor_patchlevel_prop: &str, ) -> Result<SetHalInfoRequest, Error>78 fn populate_hal_info_from(
79 os_version_prop: &str,
80 os_patchlevel_prop: &str,
81 vendor_patchlevel_prop: &str,
82 ) -> Result<SetHalInfoRequest, Error> {
83 let os_version_regex = Regex::new(OS_VERSION_REGEX)
84 .map_err(|e| format!("failed to compile version regexp: {:?}", e))?;
85 let captures = os_version_regex
86 .captures(os_version_prop)
87 .ok_or_else(|| "failed to match OS version regex".to_string())?;
88 let major = extract_u32(captures.name("major"))?;
89 let minor = extract_u32(captures.name("minor")).unwrap_or(0u32);
90 let sub = extract_u32(captures.name("sub")).unwrap_or(0u32);
91 let os_version = (major * 10000) + (minor * 100) + sub;
92
93 Ok(SetHalInfoRequest {
94 os_version,
95 os_patchlevel: extract_truncated_patchlevel(os_patchlevel_prop)?,
96 vendor_patchlevel: extract_patchlevel(vendor_patchlevel_prop)?,
97 })
98 }
99
100 /// Populate a [`SetHalInfoRequest`] based on property values read from the environment.
populate_hal_info() -> Result<SetHalInfoRequest, Error>101 pub fn populate_hal_info() -> Result<SetHalInfoRequest, Error> {
102 let os_version_prop = get_property(OS_VERSION_PROPERTY)
103 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
104 let os_patchlevel_prop = get_property(OS_PATCHLEVEL_PROPERTY)
105 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
106 let vendor_patchlevel_prop = get_property(VENDOR_PATCHLEVEL_PROPERTY)
107 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
108
109 populate_hal_info_from(&os_version_prop, &os_patchlevel_prop, &vendor_patchlevel_prop)
110 }
111
112 #[cfg(test)]
113 mod tests {
114 use super::*;
115 use kmr_wire::SetHalInfoRequest;
116 #[test]
test_hal_info()117 fn test_hal_info() {
118 let tests = vec![
119 (
120 "12",
121 "2021-02-02",
122 "2022-03-04",
123 SetHalInfoRequest {
124 os_version: 120000,
125 os_patchlevel: 202102,
126 vendor_patchlevel: 20220304,
127 },
128 ),
129 (
130 "12.5",
131 "2021-02-02",
132 "2022-03-04",
133 SetHalInfoRequest {
134 os_version: 120500,
135 os_patchlevel: 202102,
136 vendor_patchlevel: 20220304,
137 },
138 ),
139 (
140 "12.5.7",
141 "2021-02-02",
142 "2022-03-04",
143 SetHalInfoRequest {
144 os_version: 120507,
145 os_patchlevel: 202102,
146 vendor_patchlevel: 20220304,
147 },
148 ),
149 ];
150 for (os_version, os_patch, vendor_patch, want) in tests {
151 let got = populate_hal_info_from(os_version, os_patch, vendor_patch).unwrap();
152 assert_eq!(
153 got, want,
154 "Mismatch for input ({}, {}, {})",
155 os_version, os_patch, vendor_patch
156 );
157 }
158 }
159
160 #[test]
test_invalid_hal_info()161 fn test_invalid_hal_info() {
162 let tests = vec![
163 ("xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
164 ("12.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
165 ("12.5.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
166 ("12", "20212-02-02", "2022-03-04", "failed to match patchlevel regex"),
167 ("12", "2021-xx-02", "2022-03-04", "failed to match patchlevel"),
168 ("12", "2021-13-02", "2022-03-04", "month out of range"),
169 ("12", "2022-03-04", "2021-xx-02", "failed to match patchlevel"),
170 ("12", "2022-03-04", "2021-13-02", "month out of range"),
171 ("12", "2022-03-04", "2021-03-32", "day out of range"),
172 ];
173 for (os_version, os_patch, vendor_patch, want_err) in tests {
174 let result = populate_hal_info_from(os_version, os_patch, vendor_patch);
175 assert!(result.is_err());
176 let err = result.unwrap_err();
177 assert!(
178 err.contains(want_err),
179 "Mismatch for input ({}, {}, {}), got error '{}', want '{}'",
180 os_version,
181 os_patch,
182 vendor_patch,
183 err,
184 want_err
185 );
186 }
187 }
188 }
189