1#![allow(clippy::expect_used, clippy::panic, clippy::indexing_slicing)]
5
6use core::{future, net::SocketAddr, ops::Deref, pin::pin};
7#[cfg(feature = "preview")]
8use std::collections::HashMap;
9#[cfg(feature = "preview")]
10use std::time::Duration;
11use std::{path::PathBuf, sync::Arc};
12
13use anyhow::{anyhow, Context as _};
14use aranya_crypto::{
15 default::WrappedKey,
16 policy::{GroupId, LabelId, RoleId},
17 Csprng, DeviceId, EncryptionKey, EncryptionPublicKey, KeyStore as _, KeyStoreExt as _, Rng,
18};
19pub(crate) use aranya_daemon_api::crypto::ApiKey;
20use aranya_daemon_api::{
21 self as api,
22 crypto::txp::{self, LengthDelimitedCodec},
23 DaemonApi, Text, WrappedSeed,
24};
25use aranya_keygen::PublicKeys;
26use aranya_runtime::GraphId;
27#[cfg(feature = "preview")]
28use aranya_runtime::{Address, Storage, StorageProvider};
29use aranya_util::{error::ReportExt as _, ready, task::scope, Addr};
30#[cfg(feature = "afc")]
31use buggy::bug;
32use derive_where::derive_where;
33use futures_util::{StreamExt, TryStreamExt};
34pub(crate) use quic_sync::Data as QSData;
35use tarpc::{
36 context,
37 server::{incoming::Incoming, BaseChannel, Channel},
38};
39use tokio::{
40 net::UnixListener,
41 sync::{mpsc, Mutex},
42};
43use tracing::{debug, error, info, instrument, trace, warn};
44
45#[cfg(feature = "afc")]
46use crate::actions::SessionData;
47#[cfg(feature = "afc")]
48use crate::afc::Afc;
49use crate::{
50 actions::Actions,
51 daemon::{CE, CS, KS},
52 keystore::LocalStore,
53 policy::{ChanOp, Effect, PublicKeyBundle, RoleCreated, RoleManagementPerm, SimplePerm},
54 sync::{quic as qs, SyncHandle, SyncPeer},
55 util::SeedDir,
56 AranyaStore, Client, EF,
57};
58
59mod quic_sync;
60
61#[macro_export]
65macro_rules! find_effect {
66 ($effects:expr, $pattern:pat $(if $guard:expr)? $(,)?) => {
67 $effects.into_iter().find(|e| matches!(e, $pattern $(if $guard)?))
68 }
69}
70
71#[derive(Debug)]
73pub(crate) struct DaemonApiServer {
74 sk: ApiKey<CS>,
76 uds_path: PathBuf,
78 listener: UnixListener,
80
81 recv_effects: mpsc::Receiver<(GraphId, Vec<EF>)>,
83
84 api: Api,
86}
87
88pub(crate) struct DaemonApiServerArgs {
89 pub(crate) client: Client,
90 pub(crate) local_addr: SocketAddr,
91 pub(crate) uds_path: PathBuf,
92 pub(crate) sk: ApiKey<CS>,
93 pub(crate) pk: PublicKeys<CS>,
94 pub(crate) syncer: SyncHandle,
95 pub(crate) recv_effects: mpsc::Receiver<(GraphId, Vec<EF>)>,
96 #[cfg(feature = "afc")]
97 pub(crate) afc: Afc<CE, CS, KS>,
98 pub(crate) crypto: Crypto,
99 pub(crate) seed_id_dir: SeedDir,
100 pub(crate) quic: Option<quic_sync::Data>,
101}
102
103impl DaemonApiServer {
104 #[instrument(skip_all)]
106 pub(crate) fn new(
107 DaemonApiServerArgs {
108 client,
109 local_addr,
110 uds_path,
111 sk,
112 pk,
113 syncer,
114 recv_effects,
115 #[cfg(feature = "afc")]
116 afc,
117 crypto,
118 seed_id_dir,
119 quic,
120 }: DaemonApiServerArgs,
121 ) -> anyhow::Result<Self> {
122 let listener = UnixListener::bind(&uds_path)?;
123 let uds_path = uds_path
124 .canonicalize()
125 .context("could not canonicalize uds_path")?;
126 #[cfg(feature = "afc")]
127 let afc = Arc::new(afc);
128 let effect_handler = EffectHandler {
129 #[cfg(feature = "afc")]
130 afc: afc.clone(),
131 #[cfg(feature = "afc")]
132 device_id: pk.ident_pk.id()?,
133 #[cfg(feature = "preview")]
134 client: client.clone(),
135 #[cfg(feature = "preview")]
136 syncer: syncer.clone(),
137 #[cfg(feature = "preview")]
138 prev_head_addresses: Arc::default(),
139 };
140 let api = Api(Arc::new(ApiInner {
141 client,
142 local_addr,
143 pk: std::sync::Mutex::new(pk),
144 syncer,
145 effect_handler,
146 #[cfg(feature = "afc")]
147 afc,
148 crypto: Mutex::new(crypto),
149 seed_id_dir,
150 quic,
151 }));
152 Ok(Self {
153 uds_path,
154 sk,
155 recv_effects,
156 listener,
157 api,
158 })
159 }
160
161 pub(crate) async fn serve(mut self, ready: ready::Notifier) {
163 scope(async |s| {
164 s.spawn({
165 let effect_handler = self.api.effect_handler.clone();
166 async move {
167 while let Some((graph, effects)) = self.recv_effects.recv().await {
168 if let Err(err) = effect_handler.handle_effects(graph, &effects).await {
169 error!(error = ?err, "error handling effects");
170 }
171 }
172 info!("effect handler exiting");
173 }
174 });
175
176 let server = {
177 let info = self.uds_path.as_os_str().as_encoded_bytes();
178 let codec = LengthDelimitedCodec::builder()
179 .max_frame_length(usize::MAX)
180 .new_codec();
181 let listener = txp::unix::UnixListenerStream::from(self.listener);
182 txp::server(listener, codec, self.sk, info)
183 };
184 info!(path = ?self.uds_path, "listening");
185
186 let mut incoming = server
187 .inspect_err(|err| warn!(error = %err.report(), "accept error"))
188 .filter_map(|r| future::ready(r.ok()))
189 .map(BaseChannel::with_defaults)
190 .max_concurrent_requests_per_channel(10);
191
192 ready.notify();
193
194 while let Some(ch) = incoming.next().await {
195 let api = self.api.clone();
196 s.spawn(scope(async move |reqs| {
197 let requests = ch
198 .requests()
199 .inspect_err(|err| warn!(error = %err.report(), "channel failure"))
200 .take_while(|r| future::ready(r.is_ok()))
201 .filter_map(|r| async { r.ok() });
202 let mut requests = pin!(requests);
203 while let Some(req) = requests.next().await {
204 reqs.spawn(req.execute(api.clone().serve()));
205 }
206 }));
207 }
208 })
209 .await;
210
211 info!("server exiting");
212 }
213}
214
215#[derive(Clone, Debug)]
217struct EffectHandler {
218 #[cfg(feature = "afc")]
219 afc: Arc<Afc<CE, CS, KS>>,
220 #[cfg(feature = "afc")]
221 device_id: DeviceId,
222 #[cfg(feature = "preview")]
223 client: Client,
224 #[cfg(feature = "preview")]
225 syncer: SyncHandle,
226 #[cfg(feature = "preview")]
228 prev_head_addresses: Arc<Mutex<HashMap<GraphId, Address>>>,
229}
230
231impl EffectHandler {
232 #[instrument(skip_all, fields(%graph, effects = effects.len()))]
234 async fn handle_effects(&self, graph: GraphId, effects: &[Effect]) -> anyhow::Result<()> {
235 trace!("handling effects");
236
237 use Effect::*;
238 for effect in effects {
240 trace!(?effect, "handling effect");
241 match effect {
242 TeamCreated(_) => {}
243 TeamTerminated(_) => {}
244 DeviceAdded(_) => {}
245 DeviceRemoved(_) => {}
246 RoleAssigned(_) => {}
247 RoleRevoked(_) => {}
248 LabelCreated(_) => {}
249 LabelDeleted(_) => {}
250 AssignedLabelToDevice(_) => {}
251 LabelRevokedFromDevice(_) => {}
252 QueryLabelResult(_) => {}
253 AfcUniChannelCreated(_) => {}
254 AfcUniChannelReceived(_) => {}
255 QueryDevicesOnTeamResult(_) => {}
256 QueryDeviceRoleResult(_) => {}
257 QueryDeviceKeyBundleResult(_) => {}
258 QueryLabelsAssignedToDeviceResult(_) => {}
259 LabelManagingRoleAdded(_) => {}
260 LabelManagingRoleRevoked(_) => {}
261 PermAddedToRole(_) => {}
262 PermRemovedFromRole(_) => {}
263 RoleOwnerAdded(_) => {}
264 RoleOwnerRemoved(_) => {}
265 RoleManagementPermAssigned(_) => {}
266 RoleManagementPermRevoked(_) => {}
267 RoleChanged(_) => {}
268 QueryLabelsResult(_) => {}
269 QueryTeamRolesResult(_) => {}
270 QueryRoleOwnersResult(_) => {}
271 QueryAfcChannelIsValidResult(_) => {}
272 RoleCreated(_) => {}
273 RoleDeleted(_) => {}
274 CheckValidAfcChannels(_) => {
275 #[cfg(feature = "afc")]
276 self.afc
277 .remove_invalid_channels(graph, self.device_id)
278 .await?;
279 }
280 }
281 }
282
283 #[cfg(feature = "preview")]
284 {
285 let Some(current_head) = self.get_graph_head_address(graph).await else {
287 warn!(?graph, "unable to get current graph head address");
288 return Ok(());
289 };
290
291 let mut prev_addresses = self.prev_head_addresses.lock().await;
292 let has_graph_changes = match prev_addresses.get(&graph) {
293 Some(prev_head) => prev_head != ¤t_head,
294 None => true, };
296
297 if has_graph_changes {
298 trace!(
299 ?graph,
300 ?current_head,
301 "graph head address changed, triggering hello notification broadcast"
302 );
303 HashMap::insert(&mut prev_addresses, graph, current_head);
305 drop(prev_addresses); self.broadcast_hello_notifications(graph, current_head)
308 .await;
309 } else {
310 trace!(
311 ?graph,
312 "graph head address unchanged, no hello broadcast needed"
313 );
314 }
315 }
316
317 Ok(())
318 }
319
320 #[cfg(feature = "preview")]
322 async fn get_graph_head_address(&self, graph_id: GraphId) -> Option<Address> {
323 let client = &self.client;
324
325 let mut aranya = client.lock_aranya().await;
326 let storage = aranya.provider().get_storage(graph_id).ok()?;
327
328 storage.get_head_address().ok()
329 }
330
331 #[cfg(feature = "preview")]
333 #[instrument(skip(self))]
334 async fn broadcast_hello_notifications(&self, graph_id: GraphId, head: Address) {
335 let syncer = self.syncer.clone();
337 drop(tokio::spawn(async move {
338 if let Err(e) = syncer.broadcast_hello(graph_id, head).await {
339 warn!(
340 error = %e,
341 ?graph_id,
342 ?head,
343 "peers.broadcast_hello failed"
344 );
345 }
346 }));
347 }
348}
349
350#[derive_where(Debug)]
355struct ApiInner {
356 client: Client,
357 local_addr: SocketAddr,
359 pk: std::sync::Mutex<PublicKeys<CS>>,
361 syncer: SyncHandle,
363 #[derive_where(skip(Debug))]
365 effect_handler: EffectHandler,
366 #[cfg(feature = "afc")]
367 afc: Arc<Afc<CE, CS, KS>>,
368 #[derive_where(skip(Debug))]
369 crypto: Mutex<Crypto>,
370 seed_id_dir: SeedDir,
371 quic: Option<quic_sync::Data>,
372}
373
374pub(crate) struct Crypto {
375 pub(crate) engine: CE,
376 pub(crate) local_store: LocalStore<KS>,
377 pub(crate) aranya_store: AranyaStore<KS>,
378}
379
380impl ApiInner {
381 fn get_pk(&self) -> api::Result<PublicKeyBundle> {
382 let pk = self.pk.lock().expect("poisoned");
383 Ok(PublicKeyBundle::try_from(&*pk).context("bad key bundle")?)
384 }
385
386 fn device_id(&self) -> api::Result<DeviceId> {
387 let pk = self.pk.lock().expect("poisoned");
388 let id = pk.ident_pk.id()?;
389 Ok(id)
390 }
391}
392
393#[derive(Clone, Debug)]
395struct Api(Arc<ApiInner>);
396
397impl Deref for Api {
398 type Target = ApiInner;
399
400 fn deref(&self) -> &Self::Target {
401 &self.0
402 }
403}
404
405impl Api {
406 async fn check_team_valid(&self, team: api::TeamId) -> anyhow::Result<GraphId> {
409 if self
410 .client
411 .invalid_graphs()
412 .contains(GraphId::transmute(team))
413 {
414 anyhow::bail!("team {team} invalid due to graph finalization error")
416 }
417 Ok(GraphId::transmute(team))
418 }
419}
420
421impl DaemonApi for Api {
422 #[instrument(skip(self), err)]
427 async fn version(self, context: context::Context) -> api::Result<api::Version> {
428 api::Version::parse(env!("CARGO_PKG_VERSION")).map_err(Into::into)
429 }
430
431 #[instrument(skip(self), err)]
432 async fn aranya_local_addr(self, context: context::Context) -> api::Result<Addr> {
433 Ok(self.local_addr.into())
434 }
435
436 #[instrument(skip(self), err)]
437 async fn get_public_key_bundle(self, _: context::Context) -> api::Result<api::PublicKeyBundle> {
438 Ok(self
439 .get_pk()
440 .context("unable to get device public keys")?
441 .into())
442 }
443
444 #[instrument(skip(self), err)]
445 async fn get_device_id(self, _: context::Context) -> api::Result<api::DeviceId> {
446 self.device_id().map(api::DeviceId::transmute)
447 }
448
449 #[cfg(feature = "afc")]
450 #[instrument(skip(self), err)]
451 async fn afc_shm_info(self, context: context::Context) -> api::Result<api::AfcShmInfo> {
452 Ok(self.afc.get_shm_info().await)
453 }
454
455 #[instrument(skip(self), err)]
460 async fn add_sync_peer(
461 self,
462 _: context::Context,
463 peer: Addr,
464 team: api::TeamId,
465 cfg: api::SyncPeerConfig,
466 ) -> api::Result<()> {
467 let graph = self.check_team_valid(team).await?;
468 let peer = SyncPeer::new(peer, graph);
469 self.syncer.add_peer(peer, cfg).await?;
470 Ok(())
471 }
472
473 #[instrument(skip(self), err)]
474 async fn sync_now(
475 self,
476 _: context::Context,
477 peer: Addr,
478 team: api::TeamId,
479 cfg: Option<api::SyncPeerConfig>,
480 ) -> api::Result<()> {
481 let graph = self.check_team_valid(team).await?;
482 let peer = SyncPeer::new(peer, graph);
483 self.syncer.sync_now(peer, cfg).await?;
484 Ok(())
485 }
486
487 #[cfg(feature = "preview")]
488 #[instrument(skip(self), err)]
489 async fn sync_hello_subscribe(
490 self,
491 _: context::Context,
492 peer: Addr,
493 team: api::TeamId,
494 graph_change_debounce: Duration,
495 duration: Duration,
496 schedule_delay: Duration,
497 ) -> api::Result<()> {
498 let graph = self.check_team_valid(team).await?;
499 let peer = SyncPeer::new(peer, graph);
500 self.syncer
501 .sync_hello_subscribe(peer, graph_change_debounce, duration, schedule_delay)
502 .await?;
503 Ok(())
504 }
505
506 #[cfg(feature = "preview")]
507 #[instrument(skip(self), err)]
508 async fn sync_hello_unsubscribe(
509 self,
510 _: context::Context,
511 peer: Addr,
512 team: api::TeamId,
513 ) -> api::Result<()> {
514 let graph = self.check_team_valid(team).await?;
515 let peer = SyncPeer::new(peer, graph);
516 self.syncer.sync_hello_unsubscribe(peer).await?;
517 Ok(())
518 }
519
520 #[instrument(skip(self), err)]
521 async fn remove_sync_peer(
522 self,
523 _: context::Context,
524 peer: Addr,
525 team: api::TeamId,
526 ) -> api::Result<()> {
527 let graph = self.check_team_valid(team).await?;
528 let peer = SyncPeer::new(peer, graph);
529 self.syncer
530 .remove_peer(peer)
531 .await
532 .context("unable to remove sync peer")?;
533 Ok(())
534 }
535
536 #[instrument(skip(self))]
541 async fn add_team(mut self, _: context::Context, cfg: api::AddTeamConfig) -> api::Result<()> {
542 let team = cfg.team_id;
543 self.check_team_valid(team).await?;
544
545 match cfg.quic_sync {
546 Some(cfg) => self.add_team_quic_sync(team, cfg).await,
547 None => Err(anyhow!("Missing QUIC sync config").into()),
548 }
549 }
550
551 #[instrument(skip(self), err)]
552 async fn remove_team(self, _: context::Context, team: api::TeamId) -> api::Result<()> {
553 if let Some(data) = &self.quic {
554 self.remove_team_quic_sync(team, data)?;
555 }
556
557 self.seed_id_dir.remove(team).await?;
558
559 self.client
560 .lock_aranya()
561 .await
562 .remove_graph(GraphId::transmute(team))
563 .context("unable to remove graph from storage")?;
564
565 Ok(())
566 }
567
568 #[instrument(skip(self), err)]
569 async fn create_team(
570 mut self,
571 _: context::Context,
572 cfg: api::CreateTeamConfig,
573 ) -> api::Result<api::TeamId> {
574 info!("create_team");
575
576 let nonce = &mut [0u8; 16];
577 Rng.fill_bytes(nonce);
578 let pk = self.get_pk()?;
579 let (graph_id, _) = self
580 .client
581 .create_team(pk, Some(nonce))
582 .await
583 .context("unable to create team")?;
584 debug!(?graph_id);
585 let team_id = api::TeamId::transmute(graph_id);
586
587 match cfg.quic_sync {
588 Some(qs_cfg) => {
589 self.create_team_quic_sync(team_id, qs_cfg).await?;
590 }
591 None => {
592 warn!("Missing QUIC sync config");
593
594 let seed = qs::PskSeed::new(Rng, team_id);
595 self.add_seed(team_id, seed).await?;
596 }
597 }
598
599 Ok(team_id)
600 }
601
602 #[instrument(skip(self), err)]
603 async fn close_team(self, _: context::Context, team: api::TeamId) -> api::Result<()> {
604 let _graph = self.check_team_valid(team).await?;
605
606 todo!();
607 }
608
609 #[instrument(skip(self), err)]
614 async fn encrypt_psk_seed_for_peer(
615 self,
616 _: context::Context,
617 team: api::TeamId,
618 peer_enc_pk: EncryptionPublicKey<CS>,
619 ) -> aranya_daemon_api::Result<WrappedSeed> {
620 let enc_pk = self.pk.lock().expect("poisoned").enc_pk.clone();
621
622 let (seed, enc_sk) = {
623 let crypto = &mut *self.crypto.lock().await;
624 let seed = {
625 let seed_id = self.seed_id_dir.get(team).await?;
626 qs::PskSeed::load(&crypto.engine, &crypto.local_store, seed_id)?
627 .context("no seed in dir")?
628 };
629 let enc_sk: EncryptionKey<CS> = crypto
630 .aranya_store
631 .get_key(&crypto.engine, enc_pk.id()?)
632 .context("keystore error")?
633 .context("missing enc_sk for encrypt seed")?;
634 (seed, enc_sk)
635 };
636
637 let group = GroupId::transmute(team);
638 let (encap_key, encrypted_seed) = enc_sk
639 .seal_psk_seed(Rng, &seed.0, &peer_enc_pk, &group)
640 .context("could not seal psk seed")?;
641
642 Ok(WrappedSeed {
643 sender_pk: enc_pk,
644 encap_key,
645 encrypted_seed,
646 })
647 }
648
649 #[instrument(skip(self), err)]
650 async fn add_device_to_team(
651 self,
652 _: context::Context,
653 team: api::TeamId,
654 keys: api::PublicKeyBundle,
655 initial_role: Option<api::RoleId>,
656 ) -> api::Result<()> {
657 let graph = self.check_team_valid(team).await?;
658
659 let effects = self
660 .client
661 .actions(graph)
662 .add_device(keys.into(), initial_role.map(RoleId::transmute))
663 .await
664 .context("unable to add device to team")?;
665 self.effect_handler.handle_effects(graph, &effects).await?;
666 Ok(())
667 }
668
669 #[instrument(skip(self), err)]
670 async fn remove_device_from_team(
671 self,
672 _: context::Context,
673 team: api::TeamId,
674 device: api::DeviceId,
675 ) -> api::Result<()> {
676 let graph = self.check_team_valid(team).await?;
677
678 let effects = self
679 .client
680 .actions(graph)
681 .remove_device(DeviceId::transmute(device))
682 .await
683 .context("unable to remove device from team")?;
684 self.effect_handler.handle_effects(graph, &effects).await?;
685
686 Ok(())
687 }
688
689 #[instrument(skip(self))]
690 async fn devices_on_team(
691 self,
692 _: context::Context,
693 team: api::TeamId,
694 ) -> api::Result<Box<[api::DeviceId]>> {
695 let graph = self.check_team_valid(team).await?;
696
697 let devices = self
698 .client
699 .actions(graph)
700 .query_devices_on_team()
701 .await
702 .context("unable to query devices on team")?
703 .into_iter()
704 .filter_map(|e| {
705 if let Effect::QueryDevicesOnTeamResult(e) = e {
706 Some(api::DeviceId::from_base(e.device_id))
707 } else {
708 warn!(name = e.name(), "unexpected effect");
709 None
710 }
711 })
712 .collect();
713
714 Ok(devices)
715 }
716
717 #[instrument(skip(self), err)]
718 async fn device_public_key_bundle(
719 self,
720 _: context::Context,
721 team: api::TeamId,
722 device: api::DeviceId,
723 ) -> api::Result<api::PublicKeyBundle> {
724 let graph = self.check_team_valid(team).await?;
725
726 let effects = self
727 .client
728 .actions(graph)
729 .query_device_public_key_bundle(DeviceId::transmute(device))
730 .await
731 .context("unable to query device public key bundle")?;
732 if let Some(Effect::QueryDeviceKeyBundleResult(e)) =
733 find_effect!(effects, Effect::QueryDeviceKeyBundleResult(_e))
734 {
735 Ok(api::PublicKeyBundle::from(e.device_keys))
736 } else {
737 Err(anyhow!("unable to query device public key bundle").into())
738 }
739 }
740
741 #[instrument(skip(self), err)]
742 async fn labels_assigned_to_device(
743 self,
744 _: context::Context,
745 team: api::TeamId,
746 device: api::DeviceId,
747 ) -> api::Result<Box<[api::Label]>> {
748 let graph = self.check_team_valid(team).await?;
749
750 let effects = self
751 .client
752 .actions(graph)
753 .query_labels_assigned_to_device(DeviceId::transmute(device))
754 .await
755 .context("unable to query device label assignments")?;
756 let mut labels = Vec::new();
757 for e in effects {
758 if let Effect::QueryLabelsAssignedToDeviceResult(e) = e {
759 debug!("found label: {}", e.label_id);
760 labels.push(api::Label {
761 id: api::LabelId::from_base(e.label_id),
762 name: e.label_name,
763 author_id: api::DeviceId::from_base(e.label_author_id),
764 });
765 }
766 }
767 return Ok(labels.into_boxed_slice());
768 }
769
770 #[instrument(skip(self), err)]
771 async fn device_role(
772 self,
773 _: context::Context,
774 team: api::TeamId,
775 device: api::DeviceId,
776 ) -> api::Result<Option<api::Role>> {
777 let graph = self.check_team_valid(team).await?;
778
779 let effects = self
780 .client
781 .actions(graph)
782 .query_device_role(DeviceId::transmute(device))
783 .await
784 .context("unable to query device role")?;
785 if let Some(Effect::QueryDeviceRoleResult(e)) =
786 find_effect!(&effects, Effect::QueryDeviceRoleResult(_))
787 {
788 Ok(Some(api::Role {
789 id: api::RoleId::from_base(e.role_id),
790 name: e.name.clone(),
791 author_id: api::DeviceId::from_base(e.author_id),
792 default: e.default,
793 }))
794 } else {
795 Ok(None)
796 }
797 }
798
799 #[cfg(feature = "preview")]
800 #[instrument(skip(self), err)]
801 async fn create_role(
802 self,
803 _: context::Context,
804 team: api::TeamId,
805 role_name: Text,
806 owning_role: api::RoleId,
807 ) -> api::Result<api::Role> {
808 let graph = self.check_team_valid(team).await?;
809
810 let effects = self
811 .client
812 .actions(graph)
813 .create_role(role_name, RoleId::transmute(owning_role))
814 .await
815 .context("unable to create role")?;
816 self.effect_handler.handle_effects(graph, &effects).await?;
817
818 if let Some(Effect::RoleCreated(e)) = find_effect!(&effects, Effect::RoleCreated(_)) {
819 Ok(api::Role {
820 id: api::RoleId::from_base(e.role_id),
821 name: e.name.clone(),
822 author_id: api::DeviceId::from_base(e.author_id),
823 default: e.default,
824 })
825 } else {
826 Err(anyhow!("wrong effect when creating role").into())
827 }
828 }
829
830 #[cfg(feature = "preview")]
831 #[instrument(skip(self), err)]
832 async fn delete_role(
833 self,
834 _: context::Context,
835 team: api::TeamId,
836 role_id: api::RoleId,
837 ) -> api::Result<()> {
838 let graph = self.check_team_valid(team).await?;
839
840 let effects = self
841 .client
842 .actions(graph)
843 .delete_role(RoleId::transmute(role_id))
844 .await
845 .context("unable to delete role")?;
846 self.effect_handler.handle_effects(graph, &effects).await?;
847
848 if let Some(Effect::RoleDeleted(e)) = find_effect!(&effects, Effect::RoleDeleted(_)) {
849 info!("Deleted role {role_id} ({})", e.name());
850 Ok(())
851 } else {
852 Err(anyhow!("wrong effect when creating role").into())
853 }
854 }
855
856 #[instrument(skip(self), err)]
857 async fn assign_role(
858 self,
859 _: context::Context,
860 team: api::TeamId,
861 device: api::DeviceId,
862 role: api::RoleId,
863 ) -> api::Result<()> {
864 let graph = self.check_team_valid(team).await?;
865
866 let effects = self
867 .client
868 .actions(graph)
869 .assign_role(DeviceId::transmute(device), RoleId::transmute(role))
870 .await
871 .context("unable to assign role")?;
872 self.effect_handler.handle_effects(graph, &effects).await?;
873
874 if let Some(Effect::RoleAssigned(_e)) = find_effect!(&effects, Effect::RoleAssigned(_e)) {
875 Ok(())
876 } else {
877 Err(anyhow!("unable to assign role").into())
878 }
879 }
880
881 #[instrument(skip(self), err)]
882 async fn revoke_role(
883 self,
884 _: context::Context,
885 team: api::TeamId,
886 device: api::DeviceId,
887 role: api::RoleId,
888 ) -> api::Result<()> {
889 let graph = self.check_team_valid(team).await?;
890
891 let effects = self
892 .client
893 .actions(graph)
894 .revoke_role(DeviceId::transmute(device), RoleId::transmute(role))
895 .await
896 .context("unable to revoke device role")?;
897 self.effect_handler.handle_effects(graph, &effects).await?;
898
899 if let Some(Effect::RoleRevoked(_e)) = find_effect!(&effects, Effect::RoleRevoked(_e)) {
900 Ok(())
901 } else {
902 Err(anyhow!("unable to revoke device role").into())
903 }
904 }
905
906 #[instrument(skip(self), err)]
907 async fn change_role(
908 self,
909 _: context::Context,
910 team: api::TeamId,
911 device_id: api::DeviceId,
912 old_role_id: api::RoleId,
913 new_role_id: api::RoleId,
914 ) -> api::Result<()> {
915 let graph = self.check_team_valid(team).await?;
916
917 let effects = self
918 .client
919 .actions(graph)
920 .change_role(
921 DeviceId::transmute(device_id),
922 RoleId::transmute(old_role_id),
923 RoleId::transmute(new_role_id),
924 )
925 .await
926 .context("unable to change device role")?;
927 self.effect_handler.handle_effects(graph, &effects).await?;
928
929 if let Some(Effect::RoleChanged(_e)) = find_effect!(&effects, Effect::RoleChanged(_e)) {
930 Ok(())
931 } else {
932 Err(anyhow!("unable to change device role").into())
933 }
934 }
935
936 #[cfg(feature = "afc")]
937 #[instrument(skip(self), err)]
938 async fn create_afc_channel(
939 self,
940 _: context::Context,
941 team: api::TeamId,
942 peer_id: api::DeviceId,
943 label: api::LabelId,
944 ) -> api::Result<api::AfcSendChannelInfo> {
945 let graph = self.check_team_valid(team).await?;
946
947 info!("creating afc uni channel");
948
949 let SessionData { ctrl, effects } = self
950 .client
951 .actions(graph)
952 .create_afc_uni_channel_off_graph(
953 DeviceId::transmute(peer_id),
954 LabelId::transmute(label),
955 )
956 .await?;
957
958 let [Effect::AfcUniChannelCreated(e)] = effects.as_slice() else {
959 bug!("expected afc uni channel created effect")
960 };
961
962 self.effect_handler.handle_effects(graph, &effects).await?;
963
964 let (local_channel_id, channel_id) = self.afc.uni_channel_created(e).await?;
965 info!("afc uni channel created");
966
967 let ctrl = get_afc_ctrl(ctrl)?;
968
969 Ok(api::AfcSendChannelInfo {
970 ctrl,
971 local_channel_id,
972 channel_id,
973 })
974 }
975
976 #[cfg(feature = "afc")]
977 #[instrument(skip(self), err)]
978 async fn delete_afc_channel(
979 self,
980 _: context::Context,
981 chan: api::AfcLocalChannelId,
982 ) -> api::Result<()> {
983 self.afc.delete_channel(chan).await?;
984 info!("afc channel deleted");
985 Ok(())
986 }
987
988 #[cfg(feature = "afc")]
989 #[instrument(skip(self), err)]
990 async fn accept_afc_channel(
991 self,
992 _: context::Context,
993 team: api::TeamId,
994 ctrl: api::AfcCtrl,
995 ) -> api::Result<api::AfcReceiveChannelInfo> {
996 let graph = self.check_team_valid(team).await?;
997
998 let mut session = self.client.session_new(graph).await?;
999
1000 let effects = self.client.session_receive(&mut session, &ctrl).await?;
1001
1002 let [Effect::AfcUniChannelReceived(e)] = effects.as_slice() else {
1003 bug!("expected afc uni channel received effect")
1004 };
1005
1006 self.effect_handler.handle_effects(graph, &effects).await?;
1007
1008 let (local_channel_id, channel_id) = self.afc.uni_channel_received(e).await?;
1009
1010 return Ok(api::AfcReceiveChannelInfo {
1011 local_channel_id,
1012 channel_id,
1013 label_id: api::LabelId::from_base(e.label_id),
1014 peer_id: api::DeviceId::from_base(e.sender_id),
1015 });
1016 }
1017
1018 #[instrument(skip(self), err)]
1019 async fn create_label(
1020 self,
1021 _: context::Context,
1022 team: api::TeamId,
1023 label_name: Text,
1024 managing_role_id: api::RoleId,
1025 ) -> api::Result<api::LabelId> {
1026 let graph = self.check_team_valid(team).await?;
1027
1028 let effects = self
1029 .client
1030 .actions(graph)
1031 .create_label(label_name, RoleId::transmute(managing_role_id))
1032 .await
1033 .context("unable to create label")?;
1034 self.effect_handler.handle_effects(graph, &effects).await?;
1035
1036 if let Some(Effect::LabelCreated(e)) = find_effect!(&effects, Effect::LabelCreated(_e)) {
1037 Ok(api::LabelId::from_base(e.label_id))
1038 } else {
1039 Err(anyhow!("unable to create label").into())
1040 }
1041 }
1042
1043 #[instrument(skip(self), err)]
1044 async fn delete_label(
1045 self,
1046 _: context::Context,
1047 team: api::TeamId,
1048 label_id: api::LabelId,
1049 ) -> api::Result<()> {
1050 let graph = self.check_team_valid(team).await?;
1051
1052 let effects = self
1053 .client
1054 .actions(graph)
1055 .delete_label(LabelId::transmute(label_id))
1056 .await
1057 .context("unable to delete label")?;
1058 self.effect_handler.handle_effects(graph, &effects).await?;
1059
1060 if let Some(Effect::LabelDeleted(_e)) = find_effect!(&effects, Effect::LabelDeleted(_e)) {
1061 Ok(())
1062 } else {
1063 Err(anyhow!("unable to delete label").into())
1064 }
1065 }
1066
1067 async fn add_label_managing_role(
1068 self,
1069 _: context::Context,
1070 team: api::TeamId,
1071 label_id: api::LabelId,
1072 managing_role_id: api::RoleId,
1073 ) -> api::Result<()> {
1074 let graph = self.check_team_valid(team).await?;
1075
1076 let effects = self
1077 .client
1078 .actions(graph)
1079 .add_label_managing_role(
1080 LabelId::transmute(label_id),
1081 RoleId::transmute(managing_role_id),
1082 )
1083 .await
1084 .context("unable to add label managing role")?;
1085 self.effect_handler.handle_effects(graph, &effects).await?;
1086
1087 if let Some(Effect::LabelManagingRoleAdded(_e)) =
1088 find_effect!(&effects, Effect::LabelManagingRoleAdded(_e))
1089 {
1090 Ok(())
1091 } else {
1092 Err(anyhow!("unable to add label managing role").into())
1093 }
1094 }
1095
1096 #[instrument(skip(self), err)]
1097 async fn assign_label_to_device(
1098 self,
1099 _: context::Context,
1100 team: api::TeamId,
1101 device: api::DeviceId,
1102 label_id: api::LabelId,
1103 op: api::ChanOp,
1104 ) -> api::Result<()> {
1105 let graph = self.check_team_valid(team).await?;
1106
1107 let effects = self
1108 .client
1109 .actions(graph)
1110 .assign_label_to_device(
1111 DeviceId::transmute(device),
1112 LabelId::transmute(label_id),
1113 op.into(),
1114 )
1115 .await
1116 .context("unable to assign label")?;
1117 self.effect_handler.handle_effects(graph, &effects).await?;
1118
1119 if let Some(Effect::AssignedLabelToDevice(_e)) =
1120 find_effect!(&effects, Effect::AssignedLabelToDevice(_e))
1121 {
1122 Ok(())
1123 } else {
1124 Err(anyhow!("unable to assign label").into())
1125 }
1126 }
1127
1128 #[instrument(skip(self), err)]
1129 async fn revoke_label_from_device(
1130 self,
1131 _: context::Context,
1132 team: api::TeamId,
1133 device: api::DeviceId,
1134 label_id: api::LabelId,
1135 ) -> api::Result<()> {
1136 let graph = self.check_team_valid(team).await?;
1137
1138 let effects = self
1139 .client
1140 .actions(graph)
1141 .revoke_label_from_device(DeviceId::transmute(device), LabelId::transmute(label_id))
1142 .await
1143 .context("unable to revoke label")?;
1144 self.effect_handler.handle_effects(graph, &effects).await?;
1145
1146 if let Some(Effect::LabelRevokedFromDevice(_e)) =
1147 find_effect!(&effects, Effect::LabelRevokedFromDevice(_e))
1148 {
1149 Ok(())
1150 } else {
1151 Err(anyhow!("unable to revoke label").into())
1152 }
1153 }
1154
1155 #[instrument(skip(self), err)]
1156 async fn label(
1157 self,
1158 _: context::Context,
1159 team: api::TeamId,
1160 label_id: api::LabelId,
1161 ) -> api::Result<Option<api::Label>> {
1162 let graph = self.check_team_valid(team).await?;
1163
1164 let effects = self
1165 .client
1166 .actions(graph)
1167 .query_label(LabelId::transmute(label_id))
1168 .await
1169 .context("unable to query label")?;
1170 if let Some(Effect::QueryLabelResult(e)) =
1171 find_effect!(&effects, Effect::QueryLabelResult(_e))
1172 {
1173 Ok(Some(api::Label {
1174 id: api::LabelId::from_base(e.label_id),
1175 name: e.label_name.clone(),
1176 author_id: api::DeviceId::from_base(e.label_author_id),
1177 }))
1178 } else {
1179 Ok(None)
1180 }
1181 }
1182
1183 #[instrument(skip(self), err)]
1184 async fn labels(self, _: context::Context, team: api::TeamId) -> api::Result<Vec<api::Label>> {
1185 let graph = self.check_team_valid(team).await?;
1186
1187 let effects = self
1188 .client
1189 .actions(graph)
1190 .query_labels()
1191 .await
1192 .context("unable to query labels")?;
1193 let mut labels: Vec<api::Label> = Vec::new();
1194 for e in effects {
1195 if let Effect::QueryLabelsResult(e) = e {
1196 debug!("found label: {}", e.label_id);
1197 labels.push(api::Label {
1198 id: api::LabelId::from_base(e.label_id),
1199 name: e.label_name.clone(),
1200 author_id: api::DeviceId::from_base(e.label_author_id),
1201 });
1202 }
1203 }
1204 Ok(labels)
1205 }
1206
1207 #[instrument(skip(self), err)]
1208 async fn setup_default_roles(
1209 self,
1210 _: context::Context,
1211 team: api::TeamId,
1212 owning_role: api::RoleId,
1213 ) -> api::Result<Box<[api::Role]>> {
1214 let graph = self.check_team_valid(team).await?;
1215
1216 let effects = self
1217 .client
1218 .actions(graph)
1219 .setup_default_roles(RoleId::transmute(owning_role))
1220 .await
1221 .context("unable to setup default roles")?;
1222 self.effect_handler.handle_effects(graph, &effects).await?;
1223
1224 let roles = effects
1225 .into_iter()
1226 .filter_map(|e| {
1227 if let Effect::RoleCreated(e @ RoleCreated { default: true, .. }) = e {
1228 Some(api::Role {
1229 id: api::RoleId::from_base(e.role_id),
1230 name: e.name,
1231 author_id: api::DeviceId::from_base(e.author_id),
1232 default: e.default,
1233 })
1234 } else {
1235 warn!(name = e.name(), "unexpected effect");
1236 None
1237 }
1238 })
1239 .collect();
1240
1241 Ok(roles)
1242 }
1243
1244 #[instrument(skip(self), err)]
1245 async fn team_roles(
1246 self,
1247 _: context::Context,
1248 team: api::TeamId,
1249 ) -> api::Result<Box<[api::Role]>> {
1250 let graph = self.check_team_valid(team).await?;
1251
1252 let roles = self
1253 .client
1254 .actions(graph)
1255 .query_team_roles()
1256 .await
1257 .context("unable to query team roles")?
1258 .into_iter()
1259 .filter_map(|e| {
1260 if let Effect::QueryTeamRolesResult(e) = e {
1261 Some(api::Role {
1262 id: api::RoleId::from_base(e.role_id),
1263 name: e.name,
1264 author_id: api::DeviceId::from_base(e.author_id),
1265 default: e.default,
1266 })
1267 } else {
1268 warn!(name = e.name(), "unexpected effect");
1269 None
1270 }
1271 })
1272 .collect();
1273 Ok(roles)
1274 }
1275
1276 #[cfg(feature = "preview")]
1281 #[instrument(skip(self), err)]
1282 async fn add_perm_to_role(
1283 self,
1284 context: context::Context,
1285 team: api::TeamId,
1286 role: api::RoleId,
1287 perm: api::SimplePerm,
1288 ) -> api::Result<()> {
1289 let graph = self.check_team_valid(team).await?;
1290
1291 let effects = self
1292 .client
1293 .actions(graph)
1294 .add_perm_to_role(RoleId::transmute(role), perm.into())
1295 .await
1296 .context("unable to add permission to role")?;
1297 self.effect_handler.handle_effects(graph, &effects).await?;
1298
1299 Ok(())
1300 }
1301
1302 #[cfg(feature = "preview")]
1303 #[instrument(skip(self), err)]
1304 async fn remove_perm_from_role(
1305 self,
1306 context: context::Context,
1307 team: api::TeamId,
1308 role: api::RoleId,
1309 perm: api::SimplePerm,
1310 ) -> api::Result<()> {
1311 let graph = self.check_team_valid(team).await?;
1312
1313 let effects = self
1314 .client
1315 .actions(graph)
1316 .remove_perm_from_role(RoleId::transmute(role), perm.into())
1317 .await
1318 .context("unable to add permission to role")?;
1319 self.effect_handler.handle_effects(graph, &effects).await?;
1320
1321 Ok(())
1322 }
1323
1324 #[cfg(feature = "preview")]
1325 #[instrument(skip(self), err)]
1326 async fn add_role_owner(
1327 self,
1328 _: context::Context,
1329 team: api::TeamId,
1330 role: api::RoleId,
1331 owning_role: api::RoleId,
1332 ) -> api::Result<()> {
1333 let graph = self.check_team_valid(team).await?;
1334
1335 let effects = self
1336 .client
1337 .actions(graph)
1338 .add_role_owner(RoleId::transmute(role), RoleId::transmute(owning_role))
1339 .await
1340 .context("unable to add role owner")?;
1341 self.effect_handler.handle_effects(graph, &effects).await?;
1342
1343 Ok(())
1344 }
1345
1346 #[cfg(feature = "preview")]
1347 #[instrument(skip(self), err)]
1348 async fn remove_role_owner(
1349 self,
1350 _: context::Context,
1351 team: api::TeamId,
1352 role: api::RoleId,
1353 owning_role: api::RoleId,
1354 ) -> api::Result<()> {
1355 let graph = self.check_team_valid(team).await?;
1356
1357 let effects = self
1358 .client
1359 .actions(graph)
1360 .remove_role_owner(RoleId::transmute(role), RoleId::transmute(owning_role))
1361 .await
1362 .context("unable to remove role owner")?;
1363 self.effect_handler.handle_effects(graph, &effects).await?;
1364
1365 Ok(())
1366 }
1367
1368 #[instrument(skip(self), err)]
1369 async fn role_owners(
1370 self,
1371 _: context::Context,
1372 team: api::TeamId,
1373 role: api::RoleId,
1374 ) -> api::Result<Box<[api::Role]>> {
1375 let graph = self.check_team_valid(team).await?;
1376
1377 let roles = self
1378 .client
1379 .actions(graph)
1380 .query_role_owners(RoleId::transmute(role))
1381 .await
1382 .context("unable to query role owners")?
1383 .into_iter()
1384 .filter_map(|e| {
1385 if let Effect::QueryRoleOwnersResult(e) = e {
1386 Some(api::Role {
1387 id: api::RoleId::from_base(e.role_id),
1388 name: e.name,
1389 author_id: api::DeviceId::from_base(e.author_id),
1390 default: e.default,
1391 })
1392 } else {
1393 None
1394 }
1395 })
1396 .collect();
1397 Ok(roles)
1398 }
1399
1400 #[cfg(feature = "preview")]
1401 #[instrument(skip(self), err)]
1402 async fn assign_role_management_perm(
1403 self,
1404 _: context::Context,
1405 team: api::TeamId,
1406 role: api::RoleId,
1407 managing_role: api::RoleId,
1408 perm: api::RoleManagementPerm,
1409 ) -> api::Result<()> {
1410 let graph = self.check_team_valid(team).await?;
1411
1412 let effects = self
1413 .client
1414 .actions(graph)
1415 .assign_role_management_perm(
1416 RoleId::transmute(role),
1417 RoleId::transmute(managing_role),
1418 perm.into(),
1419 )
1420 .await
1421 .context("unable to assign role management permission")?;
1422 self.effect_handler.handle_effects(graph, &effects).await?;
1423
1424 Ok(())
1425 }
1426
1427 #[cfg(feature = "preview")]
1428 #[instrument(skip(self), err)]
1429 async fn revoke_role_management_perm(
1430 self,
1431 _: context::Context,
1432 team: api::TeamId,
1433 role: api::RoleId,
1434 managing_role: api::RoleId,
1435 perm: api::RoleManagementPerm,
1436 ) -> api::Result<()> {
1437 let graph = self.check_team_valid(team).await?;
1438
1439 let effects = self
1440 .client
1441 .actions(graph)
1442 .revoke_role_management_perm(
1443 RoleId::transmute(role),
1444 RoleId::transmute(managing_role),
1445 perm.into(),
1446 )
1447 .await
1448 .context("unable to revoke role management permission")?;
1449 self.effect_handler.handle_effects(graph, &effects).await?;
1450
1451 Ok(())
1452 }
1453}
1454
1455impl Api {
1456 async fn add_seed(&mut self, team: api::TeamId, seed: qs::PskSeed) -> anyhow::Result<()> {
1457 let crypto = &mut *self.crypto.lock().await;
1458
1459 let id = crypto
1460 .local_store
1461 .insert_key(&crypto.engine, seed.into_inner())
1462 .context("inserting seed")?;
1463
1464 if let Err(e) = self
1465 .seed_id_dir
1466 .append(team, id)
1467 .await
1468 .context("could not write seed id to file")
1469 {
1470 match crypto
1471 .local_store
1472 .remove::<WrappedKey<CS>>(id.as_base())
1473 .context("could not remove seed from keystore")
1474 {
1475 Ok(_) => return Err(e),
1476 Err(inner) => return Err(e).context(inner),
1477 }
1478 };
1479
1480 Ok(())
1481 }
1482}
1483
1484impl From<api::PublicKeyBundle> for PublicKeyBundle {
1485 fn from(value: api::PublicKeyBundle) -> Self {
1486 PublicKeyBundle {
1487 ident_key: value.identity,
1488 sign_key: value.signing,
1489 enc_key: value.encryption,
1490 }
1491 }
1492}
1493
1494impl From<PublicKeyBundle> for api::PublicKeyBundle {
1495 fn from(value: PublicKeyBundle) -> Self {
1496 api::PublicKeyBundle {
1497 identity: value.ident_key,
1498 signing: value.sign_key,
1499 encryption: value.enc_key,
1500 }
1501 }
1502}
1503
1504impl From<api::ChanOp> for ChanOp {
1505 fn from(value: api::ChanOp) -> Self {
1506 match value {
1507 api::ChanOp::SendRecv => ChanOp::SendRecv,
1508 api::ChanOp::RecvOnly => ChanOp::RecvOnly,
1509 api::ChanOp::SendOnly => ChanOp::SendOnly,
1510 }
1511 }
1512}
1513
1514impl From<ChanOp> for api::ChanOp {
1515 fn from(value: ChanOp) -> Self {
1516 match value {
1517 ChanOp::SendRecv => api::ChanOp::SendRecv,
1518 ChanOp::RecvOnly => api::ChanOp::RecvOnly,
1519 ChanOp::SendOnly => api::ChanOp::SendOnly,
1520 }
1521 }
1522}
1523
1524impl From<api::RoleManagementPerm> for RoleManagementPerm {
1525 fn from(value: api::RoleManagementPerm) -> Self {
1526 match value {
1527 api::RoleManagementPerm::CanAssignRole => RoleManagementPerm::CanAssignRole,
1528 api::RoleManagementPerm::CanRevokeRole => RoleManagementPerm::CanRevokeRole,
1529 api::RoleManagementPerm::CanChangeRolePerms => RoleManagementPerm::CanChangeRolePerms,
1530 }
1531 }
1532}
1533
1534impl From<RoleManagementPerm> for api::RoleManagementPerm {
1535 fn from(value: RoleManagementPerm) -> Self {
1536 match value {
1537 RoleManagementPerm::CanAssignRole => api::RoleManagementPerm::CanAssignRole,
1538 RoleManagementPerm::CanRevokeRole => api::RoleManagementPerm::CanRevokeRole,
1539 RoleManagementPerm::CanChangeRolePerms => api::RoleManagementPerm::CanChangeRolePerms,
1540 }
1541 }
1542}
1543
1544impl From<api::SimplePerm> for SimplePerm {
1545 fn from(value: api::SimplePerm) -> Self {
1546 match value {
1547 api::SimplePerm::AddDevice => SimplePerm::AddDevice,
1548 api::SimplePerm::RemoveDevice => SimplePerm::RemoveDevice,
1549 api::SimplePerm::TerminateTeam => SimplePerm::TerminateTeam,
1550 api::SimplePerm::CreateRole => SimplePerm::CreateRole,
1551 api::SimplePerm::DeleteRole => SimplePerm::DeleteRole,
1552 api::SimplePerm::AssignRole => SimplePerm::AssignRole,
1553 api::SimplePerm::RevokeRole => SimplePerm::RevokeRole,
1554 api::SimplePerm::ChangeRoleManagementPerms => SimplePerm::ChangeRoleManagementPerms,
1555 api::SimplePerm::SetupDefaultRole => SimplePerm::SetupDefaultRole,
1556 api::SimplePerm::ChangeRoleManagingRole => SimplePerm::ChangeRoleManagingRole,
1557 api::SimplePerm::CreateLabel => SimplePerm::CreateLabel,
1558 api::SimplePerm::DeleteLabel => SimplePerm::DeleteLabel,
1559 api::SimplePerm::ChangeLabelManagingRole => SimplePerm::ChangeLabelManagingRole,
1560 api::SimplePerm::AssignLabel => SimplePerm::AssignLabel,
1561 api::SimplePerm::RevokeLabel => SimplePerm::RevokeLabel,
1562 api::SimplePerm::CanUseAfc => SimplePerm::CanUseAfc,
1563 api::SimplePerm::CreateAfcUniChannel => SimplePerm::CreateAfcUniChannel,
1564 }
1565 }
1566}
1567
1568impl From<SimplePerm> for api::SimplePerm {
1569 fn from(value: SimplePerm) -> Self {
1570 match value {
1571 SimplePerm::AddDevice => api::SimplePerm::AddDevice,
1572 SimplePerm::RemoveDevice => api::SimplePerm::RemoveDevice,
1573 SimplePerm::TerminateTeam => api::SimplePerm::TerminateTeam,
1574 SimplePerm::CreateRole => api::SimplePerm::CreateRole,
1575 SimplePerm::DeleteRole => api::SimplePerm::DeleteRole,
1576 SimplePerm::AssignRole => api::SimplePerm::AssignRole,
1577 SimplePerm::RevokeRole => api::SimplePerm::RevokeRole,
1578 SimplePerm::ChangeRoleManagementPerms => api::SimplePerm::ChangeRoleManagementPerms,
1579 SimplePerm::SetupDefaultRole => api::SimplePerm::SetupDefaultRole,
1580 SimplePerm::ChangeRoleManagingRole => api::SimplePerm::ChangeRoleManagingRole,
1581 SimplePerm::CreateLabel => api::SimplePerm::CreateLabel,
1582 SimplePerm::DeleteLabel => api::SimplePerm::DeleteLabel,
1583 SimplePerm::ChangeLabelManagingRole => api::SimplePerm::ChangeLabelManagingRole,
1584 SimplePerm::AssignLabel => api::SimplePerm::AssignLabel,
1585 SimplePerm::RevokeLabel => api::SimplePerm::RevokeLabel,
1586 SimplePerm::CanUseAfc => api::SimplePerm::CanUseAfc,
1587 SimplePerm::CreateAfcUniChannel => api::SimplePerm::CreateAfcUniChannel,
1588 }
1589 }
1590}
1591
1592#[cfg(feature = "afc")]
1594fn get_afc_ctrl(cmds: Vec<Box<[u8]>>) -> anyhow::Result<Box<[u8]>> {
1595 let mut cmds = cmds.into_iter();
1596 let msg = cmds.next().context("missing AFC control message")?;
1597 if cmds.next().is_some() {
1598 anyhow::bail!("too many commands for AFC control message");
1599 }
1600 Ok(msg)
1601}