1use core::future::Future;
2use core::mem::MaybeUninit;
3
4use embassy_futures::join::join;
5use embassy_sync::blocking_mutex::raw::RawMutex;
6use embassy_sync::channel::{Channel, Receiver};
7use embedded_storage_async::nor_flash::NorFlash;
8use heapless::Vec as HeaplessVec;
9use static_cell::StaticCell;
10
11use crate::engine::{IssuedCommand, Journaled, ProofRequest, MAX_SEND_REQUEST_DATA_LEN};
12use crate::interfaces::{InterfaceDescriptor, InterfaceId, InterfaceIfac};
13use crate::manifold::driver::{
14 run_pooled, InterfaceLifecycle, PooledEgress, PooledWiring, ResumableHost,
15};
16use crate::manifold::grant::ManifoldLaneReader;
17use crate::manifold::Host;
18use crate::storage::StorageLayout;
19
20use super::super::request_endpoints::RequestEndpointSet;
21use super::super::request_runner::{run_router, RunnerRequest};
22use super::super::{
23 EmbassyInterfaceStore, EmbeddedFlashPersistence, EmbeddedPersistenceDiagnostic,
24 EmbeddedPersistenceRestoreReport, InterfaceInspectionStore, ManifoldPersistence,
25 ManuallyAttached, NoInterfaceInspectionStore, NoManifoldPersistence, PreConfiguredDestination,
26 PrnsEvent, PrnsNodeRecipe, RouteSnapshotKeys,
27};
28use super::command_handle::PrnsNodeHandle;
29use prns_runtime::runtime::placement::assemble_node_in_place;
30use prns_runtime::runtime::{assemble_node, AssembledNode, NoPersistence};
31
32pub struct ManifoldWiring<
33 M,
34 const LANE_COUNT: usize,
35 const NOTIFY: usize,
36 const COMMANDS: usize,
37 const LIFECYCLE: usize,
38 const COMPLETIONS: usize,
39> where
40 M: RawMutex + 'static,
41{
42 pub(super) inbound: HeaplessVec<(InterfaceId, &'static mut dyn ManifoldLaneReader), LANE_COUNT>,
43 pub(super) egress: PooledEgress<LANE_COUNT>,
44 pub(super) initial: HeaplessVec<InterfaceDescriptor, LANE_COUNT>,
45 pub(super) ifacs: HeaplessVec<InterfaceIfac, LANE_COUNT>,
46 pub(super) notify: Receiver<'static, M, InterfaceId, NOTIFY>,
47 pub(super) commands: Receiver<'static, M, IssuedCommand, COMMANDS>,
48 pub(super) lifecycle: Receiver<'static, M, InterfaceLifecycle, LIFECYCLE>,
49 pub(super) handle: PrnsNodeHandle<'static, M, COMMANDS, COMPLETIONS>,
50}
51
52pub struct PrnsNode<
53 St,
54 R,
55 F,
56 S,
57 H,
58 M,
59 const LANE_COUNT: usize,
60 const INTERFACE_CAPACITY: usize,
61 const NOTIFY: usize,
62 const COMMANDS: usize,
63 const LIFECYCLE: usize,
64 const COMPLETIONS: usize,
65 const ROUTED_REQUESTS: usize = 4,
66 const ROUTED_REQUEST_BYTES: usize = MAX_SEND_REQUEST_DATA_LEN,
67> where
68 S: StorageLayout,
69 M: RawMutex + 'static,
70{
71 node: AssembledNode<St, R, F, S>,
72 inbound: HeaplessVec<(InterfaceId, &'static mut dyn ManifoldLaneReader), LANE_COUNT>,
73 egress: PooledEgress<LANE_COUNT>,
74 notify: Receiver<'static, M, InterfaceId, NOTIFY>,
75 commands: Receiver<'static, M, IssuedCommand, COMMANDS>,
76 lifecycle: Receiver<'static, M, InterfaceLifecycle, LIFECYCLE>,
77 handle: PrnsNodeHandle<'static, M, COMMANDS, COMPLETIONS>,
78 host: H,
79 descriptors: HeaplessVec<InterfaceDescriptor, INTERFACE_CAPACITY>,
80 ifacs: HeaplessVec<InterfaceIfac, LANE_COUNT>,
81}
82
83pub struct RequestRoutingCapacity<const REQUESTS: usize, const REQUEST_BYTES: usize>;
84
85impl<const REQUESTS: usize, const REQUEST_BYTES: usize> Default
86 for RequestRoutingCapacity<REQUESTS, REQUEST_BYTES>
87{
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl<const REQUESTS: usize, const REQUEST_BYTES: usize>
94 RequestRoutingCapacity<REQUESTS, REQUEST_BYTES>
95{
96 #[must_use]
97 pub const fn new() -> Self {
98 Self
99 }
100}
101
102impl<
103 St,
104 R,
105 F,
106 S,
107 H,
108 M,
109 const LANE_COUNT: usize,
110 const INTERFACE_CAPACITY: usize,
111 const NOTIFY: usize,
112 const COMMANDS: usize,
113 const LIFECYCLE: usize,
114 const COMPLETIONS: usize,
115 >
116 PrnsNode<
117 St,
118 R,
119 F,
120 S,
121 H,
122 M,
123 LANE_COUNT,
124 INTERFACE_CAPACITY,
125 NOTIFY,
126 COMMANDS,
127 LIFECYCLE,
128 COMPLETIONS,
129 4,
130 MAX_SEND_REQUEST_DATA_LEN,
131 >
132where
133 R: RequestEndpointSet<St>,
134 F: FnMut(PrnsEvent<'_>, &St),
135 S: StorageLayout,
136 H: Host,
137 M: RawMutex + 'static,
138{
139 pub fn new<'d, D>(
140 recipe: PrnsNodeRecipe<D, St, R, F, ManuallyAttached, S>,
141 wiring: ManifoldWiring<M, LANE_COUNT, NOTIFY, COMMANDS, LIFECYCLE, COMPLETIONS>,
142 host: H,
143 ) -> Self
144 where
145 D: IntoIterator<Item = PreConfiguredDestination<'d>>,
146 {
147 Self::build(recipe, wiring, host)
148 }
149}
150
151impl<
152 St,
153 R,
154 F,
155 S,
156 H,
157 M,
158 const LANE_COUNT: usize,
159 const INTERFACE_CAPACITY: usize,
160 const NOTIFY: usize,
161 const COMMANDS: usize,
162 const LIFECYCLE: usize,
163 const COMPLETIONS: usize,
164 const ROUTED_REQUESTS: usize,
165 const ROUTED_REQUEST_BYTES: usize,
166 >
167 PrnsNode<
168 St,
169 R,
170 F,
171 S,
172 H,
173 M,
174 LANE_COUNT,
175 INTERFACE_CAPACITY,
176 NOTIFY,
177 COMMANDS,
178 LIFECYCLE,
179 COMPLETIONS,
180 ROUTED_REQUESTS,
181 ROUTED_REQUEST_BYTES,
182 >
183where
184 R: RequestEndpointSet<St>,
185 F: FnMut(PrnsEvent<'_>, &St),
186 S: StorageLayout,
187 H: Host,
188 M: RawMutex + 'static,
189{
190 pub fn init_static<'d, D>(
191 cell: &'static StaticCell<Self>,
192 recipe: PrnsNodeRecipe<D, St, R, F, ManuallyAttached, S>,
193 wiring: ManifoldWiring<M, LANE_COUNT, NOTIFY, COMMANDS, LIFECYCLE, COMPLETIONS>,
194 host: H,
195 ) -> &'static mut Self
196 where
197 D: IntoIterator<Item = PreConfiguredDestination<'d>>,
198 {
199 let (node, NoPersistence) = Self::init_static_with_persistence(cell, recipe, wiring, host);
200 node
201 }
202
203 #[expect(
204 unsafe_code,
205 clippy::undocumented_unsafe_blocks,
206 clippy::mut_from_ref,
207 reason = "every PrnsNode field is initialized before the slot is exposed"
208 )]
209 pub fn init_static_with_persistence<'d, D, P>(
210 cell: &'static StaticCell<Self>,
211 recipe: PrnsNodeRecipe<D, St, R, F, ManuallyAttached, S, P>,
212 wiring: ManifoldWiring<M, LANE_COUNT, NOTIFY, COMMANDS, LIFECYCLE, COMPLETIONS>,
213 host: H,
214 ) -> (&'static mut Self, P)
215 where
216 D: IntoIterator<Item = PreConfiguredDestination<'d>>,
217 {
218 const {
219 assert!(
220 INTERFACE_CAPACITY >= LANE_COUNT,
221 "PrnsNode INTERFACE_CAPACITY must cover every manifold lane"
222 );
223 }
224 let slot = cell.uninit();
225 let ManifoldWiring {
226 inbound,
227 egress,
228 initial,
229 ifacs,
230 notify,
231 commands,
232 lifecycle,
233 handle,
234 } = wiring;
235 let node = slot.as_mut_ptr();
236 let persistence = unsafe {
237 let assembled = &mut *core::ptr::addr_of_mut!((*node).node)
238 .cast::<MaybeUninit<AssembledNode<St, R, F, S>>>();
239 let (_, ManuallyAttached, persistence) = assemble_node_in_place(assembled, recipe);
240 core::ptr::addr_of_mut!((*node).inbound).write(inbound);
241 core::ptr::addr_of_mut!((*node).egress).write(egress);
242 core::ptr::addr_of_mut!((*node).notify).write(notify);
243 core::ptr::addr_of_mut!((*node).commands).write(commands);
244 core::ptr::addr_of_mut!((*node).lifecycle).write(lifecycle);
245 core::ptr::addr_of_mut!((*node).handle).write(handle);
246 core::ptr::addr_of_mut!((*node).host).write(host);
247 core::ptr::addr_of_mut!((*node).descriptors).write(HeaplessVec::new());
248 core::ptr::addr_of_mut!((*node).ifacs).write(ifacs);
249 persistence
250 };
251 let node = unsafe { slot.assume_init_mut() };
252 for descriptor in initial {
253 if node.descriptors.push(descriptor).is_err() {
254 unreachable!()
255 }
256 }
257 (node, persistence)
258 }
259
260 pub fn new_with_request_capacity<'d, D>(
261 recipe: PrnsNodeRecipe<D, St, R, F, ManuallyAttached, S>,
262 wiring: ManifoldWiring<M, LANE_COUNT, NOTIFY, COMMANDS, LIFECYCLE, COMPLETIONS>,
263 host: H,
264 _capacity: RequestRoutingCapacity<ROUTED_REQUESTS, ROUTED_REQUEST_BYTES>,
265 ) -> Self
266 where
267 D: IntoIterator<Item = PreConfiguredDestination<'d>>,
268 {
269 Self::build(recipe, wiring, host)
270 }
271
272 fn build<'d, D>(
273 recipe: PrnsNodeRecipe<D, St, R, F, ManuallyAttached, S>,
274 wiring: ManifoldWiring<M, LANE_COUNT, NOTIFY, COMMANDS, LIFECYCLE, COMPLETIONS>,
275 host: H,
276 ) -> Self
277 where
278 D: IntoIterator<Item = PreConfiguredDestination<'d>>,
279 {
280 const {
281 assert!(
282 INTERFACE_CAPACITY >= LANE_COUNT,
283 "PrnsNode INTERFACE_CAPACITY must cover every manifold lane"
284 );
285 }
286 let (node, ManuallyAttached, NoPersistence) = assemble_node(recipe);
287 let mut descriptors = HeaplessVec::new();
288 for descriptor in wiring.initial {
289 if descriptors.push(descriptor).is_err() {
290 unreachable!()
291 }
292 }
293
294 PrnsNode {
295 node,
296 inbound: wiring.inbound,
297 egress: wiring.egress,
298 notify: wiring.notify,
299 commands: wiring.commands,
300 lifecycle: wiring.lifecycle,
301 handle: wiring.handle,
302 host,
303 descriptors,
304 ifacs: wiring.ifacs,
305 }
306 }
307
308 pub fn set_protocol_policy(&mut self, policy: crate::engine::EngineProtocolPolicy) {
309 self.node.engine.set_protocol_policy(policy);
310 }
311
312 #[must_use]
313 pub fn handle(&self) -> PrnsNodeHandle<'static, M, COMMANDS, COMPLETIONS> {
314 self.handle
315 }
316
317 pub async fn run(self, drive: impl Future<Output = ()>) {
319 self.run_with_inspection_store(&NoInterfaceInspectionStore, drive)
320 .await;
321 }
322
323 pub async fn run_with_proof_decider<P>(self, should_prove: P, drive: impl Future<Output = ()>)
330 where
331 P: FnMut(&ProofRequest) -> bool,
332 {
333 self.run_with_inspection_store_and_proof_decider(
334 &NoInterfaceInspectionStore,
335 should_prove,
336 drive,
337 )
338 .await;
339 }
340
341 pub async fn run_with_interface_store<
342 const INTERFACES: usize,
343 const PACKET_PHY_CAPACITY: usize,
344 const PACKET_PHY_INDEX_BUCKETS: usize,
345 >(
346 self,
347 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
348 drive: impl Future<Output = ()>,
349 ) where
350 M: Sync,
351 {
352 const {
353 assert!(
354 INTERFACES >= INTERFACE_CAPACITY,
355 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
356 );
357 }
358 self.run_with_inspection_store(store, drive).await;
359 }
360
361 pub async fn run_with_interface_store_and_proof_decider<
362 P,
363 const INTERFACES: usize,
364 const PACKET_PHY_CAPACITY: usize,
365 const PACKET_PHY_INDEX_BUCKETS: usize,
366 >(
367 self,
368 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
369 should_prove: P,
370 drive: impl Future<Output = ()>,
371 ) where
372 M: Sync,
373 P: FnMut(&ProofRequest) -> bool,
374 {
375 const {
376 assert!(
377 INTERFACES >= INTERFACE_CAPACITY,
378 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
379 );
380 }
381 self.run_with_inspection_store_and_proof_decider(store, should_prove, drive)
382 .await;
383 }
384
385 async fn run_with_inspection_store<Store>(self, store: &Store, drive: impl Future<Output = ()>)
386 where
387 Store: InterfaceInspectionStore,
388 {
389 self.run_with_inspection_store_and_proof_decider(store, |_| false, drive)
390 .await;
391 }
392
393 async fn run_with_inspection_store_and_proof_decider<Store, P>(
394 self,
395 store: &Store,
396 should_prove: P,
397 drive: impl Future<Output = ()>,
398 ) where
399 Store: InterfaceInspectionStore,
400 P: FnMut(&ProofRequest) -> bool,
401 {
402 let PrnsNode {
403 node,
404 mut inbound,
405 mut egress,
406 notify,
407 commands,
408 lifecycle,
409 handle,
410 mut host,
411 mut descriptors,
412 mut ifacs,
413 } = self;
414 let AssembledNode {
415 mut engine,
416 state,
417 mut on_event,
418 request_endpoints: _,
419 } = node;
420 let request_channel =
421 Channel::<M, RunnerRequest<ROUTED_REQUEST_BYTES>, ROUTED_REQUESTS>::new();
422 let request_sender = request_channel.sender();
423 let mut persistence = NoManifoldPersistence;
424 let manifold = run_pooled(
425 &mut engine,
426 &mut host,
427 PooledWiring {
428 descriptors: &mut descriptors,
429 ifacs: &mut ifacs,
430 inbound: &mut inbound,
431 egress: &mut egress,
432 notify,
433 commands,
434 lifecycle,
435 },
436 |journaled| {
437 if let Journaled::CommandSettled { id, settlement } = &journaled {
438 if handle.settle(*id, settlement.clone()) {
439 return;
440 }
441 }
442 if let Some(request) = RunnerRequest::copy_from(&journaled) {
443 let _ = request_sender.try_send(request);
444 }
445 on_event(PrnsEvent::from(journaled), &state);
446 },
447 crate::manifold::AppDeciders {
448 should_prove,
449 should_accept_resource: |_| false,
450 },
451 store,
452 &mut persistence,
453 );
454 let router =
455 run_router::<St, R, M, COMMANDS, COMPLETIONS, ROUTED_REQUESTS, ROUTED_REQUEST_BYTES>(
456 &state,
457 request_channel.receiver(),
458 handle,
459 );
460 join(join(manifold, router), drive).await;
461 }
462
463 pub async fn run_manifold(&mut self) {
465 self.run_manifold_with_inspection_store(&NoInterfaceInspectionStore)
466 .await;
467 }
468
469 pub async fn run_manifold_with_proof_decider<P>(&mut self, should_prove: P)
471 where
472 P: FnMut(&ProofRequest) -> bool,
473 {
474 let mut persistence = NoManifoldPersistence;
475 self.run_manifold_with_inspection_store_and_persistence_and_proof_decider(
476 &NoInterfaceInspectionStore,
477 &mut persistence,
478 should_prove,
479 )
480 .await;
481 }
482
483 pub async fn run_manifold_with_interface_store<
484 const INTERFACES: usize,
485 const PACKET_PHY_CAPACITY: usize,
486 const PACKET_PHY_INDEX_BUCKETS: usize,
487 >(
488 &mut self,
489 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
490 ) where
491 M: Sync,
492 {
493 const {
494 assert!(
495 INTERFACES >= INTERFACE_CAPACITY,
496 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
497 );
498 }
499 self.run_manifold_with_inspection_store(store).await;
500 }
501
502 pub async fn run_manifold_with_interface_store_and_proof_decider<
503 P,
504 const INTERFACES: usize,
505 const PACKET_PHY_CAPACITY: usize,
506 const PACKET_PHY_INDEX_BUCKETS: usize,
507 >(
508 &mut self,
509 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
510 should_prove: P,
511 ) where
512 M: Sync,
513 P: FnMut(&ProofRequest) -> bool,
514 {
515 const {
516 assert!(
517 INTERFACES >= INTERFACE_CAPACITY,
518 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
519 );
520 }
521 let mut persistence = NoManifoldPersistence;
522 self.run_manifold_with_inspection_store_and_persistence_and_proof_decider(
523 store,
524 &mut persistence,
525 should_prove,
526 )
527 .await;
528 }
529
530 async fn run_manifold_with_inspection_store<Store>(&mut self, store: &Store)
531 where
532 Store: InterfaceInspectionStore,
533 {
534 let mut persistence = NoManifoldPersistence;
535 self.run_manifold_with_inspection_store_and_persistence(store, &mut persistence)
536 .await;
537 }
538
539 pub async fn run_manifold_with_persistence_and_interface_store<
540 Fl,
541 Keys,
542 Observe,
543 const PENDING: usize,
544 const INTERFACES: usize,
545 const PACKET_PHY_CAPACITY: usize,
546 const PACKET_PHY_INDEX_BUCKETS: usize,
547 >(
548 &mut self,
549 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
550 persistence: &mut EmbeddedFlashPersistence<Fl, Keys, Observe, PENDING>,
551 ) where
552 M: Sync,
553 Fl: NorFlash,
554 Keys: RouteSnapshotKeys,
555 Observe: FnMut(EmbeddedPersistenceDiagnostic),
556 {
557 const {
558 assert!(
559 INTERFACES >= INTERFACE_CAPACITY,
560 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
561 );
562 }
563 self.run_manifold_with_inspection_store_and_persistence(store, persistence)
564 .await;
565 }
566
567 pub async fn run_manifold_with_persistence_and_interface_store_and_proof_decider<
568 Fl,
569 Keys,
570 Observe,
571 Decide,
572 const PENDING: usize,
573 const INTERFACES: usize,
574 const PACKET_PHY_CAPACITY: usize,
575 const PACKET_PHY_INDEX_BUCKETS: usize,
576 >(
577 &mut self,
578 store: &EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>,
579 persistence: &mut EmbeddedFlashPersistence<Fl, Keys, Observe, PENDING>,
580 should_prove: Decide,
581 ) where
582 M: Sync,
583 Fl: NorFlash,
584 Keys: RouteSnapshotKeys,
585 Observe: FnMut(EmbeddedPersistenceDiagnostic),
586 Decide: FnMut(&ProofRequest) -> bool,
587 {
588 const {
589 assert!(
590 INTERFACES >= INTERFACE_CAPACITY,
591 "EmbassyInterfaceStore INTERFACES must cover PrnsNode INTERFACE_CAPACITY"
592 );
593 }
594 self.run_manifold_with_inspection_store_and_persistence_and_proof_decider(
595 store,
596 persistence,
597 should_prove,
598 )
599 .await;
600 }
601
602 pub async fn restore_embedded_persistence<Fl, Keys, Observe, const PENDING: usize>(
603 &mut self,
604 persistence: &mut EmbeddedFlashPersistence<Fl, Keys, Observe, PENDING>,
605 ) -> EmbeddedPersistenceRestoreReport
606 where
607 Fl: NorFlash,
608 Keys: RouteSnapshotKeys,
609 Observe: FnMut(EmbeddedPersistenceDiagnostic),
610 H: ResumableHost,
611 {
612 let report = persistence
613 .restore(&mut self.node.engine, self.host.now())
614 .await;
615 self.host.resume_at(report.logical_start);
616 report
617 }
618
619 async fn run_manifold_with_inspection_store_and_persistence<Store, P>(
620 &mut self,
621 store: &Store,
622 persistence: &mut P,
623 ) where
624 Store: InterfaceInspectionStore,
625 P: ManifoldPersistence<S>,
626 {
627 self.run_manifold_with_inspection_store_and_persistence_and_proof_decider(
628 store,
629 persistence,
630 |_| false,
631 )
632 .await;
633 }
634
635 async fn run_manifold_with_inspection_store_and_persistence_and_proof_decider<
636 Store,
637 P,
638 Decide,
639 >(
640 &mut self,
641 store: &Store,
642 persistence: &mut P,
643 should_prove: Decide,
644 ) where
645 Store: InterfaceInspectionStore,
646 P: ManifoldPersistence<S>,
647 Decide: FnMut(&ProofRequest) -> bool,
648 {
649 let PrnsNode {
650 node,
651 inbound,
652 egress,
653 notify,
654 commands,
655 lifecycle,
656 handle,
657 host,
658 descriptors,
659 ifacs,
660 } = self;
661 let AssembledNode {
662 engine,
663 state,
664 on_event,
665 request_endpoints: _,
666 } = node;
667 let request_channel =
668 Channel::<M, RunnerRequest<ROUTED_REQUEST_BYTES>, ROUTED_REQUESTS>::new();
669 let request_sender = request_channel.sender();
670 let manifold = run_pooled(
671 engine,
672 host,
673 PooledWiring {
674 descriptors,
675 ifacs,
676 inbound,
677 egress,
678 notify: *notify,
679 commands: *commands,
680 lifecycle: *lifecycle,
681 },
682 |journaled| {
683 if let Journaled::CommandSettled { id, settlement } = &journaled {
684 if handle.settle(*id, settlement.clone()) {
685 return;
686 }
687 }
688 if let Some(request) = RunnerRequest::copy_from(&journaled) {
689 let _ = request_sender.try_send(request);
690 }
691 on_event(PrnsEvent::from(journaled), state);
692 },
693 crate::manifold::AppDeciders {
694 should_prove,
695 should_accept_resource: |_| false,
696 },
697 store,
698 persistence,
699 );
700 let router =
701 run_router::<St, R, M, COMMANDS, COMPLETIONS, ROUTED_REQUESTS, ROUTED_REQUEST_BYTES>(
702 state,
703 request_channel.receiver(),
704 *handle,
705 );
706 join(manifold, router).await;
707 }
708}
709
710#[cfg(test)]
711mod tests;