1use std::sync::Arc;
28
29use crate::{
30 communication::{CallOptions, PubSubError},
31 payload::loan::LoanPayload,
32 LocalUriProvider, UFrameMetadata, UMessageBuilder, UZeroCopyTransport, UZeroCopyTransportExt,
33};
34#[cfg(feature = "zero-copy-transport")]
35use crate::{
36 payload::{
37 stable::{
38 InitializedStablePayload, StableContainerPayload, StablePayloadInit,
39 StablePayloadInitContext,
40 },
41 UWireError,
42 },
43 UZeroCopyUninitTransport, UZeroCopyUninitTransportExt,
44};
45use crate::{wire::UWirePayload, wire_transport::UHasWire};
46
47#[cfg(feature = "usubscription")]
58pub struct Subscriber<T>
59where
60 T: UZeroCopyTransport + ?Sized,
61{
62 transport: Arc<T>,
63 usubscription: Arc<dyn crate::core::usubscription::USubscription>,
64}
65
66#[cfg(feature = "usubscription")]
67impl<T> core::fmt::Debug for Subscriber<T>
68where
69 T: UZeroCopyTransport + ?Sized,
70{
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 f.debug_struct("Subscriber").finish_non_exhaustive()
73 }
74}
75
76#[cfg(feature = "usubscription")]
77impl<T> Subscriber<T>
78where
79 T: UZeroCopyTransport + ?Sized,
80{
81 #[must_use]
84 pub fn new(
85 transport: Arc<T>,
86 usubscription: Arc<dyn crate::core::usubscription::USubscription>,
87 ) -> Self {
88 Self {
89 transport,
90 usubscription,
91 }
92 }
93
94 pub async fn subscribe(
103 &self,
104 topic: &crate::UUri,
105 listener: Arc<dyn crate::UZeroCopyListener<T::Rx>>,
106 ) -> Result<(), crate::communication::RegistrationError> {
107 use crate::communication::{RegistrationError, SubscriptionStatus};
108 crate::communication::validate_listener_topic(topic)?;
109 let state = self
110 .usubscription
111 .subscribe(topic, None, None)
112 .await
113 .map_err(|status| RegistrationError::Unknown(Box::new(status)))?;
114 if state != SubscriptionStatus::Subscribed && state != SubscriptionStatus::SubscribePending
115 {
116 return Err(RegistrationError::Unknown(Box::new(
117 crate::UStatus::fail_with_code(
118 crate::UCode::FailedPrecondition,
119 format!("uSubscription service returned {state:?}"),
120 ),
121 )));
122 }
123 self.transport
124 .register_zero_copy_listener(topic, None, listener)
125 .await
126 .map_err(|status| RegistrationError::Unknown(Box::new(status)))
127 }
128
129 pub async fn unsubscribe(
137 &self,
138 topic: &crate::UUri,
139 listener: Arc<dyn crate::UZeroCopyListener<T::Rx>>,
140 ) -> Result<(), crate::communication::RegistrationError> {
141 use crate::communication::RegistrationError;
142 crate::communication::validate_listener_topic(topic)?;
143 self.usubscription
144 .unsubscribe(topic)
145 .await
146 .map_err(|status| RegistrationError::Unknown(Box::new(status)))?;
147 self.transport
148 .unregister_zero_copy_listener(topic, None, listener)
149 .await
150 .map_err(|status| RegistrationError::Unknown(Box::new(status)))
151 }
152}
153
154pub struct Endpoint<T, P>
161where
162 T: UZeroCopyTransport + ?Sized,
163 P: LocalUriProvider + ?Sized,
164{
165 transport: Arc<T>,
166 uri_provider: Arc<P>,
167}
168
169impl<T, P> core::fmt::Debug for Endpoint<T, P>
170where
171 T: UZeroCopyTransport + ?Sized,
172 P: LocalUriProvider + ?Sized,
173{
174 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
175 f.debug_struct("Endpoint").finish_non_exhaustive()
176 }
177}
178
179impl<T, P> Endpoint<T, P>
180where
181 T: UZeroCopyTransport + ?Sized,
182 P: LocalUriProvider + ?Sized,
183{
184 #[must_use]
186 pub fn new(transport: Arc<T>, uri_provider: Arc<P>) -> Self {
187 Self {
188 transport,
189 uri_provider,
190 }
191 }
192
193 #[cfg(feature = "usubscription")]
196 #[must_use]
197 pub fn subscriber(
198 &self,
199 usubscription: Arc<dyn crate::core::usubscription::USubscription>,
200 ) -> Subscriber<T> {
201 Subscriber::new(self.transport.clone(), usubscription)
202 }
203
204 #[must_use]
206 pub fn publisher(&self) -> Publisher<T, P> {
207 Publisher::new(self.transport.clone(), self.uri_provider.clone())
208 }
209}
210
211pub struct Publisher<T, P>
213where
214 T: UZeroCopyTransport + ?Sized,
215 P: LocalUriProvider + ?Sized,
216{
217 transport: Arc<T>,
218 uri_provider: Arc<P>,
219}
220
221impl<T, P> core::fmt::Debug for Publisher<T, P>
222where
223 T: UZeroCopyTransport + ?Sized,
224 P: LocalUriProvider + ?Sized,
225{
226 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
227 f.debug_struct("Publisher").finish_non_exhaustive()
228 }
229}
230
231impl<T, P> Publisher<T, P>
232where
233 T: UZeroCopyTransport + ?Sized,
234 P: LocalUriProvider + ?Sized,
235{
236 #[must_use]
238 pub fn new(transport: Arc<T>, uri_provider: Arc<P>) -> Self {
239 Self {
240 transport,
241 uri_provider,
242 }
243 }
244
245 fn build_metadata(
246 &self,
247 resource_id: u16,
248 call_options: CallOptions,
249 ) -> Result<UFrameMetadata, PubSubError> {
250 let mut builder = UMessageBuilder::publish(self.uri_provider.get_resource_uri(resource_id));
251 builder.with_ttl(call_options.ttl());
252 if let Some(message_id) = call_options.message_id() {
253 builder.with_message_id(message_id.clone());
254 }
255 if let Some(priority) = call_options.priority() {
256 builder.with_priority(priority);
257 }
258 let message = builder.build().map_err(|error| {
259 PubSubError::InvalidArgument(format!(
260 "failed to create zero-copy Publish message metadata from parameters: {error}"
261 ))
262 })?;
263 crate::frame::metadata::try_project_umessage_to_frame_metadata(&message).map_err(|error| {
264 PubSubError::InvalidArgument(format!(
265 "failed to create zero-copy Publish frame metadata from parameters: {error}"
266 ))
267 })
268 }
269}
270
271impl<T, P> Publisher<T, P>
272where
273 T: UZeroCopyTransport + UHasWire + ?Sized,
274 P: LocalUriProvider + ?Sized,
275{
276 pub async fn publish_stable<Payload>(
278 &self,
279 resource_id: u16,
280 call_options: CallOptions,
281 init: impl for<'payload> FnOnce(&'payload mut Payload) + Send,
282 ) -> Result<(), PubSubError>
283 where
284 T::Wire: UWirePayload<Payload>,
285 <T::Wire as UWirePayload<Payload>>::Codec: LoanPayload<Payload> + Send + Sync,
286 {
287 let metadata = self.build_metadata(resource_id, call_options)?;
288 self.transport
289 .send_loaned_payload::<Payload>(metadata, init)
290 .await
291 .map_err(Box::from)
292 .map_err(PubSubError::PublishError)
293 }
294}
295
296#[cfg(feature = "zero-copy-transport")]
297impl<T, P> Publisher<T, P>
298where
299 T: UZeroCopyUninitTransport + UHasWire + ?Sized,
300 P: LocalUriProvider + ?Sized,
301{
302 pub async fn publish_uninit_stable<Payload>(
305 &self,
306 resource_id: u16,
307 call_options: CallOptions,
308 init: impl for<'payload> FnOnce(
309 StablePayloadInitContext<'payload, Payload>,
310 )
311 -> Result<InitializedStablePayload<'payload, Payload>, UWireError>
312 + Send,
313 ) -> Result<(), PubSubError>
314 where
315 T::Wire: UWirePayload<Payload, Codec = StableContainerPayload<Payload>>,
316 Payload: StablePayloadInit + Send,
317 {
318 let metadata = self.build_metadata(resource_id, call_options)?;
319 self.transport
320 .send_uninit_stable_payload::<Payload>(metadata, init)
321 .await
322 .map_err(Box::from)
323 .map_err(PubSubError::PublishError)
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use async_trait::async_trait;
330
331 use super::*;
332 use crate::{
333 test_support::StableTestBytes as StableBytes, InMemoryZeroCopyTransport,
334 StableContainerWireFormat, StaticUriProvider, UCode, UFrameView,
335 ULoanedContiguousZeroCopyRxFrame, UStatus, UTxLoanSpec, UVecRxLease, UVecTxBuffer,
336 UVecUninitTxBuffer, UZeroCopyTransportImpl, UZeroCopyUninitTransportImpl,
337 };
338
339 struct SelectedStableTransport {
340 inner: InMemoryZeroCopyTransport,
341 wire: StableContainerWireFormat,
342 }
343
344 impl SelectedStableTransport {
345 fn new() -> Self {
346 Self {
347 inner: InMemoryZeroCopyTransport::default(),
348 wire: StableContainerWireFormat,
349 }
350 }
351
352 fn sent_frames(&self) -> Vec<UVecRxLease> {
353 self.inner.sent_frames()
354 }
355 }
356
357 impl UHasWire for SelectedStableTransport {
358 type Wire = StableContainerWireFormat;
359
360 fn wire(&self) -> &Self::Wire {
361 &self.wire
362 }
363 }
364
365 #[async_trait]
366 impl UZeroCopyTransportImpl for SelectedStableTransport {
367 type Tx = UVecTxBuffer;
368 type Rx = UVecRxLease;
369
370 async fn loan_validated_tx(&self, spec: UTxLoanSpec) -> Result<Self::Tx, UStatus> {
371 UZeroCopyTransportImpl::loan_validated_tx(&self.inner, spec).await
372 }
373
374 async fn send_validated_zero_copy(&self, buffer: Self::Tx) -> Result<(), UStatus> {
375 UZeroCopyTransportImpl::send_validated_zero_copy(&self.inner, buffer).await
376 }
377 }
378
379 #[async_trait]
380 impl UZeroCopyUninitTransportImpl for SelectedStableTransport {
381 type UninitTx = UVecUninitTxBuffer;
382
383 async fn loan_validated_uninit_tx(
384 &self,
385 spec: UTxLoanSpec,
386 ) -> Result<Self::UninitTx, UStatus> {
387 UZeroCopyUninitTransportImpl::loan_validated_uninit_tx(&self.inner, spec).await
388 }
389 }
390
391 fn uri_provider() -> Arc<StaticUriProvider> {
392 Arc::new(StaticUriProvider::new("", 0x0005, 0x02).expect("uri provider"))
393 }
394
395 #[tokio::test]
396 async fn publish_stable_sends_initialized_selected_wire_payload() {
397 let transport = Arc::new(SelectedStableTransport::new());
398 let publisher = Publisher::new(transport.clone(), uri_provider());
399
400 publisher
401 .publish_stable::<StableBytes>(
402 0x9A00,
403 CallOptions::for_publish(None, None, None),
404 |payload| payload.bytes.copy_from_slice(b"init"),
405 )
406 .await
407 .expect("stable publish succeeds");
408
409 let frames = transport.sent_frames();
410 assert_eq!(frames.len(), 1);
411 let frame = frames.first().expect("one sent frame");
412 assert_eq!(frame.payload_len(), std::mem::size_of::<StableBytes>());
413 assert_eq!(
414 frame.borrow_stable_payload::<StableBytes>().unwrap(),
415 &StableBytes { bytes: *b"init" }
416 );
417 }
418
419 #[tokio::test]
420 async fn publish_uninit_stable_sends_no_zero_selected_wire_payload() {
421 let transport = Arc::new(SelectedStableTransport::new());
422 let publisher = Endpoint::new(transport.clone(), uri_provider()).publisher();
423
424 publisher
425 .publish_uninit_stable::<StableBytes>(
426 0x9A00,
427 CallOptions::for_publish(None, None, None),
428 |context| context.into_init().bytes_from_array(b"zero").finish(),
429 )
430 .await
431 .expect("uninit stable publish succeeds");
432
433 let frames = transport.sent_frames();
434 assert_eq!(frames.len(), 1);
435 let frame = frames.first().expect("one sent frame");
436 assert_eq!(
437 frame.borrow_stable_payload::<StableBytes>().unwrap(),
438 &StableBytes { bytes: *b"zero" }
439 );
440 }
441
442 #[tokio::test]
443 async fn publish_stable_rejects_invalid_topic() {
444 let transport = Arc::new(SelectedStableTransport::new());
445 let publisher = Publisher::new(transport.clone(), uri_provider());
446
447 let result = publisher
448 .publish_stable::<StableBytes>(
449 0x1000,
450 CallOptions::for_publish(None, None, None),
451 |payload| payload.bytes.copy_from_slice(b"drop"),
452 )
453 .await;
454
455 assert!(matches!(result, Err(PubSubError::InvalidArgument(_))));
456 assert!(transport.sent_frames().is_empty());
457 }
458
459 struct FailingStableTransport {
460 wire: StableContainerWireFormat,
461 }
462
463 impl UHasWire for FailingStableTransport {
464 type Wire = StableContainerWireFormat;
465
466 fn wire(&self) -> &Self::Wire {
467 &self.wire
468 }
469 }
470
471 #[async_trait]
472 impl UZeroCopyTransportImpl for FailingStableTransport {
473 type Tx = UVecTxBuffer;
474 type Rx = UVecRxLease;
475
476 async fn loan_validated_tx(&self, _spec: UTxLoanSpec) -> Result<Self::Tx, UStatus> {
477 Err(UStatus::fail_with_code(
478 UCode::Unavailable,
479 "transport unavailable",
480 ))
481 }
482
483 async fn send_validated_zero_copy(&self, _buffer: Self::Tx) -> Result<(), UStatus> {
484 Ok(())
485 }
486 }
487
488 #[tokio::test]
489 async fn publish_stable_maps_transport_error() {
490 let publisher = Publisher::new(
491 Arc::new(FailingStableTransport {
492 wire: StableContainerWireFormat,
493 }),
494 uri_provider(),
495 );
496
497 let result = publisher
498 .publish_stable::<StableBytes>(
499 0x9A00,
500 CallOptions::for_publish(None, None, None),
501 |payload| payload.bytes.copy_from_slice(b"fail"),
502 )
503 .await;
504
505 assert!(matches!(result, Err(PubSubError::PublishError(_))));
506 }
507}
508
509#[cfg(all(test, feature = "usubscription"))]
510mod subscriber_tests {
511 use std::sync::{Arc, Mutex};
512
513 use async_trait::async_trait;
514
515 use super::Subscriber;
516 use crate::communication::{RegistrationError, SubscriptionStatus};
517 use crate::core::usubscription::USubscription;
518 use crate::{
519 InMemoryZeroCopyTransport, UStatus, UTxLoanSpec, UUri, UVecRxLease, UVecTxBuffer,
520 UZeroCopyListener, UZeroCopyTransportImpl,
521 };
522
523 struct RecordingZeroCopyTransport {
524 inner: InMemoryZeroCopyTransport,
525 lifecycle: Arc<Mutex<Vec<&'static str>>>,
526 }
527
528 impl RecordingZeroCopyTransport {
529 fn new(lifecycle: Arc<Mutex<Vec<&'static str>>>) -> Self {
530 Self {
531 inner: InMemoryZeroCopyTransport::default(),
532 lifecycle,
533 }
534 }
535
536 fn record(&self, event: &'static str) {
537 self.lifecycle
538 .lock()
539 .expect("lifecycle lock poisoned")
540 .push(event);
541 }
542 }
543
544 #[async_trait]
545 impl UZeroCopyTransportImpl for RecordingZeroCopyTransport {
546 type Tx = UVecTxBuffer;
547 type Rx = UVecRxLease;
548
549 async fn loan_validated_tx(&self, spec: UTxLoanSpec) -> Result<Self::Tx, UStatus> {
550 UZeroCopyTransportImpl::loan_validated_tx(&self.inner, spec).await
551 }
552
553 async fn send_validated_zero_copy(&self, buffer: Self::Tx) -> Result<(), UStatus> {
554 UZeroCopyTransportImpl::send_validated_zero_copy(&self.inner, buffer).await
555 }
556
557 async fn register_validated_zero_copy_listener(
558 &self,
559 source_filter: &UUri,
560 sink_filter: Option<&UUri>,
561 listener: Arc<dyn UZeroCopyListener<Self::Rx>>,
562 ) -> Result<(), UStatus> {
563 self.record("register");
564 UZeroCopyTransportImpl::register_validated_zero_copy_listener(
565 &self.inner,
566 source_filter,
567 sink_filter,
568 listener,
569 )
570 .await
571 }
572
573 async fn unregister_validated_zero_copy_listener(
574 &self,
575 source_filter: &UUri,
576 sink_filter: Option<&UUri>,
577 listener: Arc<dyn UZeroCopyListener<Self::Rx>>,
578 ) -> Result<(), UStatus> {
579 self.record("unregister");
580 UZeroCopyTransportImpl::unregister_validated_zero_copy_listener(
581 &self.inner,
582 source_filter,
583 sink_filter,
584 listener,
585 )
586 .await
587 }
588 }
589
590 #[derive(Default)]
591 struct StubUSubscription {
592 lifecycle: Arc<Mutex<Vec<&'static str>>>,
593 decline: bool,
594 }
595
596 #[async_trait]
597 impl USubscription for StubUSubscription {
598 async fn subscribe(
599 &self,
600 _topic: &UUri,
601 _expiration: Option<u64>,
602 _min_sample_period: Option<u32>,
603 ) -> Result<SubscriptionStatus, UStatus> {
604 self.lifecycle
605 .lock()
606 .expect("lifecycle lock poisoned")
607 .push("subscribe");
608 if self.decline {
609 Ok(SubscriptionStatus::Unsubscribed)
610 } else {
611 Ok(SubscriptionStatus::Subscribed)
612 }
613 }
614 async fn unsubscribe(&self, _topic: &UUri) -> Result<(), UStatus> {
615 self.lifecycle
616 .lock()
617 .expect("lifecycle lock poisoned")
618 .push("unsubscribe");
619 Ok(())
620 }
621 async fn fetch_subscriptions_by_topic(
622 &self,
623 _topic: &UUri,
624 ) -> Result<Vec<crate::core::usubscription::SubscriptionInfo>, UStatus> {
625 Ok(Vec::new())
626 }
627 async fn fetch_subscriptions_by_subscriber(
628 &self,
629 _subscriber: &UUri,
630 ) -> Result<Vec<crate::core::usubscription::SubscriptionInfo>, UStatus> {
631 Ok(Vec::new())
632 }
633 async fn register_for_notifications(&self, _topic: &UUri) -> Result<(), UStatus> {
634 Ok(())
635 }
636 async fn unregister_for_notifications(&self, _topic: &UUri) -> Result<(), UStatus> {
637 Ok(())
638 }
639 async fn fetch_subscribers(&self, _topic: &UUri) -> Result<Vec<UUri>, UStatus> {
640 Ok(Vec::new())
641 }
642 async fn reset(
643 &self,
644 _reason: crate::core::usubscription::ResetReason,
645 _message: Option<String>,
646 ) -> Result<(), UStatus> {
647 Ok(())
648 }
649 }
650
651 struct CountingListener(Mutex<usize>);
652
653 #[async_trait]
654 impl<Rx: crate::UZeroCopyRxLease + Send + 'static> UZeroCopyListener<Rx> for CountingListener {
655 async fn on_receive_zero_copy(&self, _frame: Rx) {
656 *self.0.lock().expect("count lock poisoned") += 1;
657 }
658 }
659
660 fn topic() -> UUri {
661 UUri::try_from_parts("demo", 0x1_0001, 1, 0x8001).expect("valid topic URI")
662 }
663
664 #[tokio::test]
665 async fn subscribe_consults_the_service_before_registering() {
666 let lifecycle = Arc::new(Mutex::new(Vec::new()));
667 let subscriber = Subscriber::new(
668 Arc::new(RecordingZeroCopyTransport::new(lifecycle.clone())),
669 Arc::new(StubUSubscription {
670 lifecycle: lifecycle.clone(),
671 decline: false,
672 }),
673 );
674
675 subscriber
676 .subscribe(&topic(), Arc::new(CountingListener(Mutex::new(0))))
677 .await
678 .expect("subscribe succeeds when the service reports Subscribed");
679
680 assert_eq!(
681 *lifecycle.lock().expect("lifecycle lock poisoned"),
682 ["subscribe", "register"]
683 );
684 }
685
686 #[tokio::test]
687 async fn subscribe_fails_and_registers_nothing_when_the_service_declines() {
688 let lifecycle = Arc::new(Mutex::new(Vec::new()));
689 let subscriber = Subscriber::new(
690 Arc::new(RecordingZeroCopyTransport::new(lifecycle.clone())),
691 Arc::new(StubUSubscription {
692 lifecycle: lifecycle.clone(),
693 decline: true,
694 }),
695 );
696
697 subscriber
698 .subscribe(&topic(), Arc::new(CountingListener(Mutex::new(0))))
699 .await
700 .expect_err("a declined subscription must not register a listener");
701
702 assert_eq!(
703 *lifecycle.lock().expect("lifecycle lock poisoned"),
704 ["subscribe"]
705 );
706 }
707
708 #[tokio::test]
709 async fn unsubscribe_informs_the_service_and_unregisters() {
710 let lifecycle = Arc::new(Mutex::new(Vec::new()));
711 let listener = Arc::new(CountingListener(Mutex::new(0)));
712 let subscriber = Subscriber::new(
713 Arc::new(RecordingZeroCopyTransport::new(lifecycle.clone())),
714 Arc::new(StubUSubscription {
715 lifecycle: lifecycle.clone(),
716 decline: false,
717 }),
718 );
719
720 subscriber
721 .subscribe(&topic(), listener.clone())
722 .await
723 .expect("subscribed");
724 subscriber
725 .unsubscribe(&topic(), listener)
726 .await
727 .expect("unsubscribe succeeds after subscribe");
728
729 assert_eq!(
730 *lifecycle.lock().expect("lifecycle lock poisoned"),
731 ["subscribe", "register", "unsubscribe", "unregister"]
732 );
733 }
734
735 #[tokio::test]
736 async fn unsubscribe_rejects_wildcards_before_contacting_the_service() {
737 let lifecycle = Arc::new(Mutex::new(Vec::new()));
738 let subscriber = Subscriber::new(
739 Arc::new(RecordingZeroCopyTransport::new(lifecycle.clone())),
740 Arc::new(StubUSubscription {
741 lifecycle: lifecycle.clone(),
742 decline: false,
743 }),
744 );
745 let invalid_topic =
746 UUri::try_from("up://my-vin/A15B/1/FFFF").expect("valid wildcard subscription filter");
747
748 let result = subscriber
749 .unsubscribe(&invalid_topic, Arc::new(CountingListener(Mutex::new(0))))
750 .await;
751
752 assert!(matches!(result, Err(RegistrationError::InvalidFilter(_))));
753 assert!(lifecycle
754 .lock()
755 .expect("lifecycle lock poisoned")
756 .is_empty());
757 }
758}