• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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