postcard_rpc/server/
mod.rs

1//! Definitions of a postcard-rpc Server
2//!
3//! The Server role is responsible for accepting endpoint requests, issuing
4//! endpoint responses, receiving client topic messages, and sending server
5//! topic messages
6//!
7//! ## Impls
8//!
9//! It is intended to allow postcard-rpc servers to be implemented for many
10//! different transport types, as well as many different operating environments.
11//!
12//! Examples of impls include:
13//!
14//! * A no-std impl using embassy and embassy-usb to provide transport over USB
15//! * A std impl using Tokio channels to provide transport for testing
16//!
17//! Impls are expected to implement three traits:
18//!
19//! * [`WireTx`]: how the server sends frames to the client
20//! * [`WireRx`]: how the server receives frames from the client
21//! * [`WireSpawn`]: how the server spawns worker tasks for certain handlers
22
23#![allow(async_fn_in_trait)]
24
25#[doc(hidden)]
26pub mod dispatch_macro;
27
28pub mod impls;
29
30use core::{fmt::Arguments, ops::DerefMut};
31
32use crate::{
33    header::{VarHeader, VarKey, VarKeyKind, VarSeq},
34    DeviceMap, Key, TopicDirection,
35};
36use postcard_schema::Schema;
37use serde::Serialize;
38use thiserror::Error;
39
40//////////////////////////////////////////////////////////////////////////////
41// TX
42//////////////////////////////////////////////////////////////////////////////
43
44/// This trait defines how the server sends frames to the client
45pub trait WireTx {
46    /// The error type of this connection.
47    ///
48    /// For simple cases, you can use [`WireTxErrorKind`] directly. You can also
49    /// use your own custom type that implements [`AsWireTxErrorKind`].
50    type Error: AsWireTxErrorKind;
51
52    /// Wait for the connection to be established
53    ///
54    /// Should be implemented for connection oriented wire protocols
55    async fn wait_connection(&self) {}
56
57    /// Send a single frame to the client, returning when send is complete.
58    async fn send<T: Serialize + ?Sized>(&self, hdr: VarHeader, msg: &T)
59        -> Result<(), Self::Error>;
60
61    /// Send a single frame to the client, without handling serialization
62    async fn send_raw(&self, buf: &[u8]) -> Result<(), Self::Error>;
63
64    /// Send a logging message on the [`LoggingTopic`][crate::standard_icd::LoggingTopic]
65    ///
66    /// This message is simpler as it does not do any formatting
67    async fn send_log_str(&self, kkind: VarKeyKind, s: &str) -> Result<(), Self::Error>;
68
69    /// Send a logging message on the [`LoggingTopic`][crate::standard_icd::LoggingTopic]
70    ///
71    /// This version formats to the outgoing buffer
72    async fn send_log_fmt<'a>(
73        &self,
74        kkind: VarKeyKind,
75        a: Arguments<'a>,
76    ) -> Result<(), Self::Error>;
77}
78
79/// The base [`WireTx`] Error Kind
80#[derive(Debug, Clone, Copy, Error)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[non_exhaustive]
83pub enum WireTxErrorKind {
84    /// The connection has been closed, and is unlikely to succeed until
85    /// the connection is re-established. This will cause the Server run
86    /// loop to terminate.
87    #[error("connection closed")]
88    ConnectionClosed,
89    /// Other unspecified errors
90    #[error("other")]
91    Other,
92    /// Timeout (WireTx impl specific) reached
93    #[error("timeout reached")]
94    Timeout,
95}
96
97/// A conversion trait to convert a user error into a base Kind type
98pub trait AsWireTxErrorKind: core::error::Error {
99    /// Convert the error type into a base type
100    fn as_kind(&self) -> WireTxErrorKind;
101}
102
103impl AsWireTxErrorKind for WireTxErrorKind {
104    #[inline]
105    fn as_kind(&self) -> WireTxErrorKind {
106        *self
107    }
108}
109
110//////////////////////////////////////////////////////////////////////////////
111// RX
112//////////////////////////////////////////////////////////////////////////////
113
114/// This trait defines how to receive a single frame from a client
115pub trait WireRx {
116    /// The error type of this connection.
117    ///
118    /// For simple cases, you can use [`WireRxErrorKind`] directly. You can also
119    /// use your own custom type that implements [`AsWireRxErrorKind`].
120    type Error: AsWireRxErrorKind;
121
122    /// Wait for the connection to be established
123    ///
124    /// Should be implemented for connection oriented wire protocols
125    async fn wait_connection(&mut self) {}
126
127    /// Receive a single frame
128    ///
129    /// On success, the portion of `buf` that contains a single frame is returned.
130    async fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a mut [u8], Self::Error>;
131}
132
133/// The base [`WireRx`] Error Kind
134#[derive(Debug, Clone, Copy, Error)]
135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
136#[non_exhaustive]
137pub enum WireRxErrorKind {
138    /// The connection has been closed, and is unlikely to succeed until
139    /// the connection is re-established. This will cause the Server run
140    /// loop to terminate.
141    #[error("connection closed")]
142    ConnectionClosed,
143    /// The received message was too large for the server to handle
144    #[error("the received message was too large for the server to handle")]
145    ReceivedMessageTooLarge,
146    /// Other message kinds
147    #[error("other")]
148    Other,
149}
150
151/// A conversion trait to convert a user error into a base Kind type
152pub trait AsWireRxErrorKind: core::error::Error {
153    /// Convert the error type into a base type
154    fn as_kind(&self) -> WireRxErrorKind;
155}
156
157impl AsWireRxErrorKind for WireRxErrorKind {
158    #[inline]
159    fn as_kind(&self) -> WireRxErrorKind {
160        *self
161    }
162}
163
164//////////////////////////////////////////////////////////////////////////////
165// SPAWN
166//////////////////////////////////////////////////////////////////////////////
167
168/// A trait to assist in spawning a handler task
169///
170/// This trait is weird, and mostly exists to abstract over how "normal" async
171/// executors like tokio spawn tasks, taking a future, and how unusual async
172/// executors like embassy spawn tasks, taking a task token that maps to static
173/// storage
174pub trait WireSpawn: Clone {
175    /// An error type returned when spawning fails. If this cannot happen,
176    /// [`Infallible`][core::convert::Infallible] can be used.
177    type Error;
178    /// The context used for spawning a task.
179    ///
180    /// For example, in tokio this is `()`, and in embassy this is `Spawner`.
181    type Info;
182
183    /// Retrieve [`Self::Info`]
184    fn info(&self) -> &Self::Info;
185}
186
187//////////////////////////////////////////////////////////////////////////////
188// SENDER (wrapper of WireTx)
189//////////////////////////////////////////////////////////////////////////////
190
191/// The [`Sender`] type wraps a [`WireTx`] impl, and provides higher level functionality
192/// over it
193#[derive(Clone)]
194pub struct Sender<Tx: WireTx> {
195    tx: Tx,
196    kkind: VarKeyKind,
197}
198
199impl<Tx: WireTx> Sender<Tx> {
200    /// Create a new Sender
201    ///
202    /// Takes a [`WireTx`] impl, as well as the [`VarKeyKind`] used when sending messages
203    /// to the client.
204    ///
205    /// `kkind` should usually come from [`Dispatch::min_key_len()`].
206    pub fn new(tx: Tx, kkind: VarKeyKind) -> Self {
207        Self { tx, kkind }
208    }
209
210    /// Send a reply for the given endpoint
211    #[inline]
212    pub async fn reply<E>(&self, seq_no: VarSeq, resp: &E::Response) -> Result<(), Tx::Error>
213    where
214        E: crate::Endpoint,
215        E::Response: Serialize + Schema,
216    {
217        let mut key = VarKey::Key8(E::RESP_KEY);
218        key.shrink_to(self.kkind);
219        let wh = VarHeader { key, seq_no };
220        self.tx.send::<E::Response>(wh, resp).await
221    }
222
223    /// Send a reply with the given Key
224    ///
225    /// This is useful when replying with "unusual" keys, for example Error responses
226    /// not tied to any specific Endpoint.
227    #[inline]
228    pub async fn reply_keyed<T>(&self, seq_no: VarSeq, key: Key, resp: &T) -> Result<(), Tx::Error>
229    where
230        T: ?Sized,
231        T: Serialize + Schema,
232    {
233        let mut key = VarKey::Key8(key);
234        key.shrink_to(self.kkind);
235        let wh = VarHeader { key, seq_no };
236        self.tx.send::<T>(wh, resp).await
237    }
238
239    /// Publish a Topic message
240    #[inline]
241    pub async fn publish<T>(&self, seq_no: VarSeq, msg: &T::Message) -> Result<(), Tx::Error>
242    where
243        T: ?Sized,
244        T: crate::Topic,
245        T::Message: Serialize + Schema,
246    {
247        let mut key = VarKey::Key8(T::TOPIC_KEY);
248        key.shrink_to(self.kkind);
249        let wh = VarHeader { key, seq_no };
250        self.tx.send::<T::Message>(wh, msg).await
251    }
252
253    /// Log a `str` directly to the [`LoggingTopic`][crate::standard_icd::LoggingTopic]
254    #[inline]
255    pub async fn log_str(&self, msg: &str) -> Result<(), Tx::Error> {
256        self.tx.send_log_str(self.kkind, msg).await
257    }
258
259    /// Format a message to the [`LoggingTopic`][crate::standard_icd::LoggingTopic]
260    #[inline]
261    pub async fn log_fmt(&self, msg: Arguments<'_>) -> Result<(), Tx::Error> {
262        self.tx.send_log_fmt(self.kkind, msg).await
263    }
264
265    /// Send a single error message
266    pub async fn error(
267        &self,
268        seq_no: VarSeq,
269        error: crate::standard_icd::WireError,
270    ) -> Result<(), Tx::Error> {
271        self.reply_keyed(seq_no, crate::standard_icd::ERROR_KEY, &error)
272            .await
273    }
274
275    /// Implements the [`GetAllSchemasEndpoint`][crate::standard_icd::GetAllSchemasEndpoint] endpoint
276    pub async fn send_all_schemas(
277        &self,
278        hdr: &VarHeader,
279        device_map: &DeviceMap,
280    ) -> Result<(), Tx::Error> {
281        #[cfg(feature = "use-std")]
282        use crate::standard_icd::OwnedSchemaData as SchemaData;
283        #[cfg(not(feature = "use-std"))]
284        use crate::standard_icd::SchemaData;
285        use crate::standard_icd::{GetAllSchemaDataTopic, GetAllSchemasEndpoint, SchemaTotals};
286
287        let mut msg_ctr = 0;
288        let mut err_ctr = 0;
289
290        // First, send all types
291        for ty in device_map.types {
292            let res = self
293                .publish::<GetAllSchemaDataTopic>(
294                    VarSeq::Seq2(msg_ctr),
295                    &SchemaData::Type((*ty).into()),
296                )
297                .await;
298            if res.is_err() {
299                err_ctr += 1;
300            };
301            msg_ctr += 1;
302        }
303
304        // Then all endpoints
305        for ep in device_map.endpoints {
306            let res = self
307                .publish::<GetAllSchemaDataTopic>(
308                    VarSeq::Seq2(msg_ctr),
309                    &SchemaData::Endpoint {
310                        path: ep.0.into(),
311                        request_key: ep.1,
312                        response_key: ep.2,
313                    },
314                )
315                .await;
316            if res.is_err() {
317                err_ctr += 1;
318            }
319
320            msg_ctr += 1;
321        }
322
323        // Then output topics
324        for to in device_map.topics_out {
325            let res = self
326                .publish::<GetAllSchemaDataTopic>(
327                    VarSeq::Seq2(msg_ctr),
328                    &SchemaData::Topic {
329                        direction: TopicDirection::ToClient,
330                        path: to.0.into(),
331                        key: to.1,
332                    },
333                )
334                .await;
335            if res.is_err() {
336                err_ctr += 1;
337            }
338            msg_ctr += 1;
339        }
340
341        // Then input topics
342        for ti in device_map.topics_in {
343            let res = self
344                .publish::<GetAllSchemaDataTopic>(
345                    VarSeq::Seq2(msg_ctr),
346                    &SchemaData::Topic {
347                        direction: TopicDirection::ToServer,
348                        path: ti.0.into(),
349                        key: ti.1,
350                    },
351                )
352                .await;
353            if res.is_err() {
354                err_ctr += 1;
355            }
356            msg_ctr += 1;
357        }
358
359        // Finally, reply with the totals
360        self.reply::<GetAllSchemasEndpoint>(
361            hdr.seq_no,
362            &SchemaTotals {
363                types_sent: device_map.types.len() as u32,
364                endpoints_sent: device_map.endpoints.len() as u32,
365                topics_in_sent: device_map.topics_in.len() as u32,
366                topics_out_sent: device_map.topics_out.len() as u32,
367                errors: err_ctr,
368            },
369        )
370        .await?;
371
372        Ok(())
373    }
374}
375
376//////////////////////////////////////////////////////////////////////////////
377// SERVER
378//////////////////////////////////////////////////////////////////////////////
379
380/// The [`Server`] is the main interface for handling communication
381pub struct Server<Tx, Rx, Buf, D>
382where
383    Tx: WireTx,
384    Rx: WireRx,
385    Buf: DerefMut<Target = [u8]>,
386    D: Dispatch<Tx = Tx>,
387{
388    tx: Sender<Tx>,
389    rx: Rx,
390    buf: Buf,
391    dis: D,
392}
393
394/// A type representing the different errors [`Server::run()`] may return
395#[derive(Debug, Error)]
396pub enum ServerError<Tx, Rx>
397where
398    Tx: WireTx,
399    Rx: WireRx,
400{
401    /// A fatal error occurred with the [`WireTx::send()`] implementation
402    #[error("A fatal error occurred while transmitting")]
403    TxFatal(#[source] Tx::Error),
404    /// A fatal error occurred with the [`WireRx::receive()`] implementation
405    #[error("A fatal error occurred while receiving")]
406    RxFatal(#[source] Rx::Error),
407}
408
409#[cfg(feature = "defmt")]
410impl<Tx, Rx> defmt::Format for ServerError<Tx, Rx>
411where
412    Tx: WireTx<Error: defmt::Format>,
413    Rx: WireRx<Error: defmt::Format>,
414{
415    fn format(&self, fmt: defmt::Formatter) {
416        match self {
417            ServerError::TxFatal(e) => defmt::write!(fmt, "Fatal Tx Error: {}", e),
418            ServerError::RxFatal(e) => defmt::write!(fmt, "Fatal Rx Error: {}", e),
419        }
420    }
421}
422
423impl<Tx, Rx, Buf, D> Server<Tx, Rx, Buf, D>
424where
425    Tx: WireTx,
426    Rx: WireRx,
427    Buf: DerefMut<Target = [u8]>,
428    D: Dispatch<Tx = Tx>,
429{
430    /// Create a new Server
431    ///
432    /// Takes:
433    ///
434    /// * a [`WireTx`] impl for sending
435    /// * a [`WireRx`] impl for receiving
436    /// * a buffer used for receiving frames
437    /// * The user provided dispatching method, usually generated by [`define_dispatch!()`][crate::define_dispatch]
438    /// * a [`VarKeyKind`], which controls the key sizes sent by the [`WireTx`] impl
439    pub fn new(tx: Tx, rx: Rx, buf: Buf, dis: D, kkind: VarKeyKind) -> Self {
440        Self {
441            tx: Sender { tx, kkind },
442            rx,
443            buf,
444            dis,
445        }
446    }
447
448    /// Run until a fatal error occurs
449    ///
450    /// The server will receive frames, and dispatch them. When a fatal error occurs,
451    /// this method will return with the fatal error.
452    ///
453    /// The caller may decide to wait until a connection is re-established, reset any
454    /// state, or immediately begin re-running.
455    pub async fn run(&mut self) -> ServerError<Tx, Rx> {
456        loop {
457            let Self {
458                tx,
459                rx,
460                buf,
461                dis: d,
462            } = self;
463            rx.wait_connection().await;
464            tx.tx.wait_connection().await;
465            let used = match rx.receive(buf).await {
466                Ok(u) => u,
467                Err(e) => {
468                    let kind = e.as_kind();
469                    match kind {
470                        WireRxErrorKind::ConnectionClosed => return ServerError::RxFatal(e),
471                        WireRxErrorKind::ReceivedMessageTooLarge => continue,
472                        WireRxErrorKind::Other => continue,
473                    }
474                }
475            };
476            let Some((hdr, body)) = VarHeader::take_from_slice(used) else {
477                // TODO: send a nak on badly formed messages? We don't have
478                // much to say because we don't have a key or seq no or anything
479                continue;
480            };
481            let fut = d.handle(tx, &hdr, body);
482            if let Err(e) = fut.await {
483                let kind = e.as_kind();
484                match kind {
485                    WireTxErrorKind::ConnectionClosed => return ServerError::TxFatal(e),
486                    WireTxErrorKind::Other => {}
487                    WireTxErrorKind::Timeout => return ServerError::TxFatal(e),
488                }
489            }
490        }
491    }
492}
493
494impl<Tx, Rx, Buf, D> Server<Tx, Rx, Buf, D>
495where
496    Tx: WireTx + Clone,
497    Rx: WireRx,
498    Buf: DerefMut<Target = [u8]>,
499    D: Dispatch<Tx = Tx>,
500{
501    /// Get a copy of the [`Sender`] to pass to tasks that need it
502    pub fn sender(&self) -> Sender<Tx> {
503        self.tx.clone()
504    }
505}
506
507//////////////////////////////////////////////////////////////////////////////
508// DISPATCH TRAIT
509//////////////////////////////////////////////////////////////////////////////
510
511/// The dispatch trait handles an incoming endpoint or topic message
512///
513/// The implementations of this trait are typically implemented by the
514/// [`define_dispatch!`][crate::define_dispatch] macro.
515pub trait Dispatch {
516    /// The [`WireTx`] impl used by this dispatcher
517    type Tx: WireTx;
518
519    /// The minimum key length required to avoid hash collisions
520    fn min_key_len(&self) -> VarKeyKind;
521
522    /// Handle a single incoming frame (endpoint or topic), and dispatch appropriately
523    async fn handle(
524        &mut self,
525        tx: &Sender<Self::Tx>,
526        hdr: &VarHeader,
527        body: &[u8],
528    ) -> Result<(), <Self::Tx as WireTx>::Error>;
529}
530
531//////////////////////////////////////////////////////////////////////////////
532// SPAWNCONTEXT TRAIT
533//////////////////////////////////////////////////////////////////////////////
534
535/// A conversion trait for taking the Context and making a SpawnContext
536///
537/// This is necessary if you use the `spawn` variant of `define_dispatch!`.
538pub trait SpawnContext {
539    /// The spawn context type
540    type SpawnCtxt: 'static;
541    /// A method to convert the regular context into [`Self::SpawnCtxt`]
542    fn spawn_ctxt(&mut self) -> Self::SpawnCtxt;
543}
544
545// Hilarious quadruply nested loop. Hope our lists are relatively small!
546macro_rules! keycheck {
547    (
548        $lists:ident;
549        $($num:literal => $func:ident;)*
550    ) => {
551        $(
552            {
553                let mut i = 0;
554                let mut good = true;
555                // For each list...
556                'dupe: while i < $lists.len() {
557                    let ilist = $lists[i];
558                    let mut j = 0;
559                    // And for each key in the list
560                    while j < ilist.len() {
561                        let jkey = ilist[j];
562                        let akey = $func(jkey);
563
564                        //
565                        // We now start checking against items later in the lists...
566                        //
567
568                        // For each list (starting with the one we are on)
569                        let mut x = i;
570                        while x < $lists.len() {
571                            // For each item...
572                            //
573                            // Note that for the STARTING list we continue where we started,
574                            // but on subsequent lists start from the beginning
575                            let xlist = $lists[x];
576                            let mut y = if x == i {
577                                j + 1
578                            } else {
579                                0
580                            };
581
582                            while y < xlist.len() {
583                                let ykey = xlist[y];
584                                let bkey = $func(ykey);
585
586                                if akey == bkey {
587                                    good = false;
588                                    break 'dupe;
589                                }
590                                y += 1;
591                            }
592                            x += 1;
593                        }
594                        j += 1;
595                    }
596                    i += 1;
597                }
598                if good {
599                    return $num;
600                }
601            }
602        )*
603    };
604}
605
606/// Calculates at const time the minimum number of bytes (1, 2, 4, or 8) to avoid
607/// hash collisions in the lists of keys provided.
608///
609/// If there are any duplicates, this function will panic at compile time. Otherwise,
610/// this function will return 1, 2, 4, or 8.
611///
612/// This function takes a very dumb "brute force" approach, that is of the order
613/// `O(4 * N^2 * M^2)`, where `N` is `lists.len()`, and `M` is the length of each
614/// sub-list. It is not recommended to call this outside of const context.
615pub const fn min_key_needed(lists: &[&[Key]]) -> usize {
616    const fn one(key: Key) -> u8 {
617        crate::Key1::from_key8(key).0
618    }
619    const fn two(key: Key) -> u16 {
620        u16::from_le_bytes(crate::Key2::from_key8(key).0)
621    }
622    const fn four(key: Key) -> u32 {
623        u32::from_le_bytes(crate::Key4::from_key8(key).0)
624    }
625    const fn eight(key: Key) -> u64 {
626        u64::from_le_bytes(key.to_bytes())
627    }
628
629    keycheck! {
630        lists;
631        1 => one;
632        2 => two;
633        4 => four;
634        8 => eight;
635    };
636
637    panic!("Collision requiring more than 8 bytes!");
638}
639
640#[cfg(test)]
641mod test {
642    use crate::{server::min_key_needed, Key};
643
644    #[test]
645    fn min_test_1() {
646        const MINA: usize = min_key_needed(&[&[
647            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) },
648            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) },
649        ]]);
650        assert_eq!(1, MINA);
651
652        const MINB: usize = min_key_needed(&[
653            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) }],
654            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) }],
655        ]);
656        assert_eq!(1, MINB);
657    }
658
659    #[test]
660    fn min_test_2() {
661        const MINA: usize = min_key_needed(&[&[
662            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) },
663            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01]) },
664        ]]);
665        assert_eq!(2, MINA);
666        const MINB: usize = min_key_needed(&[
667            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) }],
668            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01]) }],
669        ]);
670        assert_eq!(2, MINB);
671    }
672
673    #[test]
674    fn min_test_4() {
675        const MINA: usize = min_key_needed(&[&[
676            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) },
677            unsafe { Key::from_bytes([0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01]) },
678        ]]);
679        assert_eq!(4, MINA);
680        const MINB: usize = min_key_needed(&[
681            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) }],
682            &[unsafe { Key::from_bytes([0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01]) }],
683        ]);
684        assert_eq!(4, MINB);
685    }
686
687    #[test]
688    fn min_test_8() {
689        const MINA: usize = min_key_needed(&[&[
690            unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) },
691            unsafe { Key::from_bytes([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]) },
692        ]]);
693        assert_eq!(8, MINA);
694        const MINB: usize = min_key_needed(&[
695            &[unsafe { Key::from_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) }],
696            &[unsafe { Key::from_bytes([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]) }],
697        ]);
698        assert_eq!(8, MINB);
699    }
700}