use std::time::Duration;
use base::error;
use protobuf::Message;
use remain::sorted;
use system_api::client::OrgChromiumVtpm;
use system_api::vtpm_interface::SendCommandRequest;
use system_api::vtpm_interface::SendCommandResponse;
use thiserror::Error;
use super::virtio::TpmBackend;
const VTPM_DBUS_TIMEOUT: Duration = Duration::from_secs(300);
const TPM_RC_INSUFFICIENT_RESPONSE: &[u8] = &[
    0x80, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x9A, ];
const TPM_RC_FAILURE_RESPONSE: &[u8] = &[
    0x80, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x01, ];
pub struct VtpmProxy {
    dbus_connection: Option<dbus::blocking::Connection>,
    buf: Vec<u8>,
}
impl VtpmProxy {
    pub fn new() -> Self {
        VtpmProxy {
            dbus_connection: None,
            buf: Vec::new(),
        }
    }
    fn get_or_create_dbus_connection(
        &mut self,
    ) -> anyhow::Result<&dbus::blocking::Connection, dbus::Error> {
        match self.dbus_connection {
            Some(ref dbus_connection) => Ok(dbus_connection),
            None => {
                let dbus_connection = dbus::blocking::Connection::new_system()?;
                self.dbus_connection = Some(dbus_connection);
                self.get_or_create_dbus_connection()
            }
        }
    }
    fn try_execute_command(&mut self, command: &[u8]) -> anyhow::Result<(), Error> {
        let dbus_connection = self
            .get_or_create_dbus_connection()
            .map_err(Error::DBusError)?;
        let proxy = dbus_connection.with_proxy(
            "org.chromium.Vtpm",
            "/org/chromium/Vtpm",
            VTPM_DBUS_TIMEOUT,
        );
        let mut proto = SendCommandRequest::new();
        proto.set_command(command.to_vec());
        let bytes = proto.write_to_bytes().map_err(Error::ProtobufError)?;
        let resp_bytes = proxy.send_command(bytes).map_err(Error::DBusError)?;
        let response =
            SendCommandResponse::parse_from_bytes(&resp_bytes).map_err(Error::ProtobufError)?;
        self.buf = response.response().to_vec();
        Ok(())
    }
}
impl Default for VtpmProxy {
    fn default() -> Self {
        Self::new()
    }
}
impl TpmBackend for VtpmProxy {
    fn execute_command<'a>(&'a mut self, command: &[u8]) -> &'a [u8] {
        match self.try_execute_command(command) {
            Ok(()) => &self.buf,
            Err(e) => {
                error!("{:#}", e);
                match e {
                    Error::ProtobufError(_) => TPM_RC_INSUFFICIENT_RESPONSE,
                    Error::DBusError(_) => TPM_RC_FAILURE_RESPONSE,
                }
            }
        }
    }
}
#[sorted]
#[derive(Error, Debug)]
enum Error {
    #[error("D-Bus failure: {0:#}")]
    DBusError(dbus::Error),
    #[error("protocol buffers failure: {0:#}")]
    ProtobufError(protobuf::Error),
}