1use crate::engine::origin::Origin;
7use crate::engine::permissions::Permissions;
8use crate::engine::port::{InProcess, Mailbox, Outbox, Transport};
9use crate::platform::network::{
10 NetworkConfig, NetworkCore, NetworkError, NetworkRequest, StatusCode,
11};
12use anyhow::{Context, Result, anyhow};
13use base64::Engine;
14use std::fmt;
15use url::Url;
16
17fn scheme_allowed(permissions: Permissions, url: &Url) -> bool {
30 permissions.allows(url)
31}
32
33pub struct BrowserResourceLoader {
77 network: Option<NetworkCore>,
83
84 immediate_outbox: Outbox<BrowserNetworkMessage>,
88
89 immediate_inbox: Mailbox<BrowserNetworkMessage>,
91}
92
93impl BrowserResourceLoader {
94 pub fn new(network: Option<NetworkCore>) -> Self {
100 Self::with_transport(network, &InProcess)
101 }
102
103 pub fn with_transport<T: Transport>(network: Option<NetworkCore>, transport: &T) -> Self {
110 let (immediate_outbox, immediate_inbox) = transport.channel();
111 Self {
112 network,
113 immediate_outbox,
114 immediate_inbox,
115 }
116 }
117
118 pub fn with_network(network: NetworkCore) -> Self {
120 Self::new(Some(network))
121 }
122
123 pub fn offline() -> Self {
128 Self::new(None)
129 }
130
131 pub fn set_network_config(&self, config: NetworkConfig) {
133 if let Some(net) = &self.network {
134 net.set_network_config(config);
135 }
136 }
137
138 pub fn clear_cache(&self) {
140 if let Some(net) = &self.network {
141 net.clear_cache();
142 }
143 }
144
145 pub fn fetch_async(
152 &mut self,
153 url: Url,
154 id: usize,
155 initiator: &Origin,
156 permissions: Permissions,
157 ) {
158 self.fetch_request_async(
159 NetworkRequest::get(url.to_string()),
160 id,
161 initiator,
162 permissions,
163 );
164 }
165
166 pub fn fetch_request_async(
168 &mut self,
169 request: NetworkRequest,
170 id: usize,
171 initiator: &Origin,
172 permissions: Permissions,
173 ) {
174 let Ok(url) = Url::parse(&request.url) else {
175 self.post_immediate(BrowserNetworkMessage {
176 id,
177 response: Err(BrowserNetworkError::AnyhowError(anyhow!(
178 "Invalid request URL: {}",
179 request.url
180 ))),
181 });
182 return;
183 };
184 if !scheme_allowed(permissions, &url) {
185 log::warn!(
186 "Blocked {} from {} (scheme access denied)",
187 url,
188 initiator.ascii_serialization()
189 );
190 self.post_immediate(BrowserNetworkMessage {
191 id,
192 response: Err(BrowserNetworkError::AnyhowError(anyhow!(
193 "Blocked request for {url}: the requesting page is not allowed to access this scheme"
194 ))),
195 });
196 return;
197 }
198 let Some(body) = load_immediate(&url) else {
199 if let Some(net) = &self.network {
200 net.fetch_request_async(request, id);
201 }
202 return;
203 };
204 let msg = BrowserNetworkMessage {
205 id,
206 response: if request.method == "GET" && request.body.is_empty() {
207 body.map(|body| make_response(&url, body))
208 .map_err(BrowserNetworkError::AnyhowError)
209 } else {
210 Err(BrowserNetworkError::AnyhowError(anyhow!(
211 "{} is not supported for {} URLs",
212 request.method,
213 url.scheme()
214 )))
215 },
216 };
217 self.post_immediate(msg);
218 }
219
220 fn post_immediate(&self, message: BrowserNetworkMessage) {
225 self.immediate_outbox
226 .send(message)
227 .expect("the loader owns the reading end of its own channel");
228 }
229
230 pub fn fetch_blocking(&self, url: Url) -> Result<BrowserResponse> {
231 if let Some(body) = load_immediate(&url) {
232 return body.map(|body| make_response(&url, body));
233 }
234 let Some(net) = &self.network else {
235 return Err(anyhow!("NetworkCore not available"));
236 };
237 net.fetch_blocking(url.as_str())
238 .map(|resp| BrowserResponse {
239 url: resp.url,
240 status: resp.status,
241 status_text: resp.reason_phrase,
242 body: resp.body,
243 headers: resp.headers,
244 })
245 .map_err(|e| anyhow!("NetworkError: {}", e))
246 }
247
248 pub fn try_receive(&mut self) -> Vec<BrowserNetworkMessage> {
250 let mut msgs: Vec<BrowserNetworkMessage> = self
251 .network
252 .as_ref()
253 .map(|net| {
254 net.try_receive()
255 .into_iter()
256 .map(|msg| BrowserNetworkMessage {
257 id: msg.msg_id,
258 response: msg
259 .response
260 .map(|resp| BrowserResponse {
261 url: resp.url,
262 status: resp.status,
263 status_text: resp.reason_phrase,
264 body: resp.body,
265 headers: resp.headers,
266 })
267 .map_err(BrowserNetworkError::NetworkError),
268 })
269 .collect()
270 })
271 .unwrap_or_default();
272 msgs.extend(std::iter::from_fn(|| self.immediate_inbox.try_recv()));
273
274 msgs
275 }
276}
277
278fn load_immediate(url: &Url) -> Option<Result<Vec<u8>>> {
282 match url.scheme() {
283 "resource" => Some(ResourceURI::load(url.as_str())),
284 "data" => Some(DataURI::decode(url.as_str())),
285 "file" => Some(FileURI::load(url)),
286 _ => None,
287 }
288}
289
290fn make_response(url: &Url, body: Vec<u8>) -> BrowserResponse {
292 BrowserResponse {
293 url: url.to_string(),
294 status: hyper::StatusCode::OK.into(),
295 status_text: "OK".to_string(),
296 body,
297 headers: vec![],
298 }
299}
300
301pub struct BrowserResponse {
303 pub url: String,
304 pub status: StatusCode,
305 pub status_text: String,
306 pub body: Vec<u8>,
307 pub headers: Vec<(String, String)>,
308}
309
310pub struct BrowserNetworkMessage {
312 pub id: usize,
313 pub response: Result<BrowserResponse, BrowserNetworkError>,
314}
315
316#[derive(Debug)]
317pub enum BrowserNetworkError {
318 NetworkError(NetworkError),
319 AnyhowError(anyhow::Error),
320}
321
322impl fmt::Display for BrowserNetworkError {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 match self {
325 Self::NetworkError(ne) => write!(f, "{ne}"),
326 Self::AnyhowError(ae) => write!(f, "{ae}"),
327 }
328 }
329}
330
331pub struct ResourceURI;
333
334impl ResourceURI {
335 pub fn load(url: &str) -> Result<Vec<u8>, anyhow::Error> {
336 use crate::platform::io;
337 if let Some(path) = url.strip_prefix("resource:///") {
338 io::load_resource(path)
339 } else {
340 Err(anyhow!("Unsupported scheme: {}", url))
341 }
342 }
343}
344
345pub struct DataURI;
351
352impl DataURI {
353 pub fn decode(url: &str) -> Result<Vec<u8>> {
354 let rest = url
355 .strip_prefix("data:")
356 .with_context(|| format!("Not a data: URL: {url}"))?;
357 let (metadata, payload) = rest
358 .split_once(',')
359 .context("data: URL is missing the ',' delimiter")?;
360
361 if metadata.to_ascii_lowercase().contains(";base64") {
362 let decoded = percent_decode(payload);
363 let cleaned: Vec<u8> = decoded
364 .into_iter()
365 .filter(|b| !b.is_ascii_whitespace())
366 .collect();
367 base64::engine::general_purpose::STANDARD
368 .decode(cleaned)
369 .context("failed to decode base64 data: URL")
370 } else {
371 Ok(percent_decode(payload))
372 }
373 }
374}
375
376pub struct FileURI;
382
383impl FileURI {
384 pub fn load(url: &Url) -> Result<Vec<u8>> {
385 let path = url
386 .to_file_path()
387 .map_err(|()| anyhow!("Unsupported file URL (non-local host): {url}"))?;
388 crate::platform::io::load_local_file(&path.to_string_lossy())
389 }
390}
391
392fn percent_decode(input: &str) -> Vec<u8> {
394 let bytes = input.as_bytes();
395 let mut out = Vec::with_capacity(bytes.len());
396 let mut i = 0;
397 while i < bytes.len() {
398 if bytes[i] == b'%'
399 && i + 2 < bytes.len()
400 && let (Some(hi), Some(lo)) = (hex_value(bytes[i + 1]), hex_value(bytes[i + 2]))
401 {
402 out.push(hi * 16 + lo);
403 i += 3;
404 } else {
405 out.push(bytes[i]);
406 i += 1;
407 }
408 }
409 out
410}
411
412fn hex_value(b: u8) -> Option<u8> {
413 match b {
414 b'0'..=b'9' => Some(b - b'0'),
415 b'a'..=b'f' => Some(b - b'a' + 10),
416 b'A'..=b'F' => Some(b - b'A' + 10),
417 _ => None,
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use crate::engine::port::{Consume, Disconnected, Produce, Undeliverable};
425 use crate::test_support::assert_send;
426 use std::sync::atomic::{AtomicUsize, Ordering};
427
428 #[test]
432 fn loader_is_send() {
433 assert_send::<BrowserResourceLoader>();
434 assert_send::<BrowserNetworkMessage>();
435 assert_send::<BrowserResponse>();
436 assert_send::<BrowserNetworkError>();
437 }
438
439 #[test]
442 fn offline_loader_needs_no_network_stack() {
443 let loader = BrowserResourceLoader::offline();
444 assert!(loader.network.is_none());
445 }
446
447 fn temp_file(contents: &[u8]) -> std::path::PathBuf {
450 static COUNTER: AtomicUsize = AtomicUsize::new(0);
451 let name = format!(
452 "orinium-file-uri-{}-{}",
453 std::process::id(),
454 COUNTER.fetch_add(1, Ordering::Relaxed)
455 );
456 let path = std::env::temp_dir().join(name);
457 std::fs::write(&path, contents).unwrap();
458 path
459 }
460
461 #[test]
462 fn data_uri_decodes_base64_payload() {
463 let encoded = base64::engine::general_purpose::STANDARD.encode(b"hello");
464 let url = format!("data:image/png;base64,{encoded}");
465 assert_eq!(DataURI::decode(&url).unwrap(), b"hello");
466 }
467
468 #[test]
469 fn data_uri_decodes_plain_payload() {
470 let url = "data:text/plain,hello%20world";
471 assert_eq!(DataURI::decode(url).unwrap(), b"hello world");
472 }
473
474 #[test]
475 fn data_uri_ignores_whitespace_in_base64_payload() {
476 let encoded = base64::engine::general_purpose::STANDARD.encode(b"line1line2");
477 let url = format!("data:text/plain;base64,{encoded}\n\r\t ");
478 assert_eq!(DataURI::decode(&url).unwrap(), b"line1line2");
479 }
480
481 #[test]
482 fn data_uri_rejects_missing_delimiter() {
483 assert!(DataURI::decode("data:text/plain").is_err());
484 }
485
486 #[test]
487 fn data_uri_rejects_invalid_base64() {
488 assert!(DataURI::decode("data:text/plain;base64,%%%").is_err());
489 }
490
491 #[test]
492 fn data_uri_flag_is_case_insensitive() {
493 let encoded = base64::engine::general_purpose::STANDARD.encode(b"ok");
494 let url = format!("data:text/plain;BASE64,{encoded}");
495 assert_eq!(DataURI::decode(&url).unwrap(), b"ok");
496 }
497
498 #[test]
499 fn url_parse_preserves_data_url() {
500 let url = Url::parse("data:image/png;base64,AAAA").unwrap();
501 assert_eq!(url.scheme(), "data");
502 assert_eq!(url.as_str(), "data:image/png;base64,AAAA");
503 }
504
505 #[test]
506 fn fetch_blocking_decodes_data_url_without_network() {
507 let loader = BrowserResourceLoader::offline();
508 let encoded = base64::engine::general_purpose::STANDARD.encode(b"png-bytes");
509 let url = Url::parse(&format!("data:image/png;base64,{encoded}")).unwrap();
510
511 let resp = loader.fetch_blocking(url).unwrap();
512 assert!(resp.status.is_success());
513 assert_eq!(resp.body, b"png-bytes");
514 }
515
516 #[test]
517 fn fetch_async_pushes_data_url_into_immediate_pool() {
518 let mut loader = BrowserResourceLoader::offline();
519 let url = Url::parse("data:text/plain,hi").unwrap();
520 let permissions = Permissions::content_for(&url);
521
522 loader.fetch_async(url, 7, &Origin::opaque(), permissions);
523 let msgs = loader.try_receive();
524 assert_eq!(msgs.len(), 1);
525 assert_eq!(msgs[0].id, 7);
526 let resp = msgs[0].response.as_ref().unwrap();
527 assert_eq!(resp.body, b"hi");
528 }
529
530 #[test]
531 fn immediate_urls_reject_non_get_requests() {
532 let mut loader = BrowserResourceLoader::offline();
533 loader.fetch_request_async(
534 NetworkRequest {
535 url: "data:text/plain,hi".to_string(),
536 method: "POST".to_string(),
537 headers: Vec::new(),
538 body: b"request body".to_vec(),
539 },
540 8,
541 &Origin::opaque(),
542 Permissions::chrome(),
543 );
544
545 let messages = loader.try_receive();
546 assert_eq!(messages.len(), 1);
547 assert_eq!(messages[0].id, 8);
548 assert!(messages[0].response.is_err());
549 }
550
551 #[test]
552 fn network_origin_cannot_reach_resource_scheme() {
553 let mut loader = BrowserResourceLoader::offline();
554 let web = Origin::from_url(&Url::parse("https://example.test/").unwrap());
555 let permissions = Permissions::content_for(&Url::parse("https://example.test/").unwrap());
556
557 loader.fetch_async(
558 Url::parse("resource:///devtools/index.html").unwrap(),
559 9,
560 &web,
561 permissions,
562 );
563
564 let messages = loader.try_receive();
565 assert_eq!(messages.len(), 1);
566 assert_eq!(messages[0].id, 9);
567 assert!(messages[0].response.is_err());
568 }
569
570 #[test]
571 fn internal_origin_can_reach_resource_scheme() {
572 let mut loader = BrowserResourceLoader::offline();
573 let internal = Origin::internal();
574
575 loader.fetch_async(
576 Url::parse("resource:///devtools/index.html").unwrap(),
577 10,
578 &internal,
579 Permissions::chrome(),
580 );
581
582 let messages = loader.try_receive();
583 assert_eq!(messages.len(), 1);
584 assert_eq!(messages[0].id, 10);
585 assert!(messages[0].response.as_ref().is_ok());
586 }
587
588 #[test]
589 fn any_origin_can_reach_data_scheme() {
590 let mut loader = BrowserResourceLoader::offline();
591 let web = Origin::from_url(&Url::parse("https://example.test/").unwrap());
592 let permissions = Permissions::content_for(&Url::parse("https://example.test/").unwrap());
593
594 loader.fetch_async(
595 Url::parse("data:text/plain,hi").unwrap(),
596 11,
597 &web,
598 permissions,
599 );
600
601 let messages = loader.try_receive();
602 assert_eq!(messages.len(), 1);
603 assert_eq!(messages[0].id, 11);
604 let resp = messages[0].response.as_ref().unwrap();
605 assert_eq!(resp.body, b"hi");
606 }
607
608 #[test]
609 fn data_scheme_requires_network_permission() {
610 let mut loader = BrowserResourceLoader::offline();
613
614 loader.fetch_async(
615 Url::parse("data:text/plain,hi").unwrap(),
616 25,
617 &Origin::opaque(),
618 Permissions::none(),
619 );
620
621 let messages = loader.try_receive();
622 assert_eq!(messages.len(), 1);
623 assert_eq!(messages[0].id, 25);
624 assert!(messages[0].response.is_err());
625 }
626
627 #[test]
628 fn fetch_blocking_rejects_data_url_without_network() {
629 let loader = BrowserResourceLoader::offline();
630 let url = Url::parse("data:image/png;base64,@@@not-base64@@@").unwrap();
631 assert!(loader.fetch_blocking(url).is_err());
632 }
633
634 #[test]
635 fn file_uri_loads_local_file_bytes() {
636 let path = temp_file(b"file content");
637 let url = Url::from_file_path(&path).unwrap();
638
639 assert_eq!(FileURI::load(&url).unwrap(), b"file content");
640 let _ = std::fs::remove_file(&path);
641 }
642
643 #[test]
644 fn file_url_with_nonlocal_host_is_rejected() {
645 let url = Url::parse("file://evil.example/etc/passwd").unwrap();
646 assert!(FileURI::load(&url).is_err());
647 }
648
649 #[test]
650 fn fetch_async_resolves_file_url_into_immediate_pool() {
651 let path = temp_file(b"file bytes");
652 let url = Url::from_file_path(&path).unwrap();
653 let mut loader = BrowserResourceLoader::offline();
654
655 loader.fetch_async(url, 12, &Origin::internal(), Permissions::chrome());
656 let msgs = loader.try_receive();
657 assert_eq!(msgs.len(), 1);
658 assert_eq!(msgs[0].id, 12);
659 let resp = msgs[0].response.as_ref().unwrap();
660 assert_eq!(resp.body, b"file bytes");
661 let _ = std::fs::remove_file(&path);
662 }
663
664 #[test]
665 fn internal_origin_can_read_local_file() {
666 let path = temp_file(b"local data");
667 let url = Url::from_file_path(&path).unwrap();
668 let mut loader = BrowserResourceLoader::offline();
669
670 loader.fetch_async(url, 13, &Origin::internal(), Permissions::chrome());
671 let msgs = loader.try_receive();
672 assert_eq!(msgs.len(), 1);
673 let resp = msgs[0].response.as_ref().unwrap();
674 assert_eq!(resp.body, b"local data");
675 let _ = std::fs::remove_file(&path);
676 }
677
678 #[test]
679 fn network_origin_cannot_read_local_file() {
680 let path = temp_file(b"secret");
681 let url = Url::from_file_path(&path).unwrap();
682 let web = Origin::from_url(&Url::parse("https://example.test/").unwrap());
683 let permissions = Permissions::content_for(&Url::parse("https://example.test/").unwrap());
684 let mut loader = BrowserResourceLoader::offline();
685
686 loader.fetch_async(url, 14, &web, permissions);
687 let msgs = loader.try_receive();
688 assert_eq!(msgs.len(), 1);
689 assert!(msgs[0].response.is_err());
690 let _ = std::fs::remove_file(&path);
691 }
692
693 #[test]
697 fn resource_page_cannot_read_local_file() {
698 let path = temp_file(b"secret");
699 let url = Url::from_file_path(&path).unwrap();
700 let resource_url = Url::parse("resource:///devtools/index.html").unwrap();
701 let origin = Origin::from_url(&resource_url);
702 let permissions = Permissions::content_for(&resource_url);
703 let mut loader = BrowserResourceLoader::offline();
704
705 assert!(permissions.resource);
706 assert!(!permissions.is_chrome());
707 loader.fetch_async(url, 24, &origin, permissions);
708 let msgs = loader.try_receive();
709 assert_eq!(msgs.len(), 1);
710 assert!(msgs[0].response.is_err());
711 let _ = std::fs::remove_file(&path);
712 }
713
714 #[test]
715 fn data_origin_cannot_reach_resource_scheme() {
716 let mut loader = BrowserResourceLoader::offline();
717 let data_origin = Origin::from_url(&Url::parse("data:text/html,evil").unwrap());
718 let permissions = Permissions::content_for(&Url::parse("data:text/html,evil").unwrap());
719
720 loader.fetch_async(
721 Url::parse("resource:///devtools/index.html").unwrap(),
722 20,
723 &data_origin,
724 permissions,
725 );
726
727 let messages = loader.try_receive();
728 assert_eq!(messages.len(), 1);
729 assert_eq!(messages[0].id, 20);
730 assert!(messages[0].response.is_err());
731 }
732
733 #[test]
734 fn data_origin_cannot_reach_file_scheme() {
735 let path = temp_file(b"secret");
736 let url = Url::from_file_path(&path).unwrap();
737 let data_origin = Origin::from_url(&Url::parse("data:text/html,evil").unwrap());
738 let permissions = Permissions::content_for(&Url::parse("data:text/html,evil").unwrap());
739 let mut loader = BrowserResourceLoader::offline();
740
741 loader.fetch_async(url, 21, &data_origin, permissions);
742 let msgs = loader.try_receive();
743 assert_eq!(msgs.len(), 1);
744 assert!(msgs[0].response.is_err());
745 let _ = std::fs::remove_file(&path);
746 }
747
748 #[test]
749 fn opaque_origin_cannot_reach_resource_or_file() {
750 let opaque = Origin::opaque();
751 let mut loader = BrowserResourceLoader::offline();
752
753 loader.fetch_async(
754 Url::parse("resource:///devtools/index.html").unwrap(),
755 22,
756 &opaque,
757 Permissions::none(),
758 );
759 let messages = loader.try_receive();
760 assert_eq!(messages.len(), 1);
761 assert!(messages[0].response.is_err());
762
763 let path = temp_file(b"secret");
764 let file_url = Url::from_file_path(&path).unwrap();
765 loader.fetch_async(file_url, 23, &opaque, Permissions::none());
766 let messages = loader.try_receive();
767 assert_eq!(messages.len(), 1);
768 assert!(messages[0].response.is_err());
769 let _ = std::fs::remove_file(&path);
770 }
771
772 #[test]
778 fn results_arrive_over_a_transport_other_than_the_default() {
779 let mut loader = BrowserResourceLoader::with_transport(None, &Buffered);
780 let url = Url::parse("data:text/plain,relayed").unwrap();
781
782 let permissions = Permissions::content_for(&url);
783 loader.fetch_async(url, 15, &Origin::opaque(), permissions);
784 let msgs = loader.try_receive();
785
786 assert_eq!(msgs.len(), 1);
787 assert_eq!(msgs[0].id, 15);
788 assert_eq!(msgs[0].response.as_ref().unwrap().body, b"relayed");
789 }
790
791 #[test]
792 fn draining_an_idle_loader_reports_nothing_without_blocking() {
793 let mut loader = BrowserResourceLoader::with_transport(None, &Buffered);
794 assert!(loader.try_receive().is_empty());
795 }
796
797 #[test]
801 fn loader_can_move_between_threads() {
802 let loader = BrowserResourceLoader::offline();
803 let url = Url::parse("data:text/plain,moved").unwrap();
804 let (sender, receiver) = std::sync::mpsc::channel();
805
806 std::thread::spawn(move || {
807 let mut loader = loader;
808 let permissions = Permissions::content_for(&url);
809 loader.fetch_async(url, 16, &Origin::opaque(), permissions);
810 for message in loader.try_receive() {
811 sender.send(message.id).expect("receiver is alive");
812 }
813 })
814 .join()
815 .expect("the loader thread finished");
816
817 assert_eq!(receiver.recv().expect("a message was posted"), 16);
818 }
819
820 #[derive(Debug, Default, Clone, Copy)]
823 struct Buffered;
824
825 impl Transport for Buffered {
826 fn channel<T: Send + 'static>(&self) -> (Outbox<T>, Mailbox<T>) {
827 struct Queue<T> {
828 messages: std::sync::Mutex<std::collections::VecDeque<T>>,
829 arrived: std::sync::Condvar,
830 }
831
832 impl<T> Queue<T> {
833 fn push(&self, message: T) {
834 self.messages
835 .lock()
836 .expect("lock poisoned")
837 .push_back(message);
838 self.arrived.notify_all();
839 }
840
841 fn pop(&self) -> Option<T> {
842 self.messages.lock().expect("lock poisoned").pop_front()
843 }
844
845 fn pop_waiting(&self) -> Result<T, Disconnected> {
846 let mut messages = self.messages.lock().expect("lock poisoned");
847 loop {
848 if let Some(message) = messages.pop_front() {
849 return Ok(message);
850 }
851 messages = self.arrived.wait(messages).expect("lock poisoned");
852 }
853 }
854 }
855
856 struct Enqueuer<T>(std::sync::Arc<Queue<T>>);
857 struct Dequeuer<T>(std::sync::Arc<Queue<T>>);
858
859 impl<T: Send + 'static> Produce<T> for Enqueuer<T> {
860 fn send(&self, message: T) -> Result<(), Undeliverable<T>> {
861 self.0.push(message);
862 Ok(())
863 }
864 }
865
866 impl<T: Send + 'static> Consume<T> for Dequeuer<T> {
867 fn recv(&self) -> Result<T, Disconnected> {
868 self.0.pop_waiting()
869 }
870
871 fn try_recv(&self) -> Option<T> {
872 self.0.pop()
873 }
874 }
875
876 let queue = std::sync::Arc::new(Queue {
877 messages: std::sync::Mutex::new(std::collections::VecDeque::new()),
878 arrived: std::sync::Condvar::new(),
879 });
880 (
881 Outbox::of(Enqueuer(std::sync::Arc::clone(&queue))),
882 Mailbox::of(Dequeuer(queue)),
883 )
884 }
885 }
886}