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