vmgs_broker/
client.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The Vmgs worker will send messages to the Vmgs dispatch, allowing
5//! tasks to queue for the dispatcher to handle synchronously
6
7use crate::broker::VmgsBrokerError;
8use crate::broker::VmgsBrokerRpc;
9use inspect::Inspect;
10use mesh::MeshPayload;
11use mesh::rpc::RpcError;
12use mesh::rpc::RpcSend;
13use thiserror::Error;
14use tracing::instrument;
15use vmgs::VmgsFileInfo;
16use vmgs_format::FileId;
17
18/// VMGS broker errors.
19#[derive(Debug, Error)]
20#[non_exhaustive]
21pub enum VmgsClientError {
22    /// VMGS broker is offline
23    #[error("broker is offline")]
24    BrokerOffline(#[source] RpcError),
25    /// VMGS error
26    #[error("vmgs error")]
27    Vmgs(#[source] VmgsBrokerError),
28}
29
30impl From<RpcError> for VmgsClientError {
31    fn from(value: RpcError) -> Self {
32        match value {
33            RpcError::Channel(e) => VmgsClientError::BrokerOffline(RpcError::Channel(e)),
34        }
35    }
36}
37
38impl From<RpcError<VmgsBrokerError>> for VmgsClientError {
39    fn from(value: RpcError<VmgsBrokerError>) -> Self {
40        match value {
41            RpcError::Call(e) => VmgsClientError::Vmgs(e),
42            RpcError::Channel(e) => VmgsClientError::BrokerOffline(RpcError::Channel(e)),
43        }
44    }
45}
46
47/// Client to interact with a backend-agnostic VMGS instance.
48#[derive(Clone, Inspect, MeshPayload)]
49pub struct VmgsClient {
50    #[inspect(flatten, send = "VmgsBrokerRpc::Inspect")]
51    pub(crate) control: mesh::Sender<VmgsBrokerRpc>,
52}
53
54impl VmgsClient {
55    /// Get allocated and valid bytes from File Control Block for file_id.
56    #[instrument(skip_all, fields(file_id))]
57    pub async fn get_file_info(&self, file_id: FileId) -> Result<VmgsFileInfo, VmgsClientError> {
58        let res = self
59            .control
60            .call_failable(VmgsBrokerRpc::GetFileInfo, file_id.into())
61            .await?;
62
63        Ok(res)
64    }
65
66    /// Reads the specified `file_id`.
67    #[instrument(skip_all, fields(file_id))]
68    pub async fn read_file(&self, file_id: FileId) -> Result<Vec<u8>, VmgsClientError> {
69        let res = self
70            .control
71            .call_failable(VmgsBrokerRpc::ReadFile, file_id.into())
72            .await?;
73
74        Ok(res)
75    }
76
77    /// Writes `buf` to a file_id.
78    ///
79    /// NOTE: It is an error to overwrite a previously encrypted FileId with
80    /// plaintext data.
81    #[instrument(skip_all, fields(file_id))]
82    pub async fn write_file(&self, file_id: FileId, buf: Vec<u8>) -> Result<(), VmgsClientError> {
83        self.control
84            .call_failable(VmgsBrokerRpc::WriteFile, (file_id.into(), buf))
85            .await?;
86
87        Ok(())
88    }
89
90    /// If VMGS has been configured with encryption, encrypt + write `bug` to
91    /// the specified `file_id`. Otherwise, perform a regular plaintext write
92    /// instead.
93    #[cfg(with_encryption)]
94    #[instrument(skip_all, fields(file_id))]
95    pub async fn write_file_encrypted(
96        &self,
97        file_id: FileId,
98        buf: Vec<u8>,
99    ) -> Result<(), VmgsClientError> {
100        self.control
101            .call_failable(VmgsBrokerRpc::WriteFileEncrypted, (file_id.into(), buf))
102            .await?;
103
104        Ok(())
105    }
106
107    /// Save the in-memory VMGS file metadata.
108    ///
109    /// This saved state can be used alongside `open_from_saved` to obtain a
110    /// new `Vmgs` instance _without_ needing to invoke any IOs on the
111    /// underlying storage.
112    pub async fn save(&self) -> Result<vmgs::save_restore::state::SavedVmgsState, VmgsClientError> {
113        let res = self.control.call(VmgsBrokerRpc::Save, ()).await?;
114        Ok(res)
115    }
116}