| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::fmt::Write; |
| |
| use regex::Regex; |
| |
| use super::Version; |
| use super::GSCTOOL_CMD_NAME; |
| use crate::command_runner::CommandRunner; |
| use crate::context::Context; |
| use crate::error::HwsecError; |
| use crate::output::HwsecOutput; |
| use crate::tpm2::BoardID; |
| use crate::tpm2::FactoryConfig; |
| |
| /// Convert string version representation <epoch>.<major>.<minor> into Version struct. |
| pub fn parse_version(version_string: &str) -> Option<Version> { |
| let mut parts = version_string.split('.'); |
| if parts.clone().count() != 3 { |
| return None; |
| } |
| let epoch = parts.next().unwrap().parse::<u8>().ok()?; |
| let major = parts.next().unwrap().parse::<u8>().ok()?; |
| let minor = parts.next().unwrap().parse::<u8>().ok()?; |
| |
| Some(Version { |
| epoch, |
| major, |
| minor, |
| }) |
| } |
| |
| /// 'gsctool -M [options]' output has format |
| /// |
| /// ... |
| /// <INDEX1>=<VALUE1>\n |
| /// <INDEX2>=<VALUE2>\n |
| /// ... |
| /// |
| /// This function finds the line that starts with the given index, and outputs its value. |
| pub fn get_value_from_gsctool_output<'a>( |
| gsctool_output: &'a str, |
| index: &'a str, |
| ) -> Result<&'a str, HwsecError> { |
| let prefix = index.to_owned() + "="; |
| let Some(line) = gsctool_output |
| .lines() |
| .find(|line| line.starts_with(&prefix)) |
| else { |
| eprintln!("Cannot find a line starts with {}", index); |
| return Err(HwsecError::InternalError); |
| }; |
| match line.split('=').nth(1) { |
| Some(value) => Ok(value), |
| None => { |
| eprintln!("Failed when retrieving value after '='"); |
| Err(HwsecError::InternalError) |
| } |
| } |
| } |
| |
| pub fn run_gsctool_cmd( |
| ctx: &mut impl Context, |
| mut options: Vec<&str>, |
| ) -> Result<HwsecOutput, HwsecError> { |
| if cfg!(feature = "ti50_onboard") { |
| options.push("--dauntless"); |
| } |
| |
| ctx.cmd_runner() |
| .run(GSCTOOL_CMD_NAME, options) |
| .map_err(|_| HwsecError::CommandRunnerError) |
| } |
| |
| pub fn run_metrics_client( |
| ctx: &mut impl Context, |
| options: Vec<&str>, |
| ) -> Result<HwsecOutput, HwsecError> { |
| ctx.cmd_runner() |
| .run("metrics_client", options) |
| .map_err(|_| HwsecError::CommandRunnerError) |
| } |
| |
| pub fn gsctool_cmd_successful(ctx: &mut impl Context, options: Vec<&str>) -> bool { |
| let output = run_gsctool_cmd(ctx, options); |
| output.is_ok() && output.unwrap().status.success() |
| } |
| |
| pub fn u8_slice_to_hex_string(bytes: &[u8]) -> String { |
| let mut s = String::with_capacity(bytes.len() * 2); |
| for &b in bytes { |
| write!(&mut s, "{:02x}", b).unwrap(); |
| } |
| s |
| } |
| |
| /// This function finds the first occurrence of a board id representation |
| /// after the occurrence of the substring "Board ID". |
| /// |
| /// If raw_response does not contain substring "Board ID" |
| /// or there is no board id occurring after the position of that of substring "Board ID", |
| /// this function returns Err(HwsecError::GsctoolResponseBadFormatError). |
| pub fn extract_board_id_from_gsctool_response(raw_response: &str) -> Result<BoardID, HwsecError> { |
| let re: regex::Regex = Regex::new(r"[0-9a-fA-F]{8}:[0-9a-fA-F]{8}:[0-9a-fA-F]{8}").unwrap(); |
| if let Some(board_id_keyword_pos) = raw_response.find("Board ID") { |
| let board_id_str = re |
| .find(&raw_response[board_id_keyword_pos..]) |
| .ok_or(HwsecError::GsctoolResponseBadFormatError)? |
| .as_str(); |
| Ok(BoardID { |
| part_1: u32::from_str_radix(&board_id_str[0..8], 16) |
| .map_err(|_| HwsecError::InternalError)?, |
| part_2: u32::from_str_radix(&board_id_str[9..17], 16) |
| .map_err(|_| HwsecError::InternalError)?, |
| flag: u32::from_str_radix(&board_id_str[18..26], 16) |
| .map_err(|_| HwsecError::InternalError)?, |
| }) |
| } else { |
| Err(HwsecError::GsctoolResponseBadFormatError) |
| } |
| } |
| |
| pub fn get_board_id_with_gsctool(ctx: &mut impl Context) -> Result<BoardID, HwsecError> { |
| let gsctool_raw_response = run_gsctool_cmd(ctx, vec!["--any", "--board_id"])?; |
| let board_id_output = std::str::from_utf8(&gsctool_raw_response.stdout) |
| .map_err(|_| HwsecError::GsctoolResponseBadFormatError)?; |
| extract_board_id_from_gsctool_response(board_id_output) |
| } |
| |
| pub fn clear_terminal() { |
| print!("{esc}[2J{esc}[1;1H", esc = 27 as char); |
| } |
| |
| pub fn extract_factory_config_from_gsctool_response( |
| raw_response: &str, |
| ) -> Result<FactoryConfig, HwsecError> { |
| let re: regex::Regex = Regex::new(r"[0-9a-fA-F]{16}").unwrap(); |
| if let Some(factory_config_keyword_pos) = raw_response.find("raw value:") { |
| let factory_config_str = re |
| .find(&raw_response[factory_config_keyword_pos..]) |
| .ok_or(HwsecError::GsctoolResponseBadFormatError)? |
| .as_str(); |
| let raw = u64::from_str_radix(&factory_config_str[0..16], 16) |
| .map_err(|_| HwsecError::InternalError)?; |
| Ok(FactoryConfig(raw)) |
| } else { |
| Err(HwsecError::GsctoolResponseBadFormatError) |
| } |
| } |
| |
| pub fn get_factory_config_with_gsctool( |
| ctx: &mut impl Context, |
| ) -> Result<FactoryConfig, HwsecError> { |
| let gsctool_raw_response = run_gsctool_cmd(ctx, vec!["-a", "--factory_config"])?; |
| let factory_config_output = std::str::from_utf8(&gsctool_raw_response.stdout) |
| .map_err(|_| HwsecError::GsctoolResponseBadFormatError)?; |
| extract_factory_config_from_gsctool_response(factory_config_output) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::get_value_from_gsctool_output; |
| use super::parse_version; |
| use crate::error::HwsecError; |
| use crate::gsc::Version; |
| |
| #[test] |
| fn test_parse_version_ok() { |
| let result = parse_version("1.2.3"); |
| assert_eq!( |
| result, |
| Some(Version { |
| epoch: 1, |
| major: 2, |
| minor: 3, |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_parse_version_fail_1() { |
| let result = parse_version("a.b.c"); |
| assert_eq!(result, None); |
| } |
| |
| #[test] |
| fn test_parse_version_fail_2() { |
| let result = parse_version("1.23"); |
| assert_eq!(result, None); |
| } |
| |
| #[test] |
| fn test_get_value_from_gsctool_output_ok() { |
| let result = get_value_from_gsctool_output("INDEX=VALUE", "INDEX"); |
| assert_eq!(result, Ok("VALUE")); |
| } |
| |
| #[test] |
| fn test_get_value_from_gsctool_output_ok_multiple_lines() { |
| let result = get_value_from_gsctool_output("INDEX1=VALUE1\nINDEX2=VALUE2", "INDEX2"); |
| assert_eq!(result, Ok("VALUE2")); |
| } |
| |
| #[test] |
| fn test_get_value_from_gsctool_output_empty_value() { |
| let result = get_value_from_gsctool_output("INDEX=", "INDEX"); |
| assert_eq!(result, Ok("")); |
| } |
| |
| #[test] |
| fn test_get_value_from_gsctool_output_fail_index_not_found() { |
| let result = get_value_from_gsctool_output("ABC=", "INDEX"); |
| assert_eq!(result, Err(HwsecError::InternalError)); |
| } |
| } |