| // 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::collections::VecDeque; |
| use std::fmt::Write; |
| |
| use crate::command_runner::CommandRunner; |
| use crate::gsc::RmaSnBits; |
| use crate::gsc::GSCTOOL_CMD_NAME; |
| use crate::output::HwsecOutput; |
| use crate::output::HwsecStatus; |
| use crate::tpm2::tests::split_into_hex_strtok; |
| use crate::tpm2::BoardID; |
| |
| // For any member variable x in MockCommandInput: |
| // x = Some(_) means that we would check the correspondence; |
| // otherwise, we don't really care about its exact value. |
| // To know more, take a glance at impl CommandRunner for MockCommandRunner. |
| pub struct MockCommandInput { |
| pub cmd_name: Option<String>, |
| pub args: Option<Vec<String>>, |
| } |
| |
| impl MockCommandInput { |
| pub fn new(cmd_name: &str, args: Vec<&str>) -> Self { |
| Self { |
| cmd_name: Some(cmd_name.to_owned()), |
| args: Some(args.iter().map(|&s| s.into()).collect()), |
| } |
| } |
| } |
| |
| pub struct MockCommandOutput { |
| pub result: Result<HwsecOutput, std::io::Error>, |
| } |
| |
| impl MockCommandOutput { |
| pub fn new(exit_status: i32, out: &str, err: &str) -> Self { |
| Self { |
| result: Ok(HwsecOutput { |
| status: HwsecStatus::from_raw(exit_status), |
| stdout: out.to_owned().as_bytes().to_vec(), |
| stderr: err.to_owned().as_bytes().to_vec(), |
| }), |
| } |
| } |
| } |
| |
| pub struct MockCommandRunner { |
| expectations: VecDeque<(MockCommandInput, MockCommandOutput)>, |
| } |
| |
| impl Default for MockCommandRunner { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| fn u8_slice_to_upper_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 |
| } |
| |
| impl MockCommandRunner { |
| pub fn new() -> Self { |
| MockCommandRunner { |
| expectations: VecDeque::new(), |
| } |
| } |
| pub fn add_expectation(&mut self, inp: MockCommandInput, out: MockCommandOutput) { |
| self.expectations.push_back((inp, out)); |
| } |
| pub fn set_trunksd_running(&mut self, status: bool) { |
| self.add_expectation( |
| MockCommandInput::new("status", vec!["trunksd"]), |
| MockCommandOutput::new( |
| 0, |
| if status { |
| "trunksd start/running, process 17302" |
| } else { |
| "trunksd stop/waiting" |
| }, |
| "", |
| ), |
| ); |
| } |
| pub fn add_tpm_interaction( |
| &mut self, |
| cmd_name: &str, |
| flag: Vec<&str>, |
| hex_str_tokens: Vec<&str>, |
| exit_status: i32, |
| out: &str, |
| err: &str, |
| ) { |
| self.add_expectation( |
| MockCommandInput::new(cmd_name, [&flag[..], &hex_str_tokens[..]].concat()), |
| MockCommandOutput::new(exit_status, out, err), |
| ); |
| } |
| pub fn add_gsctool_interaction( |
| &mut self, |
| mut flag: Vec<&str>, |
| exit_status: i32, |
| out: &str, |
| err: &str, |
| ) { |
| if cfg!(feature = "ti50_onboard") { |
| flag.push("--dauntless"); |
| } |
| |
| self.add_expectation( |
| MockCommandInput::new(GSCTOOL_CMD_NAME, flag), |
| MockCommandOutput::new(exit_status, out, err), |
| ); |
| } |
| pub fn add_metrics_client_expectation(&mut self, event_id: u64) { |
| use crate::gsc::GSC_METRICS_PREFIX; |
| self.add_expectation( |
| MockCommandInput::new( |
| "metrics_client", |
| vec![ |
| "-s", |
| &format!("{}.FlashLog", GSC_METRICS_PREFIX), |
| &format!("0x{:02x}", event_id), |
| ], |
| ), |
| MockCommandOutput::new(0, "", ""), |
| ); |
| } |
| pub fn add_successful_generic_read_board_id_interaction(&mut self, board_id: BoardID) { |
| self.add_tpm_interaction( |
| "trunks_send", |
| vec!["--raw"], |
| split_into_hex_strtok( |
| "80 02 00 00 00 23 00 00 \ |
| 01 4e 01 3f ff 00 01 3f \ |
| ff 00 00 00 00 09 40 00 \ |
| 00 09 00 00 00 00 00 00 \ |
| 0c 00 00", |
| ), |
| 0, |
| &format!( |
| "800200000021000000000000000E000C{:08X}{:08X}{:08X}0000010000", |
| board_id.part_1.swap_bytes(), |
| board_id.part_2.swap_bytes(), |
| board_id.flag.swap_bytes(), |
| ), |
| "", |
| ); |
| } |
| pub fn add_successful_generic_read_board_id_arbitary_interaction(&mut self) { |
| // Use this when not in want of specifying board id. |
| // Reading board id with mock context |
| // after calling this function with would return |
| // BoardID { |
| // part_1: 0x4d524646, |
| // part_2: 0xb2adb9b9, |
| // flag: 0x00007f7f |
| // } |
| self.add_successful_generic_read_board_id_interaction(BoardID { |
| part_1: 0x4d524646, |
| part_2: 0xb2adb9b9, |
| flag: 0x00007f7f, |
| }); |
| } |
| pub fn add_successful_gsctool_read_board_id_interaction(&mut self, board_id: BoardID) { |
| self.add_gsctool_interaction( |
| vec!["--any", "--board_id"], |
| 0, |
| &format!( |
| "finding_device 18d1:5014\n\ |
| Found device.\n\ |
| found interface 3 endpoint 4, chunk_len 64\n\ |
| READY\n\ |
| -------\n\ |
| Board ID space: {:08x}:{:08x}:{:08x}\n", |
| board_id.part_1, board_id.part_2, board_id.flag |
| ), |
| "", |
| ); |
| } |
| pub fn add_successful_gsctool_read_board_id_arbitary_interaction(&mut self) { |
| // Use this when not in want of specifying a board id. |
| // Reading board id with mock context |
| // after calling this function with would return |
| // BoardID { |
| // part_1: 0x43425559, |
| // part_2: 0xbcbdaaa6, |
| // flag: 0x00007f80 |
| // } |
| self.add_successful_gsctool_read_board_id_interaction(BoardID { |
| part_1: 0x43425559, |
| part_2: 0xbcbdaaa6, |
| flag: 0x00007f80, |
| }); |
| } |
| pub fn add_successful_gsc_get_name_arbitary_interaction(&mut self) { |
| self.add_successful_gsctool_read_board_id_arbitary_interaction(); |
| } |
| pub fn add_successful_gsc_read_rma_sn_bits_interaction_non_generic_tpm2( |
| &mut self, |
| rma_sn_bits: RmaSnBits, |
| ) { |
| self.set_trunksd_running(true); |
| self.add_tpm_interaction( |
| "trunks_send", |
| vec!["--raw"], |
| split_into_hex_strtok( |
| "80 02 00 00 00 23 00 00 \ |
| 01 4e 01 3f ff 01 01 3f \ |
| ff 01 00 00 00 09 40 00 \ |
| 00 09 00 00 00 00 00 00 \ |
| 10 00 00", |
| ), |
| 0, |
| &format!( |
| "80020000002500000000000000120010{}{:02X}{}0000010000", |
| u8_slice_to_upper_hex_string(&rma_sn_bits.sn_data_version), |
| rma_sn_bits.rma_status, |
| u8_slice_to_upper_hex_string(&rma_sn_bits.sn_bits), |
| ), |
| "", |
| ); |
| } |
| pub fn add_successful_gsc_read_rma_sn_bits_arbitary_interaction(&mut self) { |
| // Use this when not in want of specifying rma sn bits. |
| // Reading rma sn bits with mock context |
| // after calling this function with would return |
| // RmaSnBits { |
| // sn_data_version: [0x0f, 0xff, 0xff], |
| // rma_status: 0xff, |
| // sn_bits: [0x87, 0x7f, 0x50, 0xd2, 0x08, 0xec, 0x89, 0xe9, 0xc1, 0x69, 0x1f, 0x54], |
| // } |
| self.add_successful_gsc_read_rma_sn_bits_interaction_non_generic_tpm2(RmaSnBits { |
| sn_data_version: [0x0f, 0xff, 0xff], |
| rma_status: 0xff, |
| sn_bits: [ |
| 0x87, 0x7f, 0x50, 0xd2, 0x08, 0xec, 0x89, 0xe9, 0xc1, 0x69, 0x1f, 0x54, |
| ], |
| }); |
| } |
| } |
| |
| impl CommandRunner for MockCommandRunner { |
| fn run(&mut self, cmd_name: &str, args: Vec<&str>) -> Result<HwsecOutput, std::io::Error> { |
| assert!( |
| !self.expectations.is_empty(), |
| "Failed to pop front from queue -- it's empty!" |
| ); |
| let io_pair = self.expectations.pop_front().unwrap(); |
| let inp = io_pair.0; |
| let out = io_pair.1; |
| if let Some(inp_name) = inp.cmd_name { |
| assert_eq!(cmd_name, inp_name); |
| } |
| if let Some(inp_args) = inp.args { |
| assert_eq!(args, inp_args); |
| } |
| out.result |
| } |
| fn output(&mut self, cmd_name: &str, args: Vec<&str>) -> Result<String, std::io::Error> { |
| let run_result = self.run(cmd_name, args)?; |
| Ok(String::from_utf8_lossy(&run_result.stdout).to_string()) |
| } |
| } |
| |
| impl Drop for MockCommandRunner { |
| fn drop(&mut self) { |
| assert!(self.expectations.is_empty()); |
| } |
| } |