opentitanlib/test_utils/
rpc.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4use anyhow::Result;
5use crc::{CRC_32_ISO_HDLC, Crc};
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8use std::time::Duration;
9
10use crate::io::console::ext::{PassFail, PassFailResult};
11use crate::io::console::{ConsoleDevice, ConsoleError, ConsoleExt};
12use crate::regex;
13use crate::test_utils::status::Status;
14
15// Bring in the auto-generated sources.
16include!(env!("ottf"));
17
18pub trait ConsoleSend<T>
19where
20    T: ConsoleDevice + ?Sized,
21{
22    fn send(&self, device: &T) -> Result<String>;
23    fn send_with_crc(&self, device: &T) -> Result<String>;
24    fn send_with_padding(&self, device: &T, max_size: usize) -> Result<String>;
25}
26
27impl<T, U> ConsoleSend<T> for U
28where
29    T: ConsoleDevice + ?Sized,
30    U: Serialize,
31{
32    fn send(&self, device: &T) -> Result<String> {
33        let s = serde_json::to_string(self)?;
34        log::info!("Sending: {}", s);
35        device.write(s.as_bytes())?;
36        Ok(s)
37    }
38
39    fn send_with_crc(&self, device: &T) -> Result<String> {
40        let s = serde_json::to_string(self)?;
41        log::info!("Sending: {}", s);
42        device.write(s.as_bytes())?;
43        let actual_crc = OttfCrc {
44            crc: Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(s.as_bytes()),
45        };
46        let crc_s = actual_crc.send(device)?;
47        Ok(s + &crc_s)
48    }
49
50    fn send_with_padding(&self, device: &T, max_size: usize) -> Result<String> {
51        let mut s = serde_json::to_string(self)?;
52        let pad_len = max_size - s.len();
53        let pad_str = ' '.to_string().repeat(pad_len);
54        s.insert_str(s.len() - 1, &pad_str);
55        device.write(s.as_bytes())?;
56        Ok(s)
57    }
58}
59
60pub trait ConsoleRecv<T>
61where
62    T: ConsoleDevice + ?Sized,
63{
64    fn recv(device: &T, timeout: Duration, quiet: bool, skip_crc: bool) -> Result<Self>
65    where
66        Self: Sized;
67}
68
69impl<T, U> ConsoleRecv<T> for U
70where
71    T: ConsoleDevice + ?Sized,
72    U: DeserializeOwned,
73{
74    fn recv(device: &T, timeout: Duration, quiet: bool, skip_crc: bool) -> Result<Self>
75    where
76        Self: Sized,
77    {
78        let device: &dyn ConsoleDevice = if quiet { &device } else { &device.logged() };
79        let result = device.wait_for_line(
80            PassFail(
81                regex!(r"RESP_OK:(.*) CRC:([0-9]+)"),
82                regex!(r"RESP_ERR:(.*) CRC:([0-9]+)"),
83            ),
84            timeout,
85        )?;
86        println!();
87        match result {
88            PassFailResult::Pass(cap) => {
89                let json_str = &cap[1];
90                let crc_str = &cap[2];
91                if !skip_crc {
92                    check_crc(json_str, crc_str)?;
93                }
94                Ok(serde_json::from_str::<Self>(json_str)?)
95            }
96            PassFailResult::Fail(cap) => {
97                let json_str = &cap[1];
98                let crc_str = &cap[2];
99                check_crc(json_str, crc_str)?;
100                Err(serde_json::from_str::<Status>(json_str)?)?
101            }
102        }
103    }
104}
105
106fn check_crc(json_str: &str, crc_str: &str) -> Result<()> {
107    let crc = crc_str.parse::<u32>()?;
108    let actual_crc = Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(json_str.as_bytes());
109    if crc != actual_crc {
110        return Err(
111            ConsoleError::GenericError("CRC didn't match received json body.".into()).into(),
112        );
113    }
114    Ok(())
115}