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}