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