aranya_daemon_api/
service.rs

1#![allow(clippy::disallowed_macros)] // tarpc uses unreachable
2
3use core::{
4    borrow::Borrow,
5    error, fmt,
6    hash::{Hash, Hasher},
7    net::SocketAddr,
8    ops::Deref,
9    time::Duration,
10};
11use std::collections::hash_map::{self, HashMap};
12
13use anyhow::bail;
14pub use aranya_crypto::aqc::CipherSuiteId;
15use aranya_crypto::{
16    aqc::{BidiPskId, UniPskId},
17    custom_id,
18    default::DefaultEngine,
19    id::IdError,
20    subtle::{Choice, ConstantTimeEq},
21    zeroize::{Zeroize, ZeroizeOnDrop},
22    EncryptionPublicKey, Engine, Id,
23};
24pub use aranya_policy_text::{text, Text};
25use aranya_util::Addr;
26use buggy::Bug;
27pub use semver::Version;
28use serde::{Deserialize, Serialize};
29use tracing::error;
30
31pub mod quic_sync;
32pub use quic_sync::*;
33
34/// CE = Crypto Engine
35pub type CE = DefaultEngine;
36/// CS = Cipher Suite
37pub type CS = <DefaultEngine as Engine>::CS;
38
39/// An error returned by the API.
40// TODO: enum?
41#[derive(Serialize, Deserialize, Debug)]
42pub struct Error(String);
43
44impl Error {
45    pub fn from_msg(err: &str) -> Self {
46        error!(?err);
47        Self(err.into())
48    }
49
50    pub fn from_err<E: error::Error>(err: E) -> Self {
51        error!(?err);
52        Self(format!("{err:?}"))
53    }
54}
55
56impl From<Bug> for Error {
57    fn from(err: Bug) -> Self {
58        error!(?err);
59        Self(format!("{err:?}"))
60    }
61}
62
63impl From<anyhow::Error> for Error {
64    fn from(err: anyhow::Error) -> Self {
65        error!(?err);
66        Self(format!("{err:?}"))
67    }
68}
69
70impl From<semver::Error> for Error {
71    fn from(err: semver::Error) -> Self {
72        error!(?err);
73        Self(format!("{err:?}"))
74    }
75}
76
77impl From<IdError> for Error {
78    fn from(err: IdError) -> Self {
79        error!(%err);
80        Self(err.to_string())
81    }
82}
83
84impl fmt::Display for Error {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        self.0.fmt(f)
87    }
88}
89
90impl error::Error for Error {}
91
92pub type Result<T, E = Error> = core::result::Result<T, E>;
93
94custom_id! {
95    /// The Device ID.
96    pub struct DeviceId;
97}
98
99custom_id! {
100    /// The Team ID (a.k.a Graph ID).
101    pub struct TeamId;
102}
103
104custom_id! {
105    /// An AQC label ID.
106    pub struct LabelId;
107}
108
109custom_id! {
110    /// An AQC bidi channel ID.
111    pub struct AqcBidiChannelId;
112}
113
114custom_id! {
115    /// An AQC uni channel ID.
116    pub struct AqcUniChannelId;
117}
118
119/// A device's public key bundle.
120#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
121pub struct KeyBundle {
122    pub identity: Vec<u8>,
123    pub signing: Vec<u8>,
124    pub encoding: Vec<u8>,
125}
126
127/// A device's role on the team.
128#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
129pub enum Role {
130    Owner,
131    Admin,
132    Operator,
133    Member,
134}
135
136// Note: any fields added to this type should be public
137/// A configuration for creating or adding a team to a daemon.
138#[derive(Debug, Serialize, Deserialize)]
139pub struct TeamConfig {
140    pub quic_sync: Option<QuicSyncConfig>,
141}
142
143/// A device's network identifier.
144#[derive(Clone, Debug, Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd)]
145pub struct NetIdentifier(pub Text);
146
147impl Borrow<str> for NetIdentifier {
148    #[inline]
149    fn borrow(&self) -> &str {
150        &self.0
151    }
152}
153
154impl<T> AsRef<T> for NetIdentifier
155where
156    T: ?Sized,
157    <Self as Deref>::Target: AsRef<T>,
158{
159    #[inline]
160    fn as_ref(&self) -> &T {
161        self.deref().as_ref()
162    }
163}
164
165impl Deref for NetIdentifier {
166    type Target = str;
167
168    #[inline]
169    fn deref(&self) -> &Self::Target {
170        &self.0
171    }
172}
173
174impl fmt::Display for NetIdentifier {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        self.0.fmt(f)
177    }
178}
179
180/// A serialized command for AQC.
181pub type AqcCtrl = Vec<Box<[u8]>>;
182
183/// A secret.
184#[derive(Clone, Debug, Serialize, Deserialize)]
185pub struct Secret(Box<[u8]>);
186
187impl Secret {
188    /// Provides access to the raw secret bytes.
189    #[inline]
190    pub fn raw_secret_bytes(&self) -> &[u8] {
191        &self.0
192    }
193}
194
195impl<T> From<T> for Secret
196where
197    T: Into<Box<[u8]>>,
198{
199    fn from(value: T) -> Self {
200        Self(value.into())
201    }
202}
203
204impl ConstantTimeEq for Secret {
205    fn ct_eq(&self, other: &Self) -> Choice {
206        self.0.ct_eq(&other.0)
207    }
208}
209
210impl ZeroizeOnDrop for Secret {}
211impl Drop for Secret {
212    fn drop(&mut self) {
213        self.0.zeroize()
214    }
215}
216
217macro_rules! psk_map {
218    (
219        $(#[$meta:meta])*
220        $vis:vis struct $name:ident(PskMap<$psk:ty>);
221    ) => {
222        $(#[$meta])*
223        #[derive(Clone, Debug, Serialize, Deserialize)]
224        #[cfg_attr(test, derive(PartialEq))]
225        $vis struct $name {
226            id: Id,
227            psks: HashMap<CsId, $psk>
228        }
229
230        impl $name {
231            /// Returns the number of PSKs.
232            pub fn len(&self) -> usize {
233                self.psks.len()
234            }
235
236            /// Reports whether `self` is empty.
237            pub fn is_empty(&self) -> bool {
238                self.psks.is_empty()
239            }
240
241            /// Returns the channel ID.
242            pub fn channel_id(&self) -> &Id {
243                &self.id
244            }
245
246            /// Returns the PSK for the cipher suite.
247            pub fn get(&self, suite: CipherSuiteId) -> Option<&$psk> {
248                self.psks.get(&CsId(suite))
249            }
250
251            /// Creates a PSK map from a function that generates
252            /// a PSK for a cipher suite.
253            pub fn try_from_fn<I, E, F>(id: I, mut f: F) -> anyhow::Result<Self>
254            where
255                I: Into<Id>,
256                anyhow::Error: From<E>,
257                F: FnMut(CipherSuiteId) -> Result<$psk, E>,
258            {
259                let id = id.into();
260                let mut psks = HashMap::new();
261                for &suite in CipherSuiteId::all() {
262                    let psk = f(suite)?;
263                    if !bool::from(psk.identity().channel_id().into_id().ct_eq(&id)) {
264                        bail!("PSK identity does not match channel ID");
265                    }
266                    psks.insert(CsId(suite), psk);
267                }
268                Ok(Self { id, psks })
269            }
270        }
271
272        impl IntoIterator for $name {
273            type Item = (CipherSuiteId, $psk);
274            type IntoIter = IntoPsks<$psk>;
275
276            fn into_iter(self) -> Self::IntoIter {
277                IntoPsks {
278                    iter: self.psks.into_iter(),
279                }
280            }
281        }
282
283        #[cfg(test)]
284        impl tests::PskMap for $name {
285            type Psk = $psk;
286
287            fn new() -> Self {
288                Self {
289                    // TODO
290                    id: Id::default(),
291                    psks: HashMap::new(),
292                }
293            }
294
295            fn len(&self) -> usize {
296                self.psks.len()
297            }
298
299            fn insert(&mut self, psk: Self::Psk) {
300                let suite = psk.cipher_suite();
301                let opt = self.psks.insert(CsId(suite), psk);
302                assert!(opt.is_none());
303            }
304        }
305    };
306}
307psk_map! {
308    /// An injective mapping of PSKs to cipher suites for
309    /// a single bidirectional channel.
310    pub struct AqcBidiPsks(PskMap<AqcBidiPsk>);
311}
312
313psk_map! {
314    /// An injective mapping of PSKs to cipher suites for
315    /// a single unidirectional channel.
316    pub struct AqcUniPsks(PskMap<AqcUniPsk>);
317}
318
319/// An injective mapping of PSKs to cipher suites for a single
320/// bidirectional or unidirectional channel.
321#[derive(Clone, Debug, Serialize, Deserialize)]
322pub enum AqcPsks {
323    Bidi(AqcBidiPsks),
324    Uni(AqcUniPsks),
325}
326
327impl IntoIterator for AqcPsks {
328    type IntoIter = AqcPsksIntoIter;
329    type Item = <Self::IntoIter as Iterator>::Item;
330
331    fn into_iter(self) -> Self::IntoIter {
332        match self {
333            AqcPsks::Bidi(psks) => AqcPsksIntoIter::Bidi(psks.into_iter()),
334            AqcPsks::Uni(psks) => AqcPsksIntoIter::Uni(psks.into_iter()),
335        }
336    }
337}
338
339/// An iterator over an AQC channel's PSKs.
340#[derive(Debug)]
341pub enum AqcPsksIntoIter {
342    Bidi(IntoPsks<AqcBidiPsk>),
343    Uni(IntoPsks<AqcUniPsk>),
344}
345
346impl Iterator for AqcPsksIntoIter {
347    type Item = (CipherSuiteId, AqcPsk);
348    fn next(&mut self) -> Option<Self::Item> {
349        match self {
350            AqcPsksIntoIter::Bidi(it) => it.next().map(|(s, k)| (s, AqcPsk::Bidi(k))),
351            AqcPsksIntoIter::Uni(it) => it.next().map(|(s, k)| (s, AqcPsk::Uni(k))),
352        }
353    }
354}
355
356/// An iterator over an AQC channel's PSKs.
357#[derive(Debug)]
358pub struct IntoPsks<V> {
359    iter: hash_map::IntoIter<CsId, V>,
360}
361
362impl<V> Iterator for IntoPsks<V> {
363    type Item = (CipherSuiteId, V);
364
365    fn next(&mut self) -> Option<Self::Item> {
366        self.iter.next().map(|(k, v)| (k.0, v))
367    }
368}
369
370// TODO(eric): Get rid of this once `CipherSuiteId` implements
371// `Hash`.
372#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
373#[serde(transparent)]
374struct CsId(CipherSuiteId);
375
376impl Hash for CsId {
377    fn hash<H: Hasher>(&self, state: &mut H) {
378        self.0.to_bytes().hash(state);
379    }
380}
381
382/// An AQC PSK.
383#[derive(Clone, Debug, Serialize, Deserialize)]
384pub enum AqcPsk {
385    /// Bidirectional.
386    Bidi(AqcBidiPsk),
387    /// Unidirectional.
388    Uni(AqcUniPsk),
389}
390
391impl AqcPsk {
392    /// Returns the PSK identity.
393    #[inline]
394    pub fn identity(&self) -> AqcPskId {
395        match self {
396            Self::Bidi(psk) => AqcPskId::Bidi(psk.identity),
397            Self::Uni(psk) => AqcPskId::Uni(psk.identity),
398        }
399    }
400
401    /// Returns the PSK cipher suite.
402    #[inline]
403    pub fn cipher_suite(&self) -> CipherSuiteId {
404        self.identity().cipher_suite()
405    }
406
407    /// Returns the PSK secret.
408    #[inline]
409    pub fn secret(&self) -> &[u8] {
410        match self {
411            Self::Bidi(psk) => psk.secret.raw_secret_bytes(),
412            Self::Uni(psk) => match &psk.secret {
413                Directed::Send(secret) | Directed::Recv(secret) => secret.raw_secret_bytes(),
414            },
415        }
416    }
417}
418
419impl From<AqcBidiPsk> for AqcPsk {
420    fn from(psk: AqcBidiPsk) -> Self {
421        Self::Bidi(psk)
422    }
423}
424
425impl From<AqcUniPsk> for AqcPsk {
426    fn from(psk: AqcUniPsk) -> Self {
427        Self::Uni(psk)
428    }
429}
430
431impl ConstantTimeEq for AqcPsk {
432    fn ct_eq(&self, other: &Self) -> Choice {
433        // It's fine that matching discriminants isn't constant
434        // time since it isn't secret data.
435        match (self, other) {
436            (Self::Bidi(lhs), Self::Bidi(rhs)) => lhs.ct_eq(rhs),
437            (Self::Uni(lhs), Self::Uni(rhs)) => lhs.ct_eq(rhs),
438            _ => Choice::from(0u8),
439        }
440    }
441}
442
443/// An AQC bidirectional channel PSK.
444#[derive(Clone, Debug, Serialize, Deserialize)]
445pub struct AqcBidiPsk {
446    /// The PSK identity.
447    pub identity: BidiPskId,
448    /// The PSK's secret.
449    pub secret: Secret,
450}
451
452impl AqcBidiPsk {
453    fn identity(&self) -> &BidiPskId {
454        &self.identity
455    }
456
457    #[cfg(test)]
458    fn cipher_suite(&self) -> CipherSuiteId {
459        self.identity.cipher_suite()
460    }
461}
462
463impl ConstantTimeEq for AqcBidiPsk {
464    fn ct_eq(&self, other: &Self) -> Choice {
465        let id = self.identity.ct_eq(&other.identity);
466        let secret = self.secret.ct_eq(&other.secret);
467        id & secret
468    }
469}
470
471impl ZeroizeOnDrop for AqcBidiPsk {}
472
473/// An AQC unidirectional PSK.
474#[derive(Clone, Debug, Serialize, Deserialize)]
475pub struct AqcUniPsk {
476    /// The PSK identity.
477    pub identity: UniPskId,
478    /// The PSK's secret.
479    pub secret: Directed<Secret>,
480}
481
482impl AqcUniPsk {
483    fn identity(&self) -> &UniPskId {
484        &self.identity
485    }
486
487    #[cfg(test)]
488    fn cipher_suite(&self) -> CipherSuiteId {
489        self.identity.cipher_suite()
490    }
491}
492
493impl ConstantTimeEq for AqcUniPsk {
494    fn ct_eq(&self, other: &Self) -> Choice {
495        let id = self.identity.ct_eq(&other.identity);
496        let secret = self.secret.ct_eq(&other.secret);
497        id & secret
498    }
499}
500
501impl ZeroizeOnDrop for AqcUniPsk {}
502
503/// Either send only or receive only.
504#[derive(Clone, Debug, Serialize, Deserialize)]
505pub enum Directed<T> {
506    /// Send only.
507    Send(T),
508    /// Receive only.
509    Recv(T),
510}
511
512impl<T: ConstantTimeEq> ConstantTimeEq for Directed<T> {
513    fn ct_eq(&self, other: &Self) -> Choice {
514        // It's fine that matching discriminants isn't constant
515        // time since the direction isn't secret data.
516        match (self, other) {
517            (Self::Send(lhs), Self::Send(rhs)) => lhs.ct_eq(rhs),
518            (Self::Recv(lhs), Self::Recv(rhs)) => lhs.ct_eq(rhs),
519            _ => Choice::from(0u8),
520        }
521    }
522}
523
524/// An AQC PSK identity.
525#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
526pub enum AqcPskId {
527    /// A bidirectional PSK.
528    Bidi(BidiPskId),
529    /// A unidirectional PSK.
530    Uni(UniPskId),
531}
532
533impl AqcPskId {
534    /// Returns the unique channel ID.
535    pub fn channel_id(&self) -> Id {
536        match self {
537            Self::Bidi(v) => (*v.channel_id()).into(),
538            Self::Uni(v) => (*v.channel_id()).into(),
539        }
540    }
541
542    /// Returns the cipher suite.
543    pub fn cipher_suite(&self) -> CipherSuiteId {
544        match self {
545            Self::Bidi(v) => v.cipher_suite(),
546            Self::Uni(v) => v.cipher_suite(),
547        }
548    }
549
550    /// Converts the ID to its byte encoding.
551    pub fn as_bytes(&self) -> &[u8; 34] {
552        match self {
553            Self::Bidi(v) => v.as_bytes(),
554            Self::Uni(v) => v.as_bytes(),
555        }
556    }
557}
558
559/// Configuration values for syncing with a peer
560#[derive(Clone, Debug, Serialize, Deserialize)]
561pub struct SyncPeerConfig {
562    /// The interval at which syncing occurs
563    pub interval: Duration,
564    /// Determines if a peer should be synced with immediately after they're added
565    pub sync_now: bool,
566}
567
568/// Valid channel operations for a label assignment.
569#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
570pub enum ChanOp {
571    /// The device can only receive data in channels with this
572    /// label.
573    RecvOnly,
574    /// The device can only send data in channels with this
575    /// label.
576    SendOnly,
577    /// The device can send and receive data in channels with this
578    /// label.
579    SendRecv,
580}
581
582/// A label.
583#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
584pub struct Label {
585    pub id: LabelId,
586    pub name: Text,
587}
588
589#[tarpc::service]
590pub trait DaemonApi {
591    /// Returns the daemon's version.
592    async fn version() -> Result<Version>;
593
594    /// Gets local address the Aranya sync server is bound to.
595    async fn aranya_local_addr() -> Result<SocketAddr>;
596
597    /// Gets the public key bundle for this device
598    async fn get_key_bundle() -> Result<KeyBundle>;
599
600    /// Gets the public device id.
601    async fn get_device_id() -> Result<DeviceId>;
602
603    /// Adds the peer for automatic periodic syncing.
604    async fn add_sync_peer(addr: Addr, team: TeamId, config: SyncPeerConfig) -> Result<()>;
605
606    /// Sync with peer immediately.
607    async fn sync_now(addr: Addr, team: TeamId, cfg: Option<SyncPeerConfig>) -> Result<()>;
608
609    /// Removes the peer from automatic syncing.
610    async fn remove_sync_peer(addr: Addr, team: TeamId) -> Result<()>;
611
612    /// add a team to the local device store that was created by someone else. Not an aranya action/command.
613    async fn add_team(team: TeamId, cfg: TeamConfig) -> Result<()>;
614
615    /// Remove a team from local device storage.
616    async fn remove_team(team: TeamId) -> Result<()>;
617
618    /// Create a new graph/team with the current device as the owner.
619    async fn create_team(cfg: TeamConfig) -> Result<TeamId>;
620    /// Close the team.
621    async fn close_team(team: TeamId) -> Result<()>;
622
623    async fn encrypt_psk_seed_for_peer(
624        team: TeamId,
625        peer_enc_pk: EncryptionPublicKey<CS>,
626    ) -> Result<WrappedSeed>;
627
628    /// Add device to the team.
629    async fn add_device_to_team(team: TeamId, keys: KeyBundle) -> Result<()>;
630    /// Remove device from the team.
631    async fn remove_device_from_team(team: TeamId, device: DeviceId) -> Result<()>;
632
633    /// Assign a role to a device.
634    async fn assign_role(team: TeamId, device: DeviceId, role: Role) -> Result<()>;
635    /// Revoke a role from a device.
636    async fn revoke_role(team: TeamId, device: DeviceId, role: Role) -> Result<()>;
637
638    /// Assign a QUIC channels network identifier to a device.
639    async fn assign_aqc_net_identifier(
640        team: TeamId,
641        device: DeviceId,
642        name: NetIdentifier,
643    ) -> Result<()>;
644    /// Remove a QUIC channels network identifier from a device.
645    async fn remove_aqc_net_identifier(
646        team: TeamId,
647        device: DeviceId,
648        name: NetIdentifier,
649    ) -> Result<()>;
650
651    // Create a label.
652    async fn create_label(team: TeamId, name: Text) -> Result<LabelId>;
653    // Delete a label.
654    async fn delete_label(team: TeamId, label_id: LabelId) -> Result<()>;
655    // Assign a label to a device.
656    async fn assign_label(
657        team: TeamId,
658        device: DeviceId,
659        label_id: LabelId,
660        op: ChanOp,
661    ) -> Result<()>;
662    // Revoke a label from a device.
663    async fn revoke_label(team: TeamId, device: DeviceId, label_id: LabelId) -> Result<()>;
664
665    /// Create a bidirectional QUIC channel.
666    async fn create_aqc_bidi_channel(
667        team: TeamId,
668        peer: NetIdentifier,
669        label_id: LabelId,
670    ) -> Result<(AqcCtrl, AqcBidiPsks)>;
671    /// Create a unidirectional QUIC channel.
672    async fn create_aqc_uni_channel(
673        team: TeamId,
674        peer: NetIdentifier,
675        label_id: LabelId,
676    ) -> Result<(AqcCtrl, AqcUniPsks)>;
677    /// Delete a QUIC bidi channel.
678    async fn delete_aqc_bidi_channel(chan: AqcBidiChannelId) -> Result<AqcCtrl>;
679    /// Delete a QUIC uni channel.
680    async fn delete_aqc_uni_channel(chan: AqcUniChannelId) -> Result<AqcCtrl>;
681    /// Receive AQC ctrl message.
682    async fn receive_aqc_ctrl(team: TeamId, ctrl: AqcCtrl) -> Result<(LabelId, AqcPsks)>;
683
684    /// Query devices on team.
685    async fn query_devices_on_team(team: TeamId) -> Result<Vec<DeviceId>>;
686    /// Query device role.
687    async fn query_device_role(team: TeamId, device: DeviceId) -> Result<Role>;
688    /// Query device keybundle.
689    async fn query_device_keybundle(team: TeamId, device: DeviceId) -> Result<KeyBundle>;
690    /// Query device label assignments.
691    async fn query_device_label_assignments(team: TeamId, device: DeviceId) -> Result<Vec<Label>>;
692    /// Query AQC network ID.
693    async fn query_aqc_net_identifier(
694        team: TeamId,
695        device: DeviceId,
696    ) -> Result<Option<NetIdentifier>>;
697    // Query labels on team.
698    async fn query_labels(team: TeamId) -> Result<Vec<Label>>;
699    /// Query whether a label exists.
700    async fn query_label_exists(team: TeamId, label: LabelId) -> Result<bool>;
701}
702
703#[cfg(test)]
704mod tests {
705    use aranya_crypto::Rng;
706    use serde::de::DeserializeOwned;
707
708    use super::*;
709
710    fn secret(secret: &[u8]) -> Secret {
711        Secret(Box::from(secret))
712    }
713
714    pub(super) trait PskMap:
715        fmt::Debug + PartialEq + Serialize + DeserializeOwned + Sized
716    {
717        type Psk;
718        fn new() -> Self;
719        /// Returns the number of PSKs in the map.
720        fn len(&self) -> usize;
721        /// Adds `psk` to the map.
722        ///
723        /// # Panics
724        ///
725        /// Panics if `psk` already exists.
726        fn insert(&mut self, psk: Self::Psk);
727    }
728
729    impl PartialEq for AqcBidiPsk {
730        fn eq(&self, other: &Self) -> bool {
731            bool::from(self.ct_eq(other))
732        }
733    }
734    impl PartialEq for AqcUniPsk {
735        fn eq(&self, other: &Self) -> bool {
736            bool::from(self.ct_eq(other))
737        }
738    }
739    impl PartialEq for AqcPsk {
740        fn eq(&self, other: &Self) -> bool {
741            bool::from(self.ct_eq(other))
742        }
743    }
744
745    #[track_caller]
746    fn psk_map_test<M, F>(name: &'static str, mut f: F)
747    where
748        M: PskMap,
749        F: FnMut(Secret, Id, CipherSuiteId) -> M::Psk,
750    {
751        let mut psks = M::new();
752        for (i, &suite) in CipherSuiteId::all().iter().enumerate() {
753            let id = Id::random(&mut Rng);
754            let secret = secret(&i.to_le_bytes());
755            psks.insert(f(secret, id, suite));
756        }
757        assert_eq!(psks.len(), CipherSuiteId::all().len(), "{name}");
758
759        let bytes = postcard::to_allocvec(&psks).unwrap();
760        let got = postcard::from_bytes::<M>(&bytes).unwrap();
761        assert_eq!(got, psks, "{name}")
762    }
763
764    /// Test that we can correctly serialize and deserialize
765    /// [`AqcBidiPsk`].
766    #[test]
767    fn test_aqc_bidi_psks_serde() {
768        psk_map_test::<AqcBidiPsks, _>("AqcBidiPsk", |secret, id, suite| AqcBidiPsk {
769            identity: BidiPskId::from((id.into(), suite)),
770            secret,
771        });
772    }
773
774    /// Test that we can correctly serialize and deserialize
775    /// [`AqcUniPsk`].
776    #[test]
777    fn test_aqc_uni_psks_serde() {
778        psk_map_test::<AqcUniPsks, _>("AqcUniPsk (send)", |secret, id, suite| AqcUniPsk {
779            identity: UniPskId::from((id.into(), suite)),
780            secret: Directed::Send(secret),
781        });
782        psk_map_test::<AqcUniPsks, _>("AqcUniPsk (recv)", |secret, id, suite| AqcUniPsk {
783            identity: UniPskId::from((id.into(), suite)),
784            secret: Directed::Recv(secret),
785        });
786    }
787}