Skip to main content

aranya_client/client/
device.rs

1use std::slice;
2
3use aranya_daemon_api as api;
4use aranya_id::custom_id;
5use serde::{Deserialize, Serialize};
6use tracing::instrument;
7
8use crate::{
9    client::{create_ctx, ChanOp, Client, Label, LabelId, Labels, Role, RoleId},
10    error::{aranya_error, IpcError, Result},
11    util::{impl_slice_iter_wrapper, ApiConv as _, ApiId},
12};
13
14/// A device's public key bundle.
15#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
16#[serde(transparent)]
17pub struct PublicKeyBundle(api::PublicKeyBundle);
18
19/// See [`PublicKeyBundle`].
20#[deprecated(note = "use `PublicKeyBundle`")]
21pub type KeyBundle = PublicKeyBundle;
22
23impl PublicKeyBundle {
24    #[doc(hidden)]
25    pub fn from_api(api: api::PublicKeyBundle) -> Self {
26        Self(api)
27    }
28
29    #[doc(hidden)]
30    pub fn into_api(self) -> api::PublicKeyBundle {
31        self.0
32    }
33
34    /// Return public encryption key bytes.
35    pub fn encryption(&self) -> &[u8] {
36        &self.0.encryption
37    }
38}
39
40custom_id! {
41    /// Uniquely identifies a device.
42    pub struct DeviceId;
43}
44impl ApiId<api::DeviceId> for DeviceId {}
45
46/// A list of [`DeviceId`].
47#[derive(Debug)]
48pub struct Devices {
49    pub(super) data: Box<[DeviceId]>,
50}
51
52impl Devices {
53    /// Returns an iterator over the [`DeviceId`]s.
54    pub fn iter(&self) -> IterDevices<'_> {
55        IterDevices(self.data.iter())
56    }
57
58    #[doc(hidden)]
59    pub fn __data(&self) -> &[DeviceId] {
60        &self.data
61    }
62}
63
64/// An iterator over [`DeviceId`]s.
65#[derive(Clone, Debug)]
66pub struct IterDevices<'a>(slice::Iter<'a, DeviceId>);
67
68impl_slice_iter_wrapper!(IterDevices<'a> for DeviceId);
69
70/// Represents an Aranya device
71#[derive(Debug)]
72pub struct Device<'a> {
73    pub(super) client: &'a Client,
74    pub(super) id: api::DeviceId,
75    pub(super) team_id: api::TeamId,
76}
77
78impl Device<'_> {
79    /// Returns the device's globally unique ID.
80    pub fn id(&self) -> DeviceId {
81        DeviceId::from_api(self.id)
82    }
83
84    /// See [`Self::public_key_bundle`].
85    #[deprecated(note = "Use `public_key_bundle`.")]
86    pub async fn keybundle(&self) -> Result<PublicKeyBundle> {
87        self.public_key_bundle().await
88    }
89
90    /// Returns device's public key bundle.
91    pub async fn public_key_bundle(&self) -> Result<PublicKeyBundle> {
92        self.client
93            .daemon
94            .device_public_key_bundle(create_ctx(), self.team_id, self.id)
95            .await
96            .map_err(IpcError::new)?
97            .map_err(aranya_error)
98            .map(PublicKeyBundle::from_api)
99    }
100
101    /// Removes `device` from the team.
102    #[instrument(skip(self))]
103    pub async fn remove_from_team(&self) -> Result<()> {
104        self.client
105            .daemon
106            .remove_device_from_team(create_ctx(), self.team_id, self.id)
107            .await
108            .map_err(IpcError::new)?
109            .map_err(aranya_error)
110    }
111
112    /// Assigns `role` to `device`.
113    #[instrument(skip(self))]
114    pub async fn assign_role(&self, role: RoleId) -> Result<()> {
115        self.client
116            .daemon
117            .assign_role(create_ctx(), self.team_id, self.id, role.into_api())
118            .await
119            .map_err(IpcError::new)?
120            .map_err(aranya_error)
121    }
122
123    /// Revokes `role` from `device`.
124    #[instrument(skip(self))]
125    pub async fn revoke_role(&self, role: RoleId) -> Result<()> {
126        self.client
127            .daemon
128            .revoke_role(create_ctx(), self.team_id, self.id, role.into_api())
129            .await
130            .map_err(IpcError::new)?
131            .map_err(aranya_error)
132    }
133
134    /// Changes the `role` on a `device`
135    #[instrument(skip(self))]
136    pub async fn change_role(&self, old_role: RoleId, new_role: RoleId) -> Result<()> {
137        self.client
138            .daemon
139            .change_role(
140                create_ctx(),
141                self.team_id,
142                self.id,
143                old_role.into_api(),
144                new_role.into_api(),
145            )
146            .await
147            .map_err(IpcError::new)?
148            .map_err(aranya_error)
149    }
150
151    /// Returns the role assigned to the device, if any.
152    pub async fn role(&self) -> Result<Option<Role>> {
153        let role = self
154            .client
155            .daemon
156            .device_role(create_ctx(), self.team_id, self.id)
157            .await
158            .map_err(IpcError::new)?
159            .map_err(aranya_error)?
160            .map(Role::from_api);
161        Ok(role)
162    }
163
164    /// Returns a list of labels assiged to the device.
165    pub async fn label_assignments(&self) -> Result<Labels> {
166        let data = self
167            .client
168            .daemon
169            .labels_assigned_to_device(create_ctx(), self.team_id, self.id)
170            .await
171            .map_err(IpcError::new)?
172            .map_err(aranya_error)?
173            // This _should_ just be `into_iter`, but the
174            // compiler chooses the `&Box` impl. It's the same
175            // end result, though.
176            .into_vec()
177            .into_iter()
178            .map(Label::from_api)
179            .collect();
180        Ok(Labels { labels: data })
181    }
182
183    /// Assigns `label` to the device.
184    #[instrument(skip(self))]
185    pub async fn assign_label(&self, label: LabelId, op: ChanOp) -> Result<()> {
186        self.client
187            .daemon
188            .assign_label_to_device(create_ctx(), self.team_id, self.id, label.into_api(), op)
189            .await
190            .map_err(IpcError::new)?
191            .map_err(aranya_error)
192    }
193
194    /// Revokes `label` from the device.
195    #[instrument(skip(self))]
196    pub async fn revoke_label(&self, label: LabelId) -> Result<()> {
197        self.client
198            .daemon
199            .revoke_label_from_device(create_ctx(), self.team_id, self.id, label.into_api())
200            .await
201            .map_err(IpcError::new)?
202            .map_err(aranya_error)
203    }
204}