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    fn send_with_padding_and_crc(&self, device: &T, max_size: usize, quiet: bool)
26    -> Result<String>;
27}
28
29impl<T, U> ConsoleSend<T> for U
30where
31    T: ConsoleDevice + ?Sized,
32    U: Serialize,
33{
34    fn send(&self, device: &T) -> Result<String> {
35        let s = serde_json::to_string(self)?;
36        log::info!("Sending: {}", s);
37        device.write(s.as_bytes())?;
38        Ok(s)
39    }
40
41    fn send_with_crc(&self, device: &T) -> Result<String> {
42        let s = serde_json::to_string(self)?;
43        log::info!("Sending: {}", s);
44        device.write(s.as_bytes())?;
45        let actual_crc = OttfCrc {
46            crc: Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(s.as_bytes()),
47        };
48        let crc_s = actual_crc.send(device)?;
49        Ok(s + &crc_s)
50    }
51
52    fn send_with_padding(&self, device: &T, max_size: usize) -> Result<String> {
53        let mut s = serde_json::to_string(self)?;
54        let pad_len = max_size - s.len();
55        let pad_str = ' '.to_string().repeat(pad_len);
56        s.insert_str(s.len() - 1, &pad_str);
57        device.write(s.as_bytes())?;
58        Ok(s)
59    }
60
61    fn send_with_padding_and_crc(
62        &self,
63        device: &T,
64        max_size: usize,
65        quiet: bool,
66    ) -> Result<String> {
67        // Craft the UJSON string and pad with whitespace.
68        let mut s = serde_json::to_string(self)?;
69        let pad_len = max_size - s.len();
70        let pad_str = ' '.to_string().repeat(pad_len);
71        s.insert_str(s.len() - 1, &pad_str);
72        // Craft the CRC and pad with whitespace.
73        let actual_crc = OttfCrc {
74            crc: Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(s.as_bytes()),
75        };
76        let max_crc = OttfCrc { crc: u32::MAX };
77        let mut crc_s = serde_json::to_string(&actual_crc)?;
78        let max_crc_s = serde_json::to_string(&max_crc)?;
79        let crc_pad_str = ' '.to_string().repeat(max_crc_s.len() - crc_s.len());
80        crc_s.insert_str(crc_s.len() - 1, &crc_pad_str);
81        // Send bytes to the DUT.
82        if !quiet {
83            log::info!("Sending: {}", s.clone() + &crc_s);
84        }
85        device.write(s.as_bytes())?;
86        device.write(crc_s.as_bytes())?;
87        Ok(s + &crc_s)
88    }
89}
90
91pub trait ConsoleRecv<T>
92where
93    T: ConsoleDevice + ?Sized,
94{
95    fn recv(device: &T, timeout: Duration, quiet: bool, skip_crc: bool) -> Result<Self>
96    where
97        Self: Sized;
98}
99
100impl<T, U> ConsoleRecv<T> for U
101where
102    T: ConsoleDevice + ?Sized,
103    U: DeserializeOwned,
104{
105    fn recv(device: &T, timeout: Duration, quiet: bool, skip_crc: bool) -> Result<Self>
106    where
107        Self: Sized,
108    {
109        let device: &dyn ConsoleDevice = if quiet { &device } else { &device.logged() };
110        let result = device.wait_for_line(
111            PassFail(
112                regex!(r"RESP_OK:(.*) CRC:([0-9]+)"),
113                regex!(r"RESP_ERR:(.*) CRC:([0-9]+)"),
114            ),
115            timeout,
116        )?;
117        println!();
118        match result {
119            PassFailResult::Pass(cap) => {
120                let json_str = &cap[1];
121                let crc_str = &cap[2];
122                if !skip_crc {
123                    check_crc(json_str, crc_str)?;
124                }
125                Ok(serde_json::from_str::<Self>(json_str)?)
126            }
127            PassFailResult::Fail(cap) => {
128                let json_str = &cap[1];
129                let crc_str = &cap[2];
130                check_crc(json_str, crc_str)?;
131                Err(serde_json::from_str::<Status>(json_str)?)?
132            }
133        }
134    }
135}
136
137fn check_crc(json_str: &str, crc_str: &str) -> Result<()> {
138    let crc = crc_str.parse::<u32>()?;
139    let actual_crc = Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(json_str.as_bytes());
140    if crc != actual_crc {
141        return Err(
142            ConsoleError::GenericError("CRC didn't match received json body.".into()).into(),
143        );
144    }
145    Ok(())
146}