1use std::collections::VecDeque;
8use std::io;
9use std::sync::{
10 atomic::{AtomicBool, Ordering},
11 Arc,
12};
13
14use iroh::endpoint::{ClosedStream, ReadError, RecvStream, SendStream, WriteError};
15use tokio::io::AsyncWriteExt;
16
17use crate::application_crypto::{
18 protect_frame, CryptoFrameDecoder, CryptoStreamError, APPLICATION_KEY_BYTES,
19};
20
21#[derive(Debug)]
22struct ApplicationCryptoStreamIoError(CryptoStreamError);
23
24impl std::fmt::Display for ApplicationCryptoStreamIoError {
25 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(formatter, "{:?}", self.0)
27 }
28}
29
30impl std::error::Error for ApplicationCryptoStreamIoError {}
31
32#[derive(Clone)]
35pub(crate) enum ApplicationStreamAdmission {
36 Latest,
37 Scope(crate::session_token::GrantScope),
38 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
39 Envelope,
40}
41
42#[derive(Clone)]
45pub(crate) struct ApplicationStreamAccess {
46 current: Arc<dyn Fn() -> bool + Send + Sync>,
47 denied: Arc<AtomicBool>,
48}
49
50impl std::fmt::Debug for ApplicationStreamAccess {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.write_str("ApplicationStreamAccess")
53 }
54}
55
56impl ApplicationStreamAccess {
57 pub(crate) fn new(current: impl Fn() -> bool + Send + Sync + 'static) -> Self {
58 Self {
59 current: Arc::new(current),
60 denied: Arc::new(AtomicBool::new(false)),
61 }
62 }
63
64 #[cfg(not(target_arch = "wasm32"))]
65 pub(crate) fn same_fence(&self, other: &Self) -> bool {
66 Arc::ptr_eq(&self.denied, &other.denied)
67 }
68
69 pub(crate) fn check(&self) -> io::Result<()> {
70 if !self.denied.load(Ordering::Acquire) && (self.current)() {
71 return Ok(());
72 }
73 self.denied.store(true, Ordering::Release);
75 Err(io::Error::new(
76 io::ErrorKind::PermissionDenied,
77 "application stream authorization expired or changed",
78 ))
79 }
80}
81
82fn check_access(access: &Option<ApplicationStreamAccess>) -> io::Result<()> {
83 access
84 .as_ref()
85 .map_or(Ok(()), ApplicationStreamAccess::check)
86}
87
88async fn with_access<T>(
91 access: &Option<ApplicationStreamAccess>,
92 operation: impl std::future::Future<Output = io::Result<T>>,
93) -> io::Result<T> {
94 let mut operation = std::pin::pin!(operation);
95 std::future::poll_fn(|cx| {
96 check_access(access)?;
97 let result = operation.as_mut().poll(cx);
98 check_access(access)?;
99 result
100 })
101 .await
102}
103
104pub fn is_crypto_auth_failure(error: &io::Error) -> bool {
109 error
110 .get_ref()
111 .and_then(|source| source.downcast_ref::<ApplicationCryptoStreamIoError>())
112 .is_some_and(|source| matches!(&source.0, CryptoStreamError::AuthenticationFailed(_)))
113}
114
115pub fn peer_stop_code(error: &io::Error) -> Option<u64> {
118 error
119 .get_ref()
120 .and_then(|source| source.downcast_ref::<WriteError>())
121 .and_then(|source| match source {
122 WriteError::Stopped(code) => Some(code.into_inner()),
123 _ => None,
124 })
125}
126
127#[derive(Debug)]
128pub struct CryptoSendStream {
129 inner: SendStream,
130 key: [u8; APPLICATION_KEY_BYTES],
131 access: Option<ApplicationStreamAccess>,
132}
133
134impl CryptoSendStream {
135 pub fn new(inner: SendStream, key: [u8; APPLICATION_KEY_BYTES]) -> Self {
136 Self {
137 inner,
138 key,
139 access: None,
140 }
141 }
142
143 pub async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
144 check_access(&self.access)?;
145 let frame = protect_frame(&self.key, buf).map_err(crypto_io_error)?;
146 for chunk in frame.chunks(16 * 1024) {
148 with_access(&self.access, async {
149 self.inner.write_all(chunk).await.map_err(map_write_error)
150 })
151 .await?;
152 }
153 Ok(())
154 }
155
156 pub async fn flush(&mut self) -> io::Result<()> {
157 with_access(&self.access, self.inner.flush()).await
158 }
159
160 pub fn finish(mut self) -> io::Result<()> {
161 self.inner.finish().map_err(map_finish_error)
162 }
163
164 pub fn into_inner(self) -> SendStream {
165 self.inner
166 }
167}
168
169#[derive(Debug)]
170pub struct CryptoRecvStream {
171 inner: RecvStream,
172 decoder: CryptoFrameDecoder,
173 read_buf: Vec<u8>,
174 finished: bool,
175 access: Option<ApplicationStreamAccess>,
176}
177
178impl CryptoRecvStream {
179 pub fn new(inner: RecvStream, key: [u8; APPLICATION_KEY_BYTES]) -> io::Result<Self> {
180 Ok(Self {
181 inner,
182 decoder: CryptoFrameDecoder::new(&key).map_err(crypto_io_error)?,
183 read_buf: Vec::new(),
184 finished: false,
185 access: None,
186 })
187 }
188
189 pub fn new_with_prefix(
190 inner: RecvStream,
191 key: [u8; APPLICATION_KEY_BYTES],
192 prefix: &[u8],
193 ) -> io::Result<Self> {
194 let mut decoder = CryptoFrameDecoder::new(&key).map_err(crypto_io_error)?;
195 let mut read_buf = Vec::new();
196 if !prefix.is_empty() {
197 for frame in decoder.push(prefix).map_err(crypto_stream_io_error)? {
198 read_buf.extend_from_slice(&frame);
199 }
200 }
201 Ok(Self {
202 inner,
203 decoder,
204 read_buf,
205 finished: false,
206 access: None,
207 })
208 }
209
210 pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
211 check_access(&self.access)?;
212 if !self.read_buf.is_empty() {
213 let copied = self.read_buf.len().min(buf.len());
214 buf[..copied].copy_from_slice(&self.read_buf[..copied]);
215 self.read_buf.drain(..copied);
216 return Ok(copied);
217 }
218
219 if self.finished {
220 return Ok(0);
221 }
222
223 loop {
224 let mut chunk = vec![0u8; 16 * 1024];
225 let read_bytes = match with_access(&self.access, async {
226 self.inner.read(&mut chunk).await.map_err(map_read_error)
227 })
228 .await
229 {
230 Ok(Some(0)) => 0,
231 Ok(Some(read_bytes)) => read_bytes,
232 Ok(None) => 0,
233 Err(error) => return Err(error),
234 };
235 if read_bytes == 0 {
236 self.finished = true;
237 self.decoder.finish().map_err(crypto_stream_io_error)?;
238 if self.read_buf.is_empty() {
239 return Ok(0);
240 }
241 check_access(&self.access)?;
242 let copied = self.read_buf.len().min(buf.len());
243 buf[..copied].copy_from_slice(&self.read_buf[..copied]);
244 self.read_buf.drain(..copied);
245 return Ok(copied);
246 }
247
248 chunk.truncate(read_bytes);
249 let opened = self.decoder.push(&chunk).map_err(crypto_stream_io_error)?;
250 for frame in opened {
251 self.read_buf.extend_from_slice(&frame);
252 }
253
254 if !self.read_buf.is_empty() {
255 check_access(&self.access)?;
256 let copied = self.read_buf.len().min(buf.len());
257 buf[..copied].copy_from_slice(&self.read_buf[..copied]);
258 self.read_buf.drain(..copied);
259 return Ok(copied);
260 }
261 }
262 }
263
264 pub fn into_inner(self) -> RecvStream {
265 self.inner
266 }
267}
268
269#[derive(Debug)]
270pub enum PeerSendStream {
271 Plain(SendStream),
272 Encrypted(CryptoSendStream),
273}
274
275impl PeerSendStream {
276 #[cfg(not(target_arch = "wasm32"))]
277 pub(crate) fn binding(&self) -> Option<(&ApplicationStreamAccess, [u8; APPLICATION_KEY_BYTES])> {
278 match self {
279 Self::Encrypted(stream) => stream.access.as_ref().map(|access| (access, stream.key)),
280 _ => None,
281 }
282 }
283 pub(crate) fn with_access(mut self, access: ApplicationStreamAccess) -> Self {
284 if let Self::Encrypted(stream) = &mut self {
285 stream.access = Some(access);
286 }
287 self
288 }
289 pub fn plain(inner: SendStream) -> Self {
290 Self::Plain(inner)
291 }
292
293 pub fn encrypted(inner: SendStream, key: [u8; APPLICATION_KEY_BYTES]) -> Self {
294 Self::Encrypted(CryptoSendStream::new(inner, key))
295 }
296
297 pub fn is_encrypted(&self) -> bool {
298 matches!(self, Self::Encrypted(_))
299 }
300
301 pub async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
302 match self {
303 Self::Plain(stream) => stream.write_all(buf).await.map_err(map_write_error),
304 Self::Encrypted(stream) => stream.write_all(buf).await,
305 }
306 }
307
308 pub async fn flush(&mut self) -> io::Result<()> {
309 match self {
310 Self::Plain(stream) => stream.flush().await,
311 Self::Encrypted(stream) => stream.flush().await,
312 }
313 }
314
315 pub fn finish(self) -> io::Result<()> {
316 match self {
317 Self::Plain(mut stream) => stream.finish().map_err(map_finish_error),
318 Self::Encrypted(stream) => stream.finish(),
319 }
320 }
321
322 pub async fn finish_and_wait_for_peer(self, timeout: std::time::Duration) -> io::Result<()> {
328 async fn finish_and_wait(
329 mut stream: SendStream,
330 timeout: std::time::Duration,
331 ) -> io::Result<()> {
332 stream.finish().map_err(map_finish_error)?;
333 #[cfg(not(target_arch = "wasm32"))]
334 match tokio::time::timeout(timeout, stream.stopped()).await {
335 Ok(Ok(_)) => Ok(()),
336 Ok(Err(error)) => Err(io::Error::other(format!(
337 "wait for peer acknowledgement: {error}"
338 ))),
339 Err(_) => Err(io::Error::new(
340 io::ErrorKind::TimedOut,
341 "timed out waiting for peer acknowledgement",
342 )),
343 }
344 #[cfg(target_arch = "wasm32")]
345 {
346 use futures::FutureExt;
347
348 let stopped = stream.stopped().fuse();
349 let timeout = gloo_timers::future::sleep(timeout).fuse();
350 futures::pin_mut!(stopped, timeout);
351 futures::select! {
352 result = stopped => result.map(|_| ()).map_err(|error| {
353 io::Error::other(format!("wait for peer acknowledgement: {error}"))
354 }),
355 _ = timeout => Err(io::Error::new(
356 io::ErrorKind::TimedOut,
357 "timed out waiting for peer acknowledgement",
358 )),
359 }
360 }
361 }
362
363 match self {
364 Self::Plain(stream) => finish_and_wait(stream, timeout).await,
365 Self::Encrypted(stream) => finish_and_wait(stream.into_inner(), timeout).await,
366 }
367 }
368
369 pub fn into_plain(self) -> SendStream {
370 match self {
371 Self::Plain(stream) => stream,
372 Self::Encrypted(stream) => stream.into_inner(),
373 }
374 }
375}
376
377#[derive(Debug)]
378pub enum PeerRecvStream {
379 Plain(RecvStream),
380 Encrypted(CryptoRecvStream),
381 Prefixed {
386 prefix: VecDeque<u8>,
387 inner: Box<PeerRecvStream>,
388 },
389}
390
391impl PeerRecvStream {
392 #[cfg(not(target_arch = "wasm32"))]
393 pub(crate) fn binding(&self) -> Option<&ApplicationStreamAccess> {
394 match self {
395 Self::Encrypted(stream) => stream.access.as_ref(),
396 Self::Prefixed { inner, .. } => inner.binding(),
397 Self::Plain(_) => None,
398 }
399 }
400 pub(crate) fn with_access(mut self, access: ApplicationStreamAccess) -> Self {
401 let mut current = &mut self;
402 loop {
403 match current {
404 Self::Encrypted(stream) => {
405 stream.access = Some(access);
406 break;
407 }
408 Self::Prefixed { inner, .. } => current = inner.as_mut(),
409 Self::Plain(_) => break,
410 }
411 }
412 self
413 }
414
415 fn check_access(&self) -> io::Result<()> {
416 let mut current = self;
417 loop {
418 match current {
419 Self::Encrypted(stream) => return check_access(&stream.access),
420 Self::Prefixed { inner, .. } => current = inner.as_ref(),
421 Self::Plain(_) => return Ok(()),
422 }
423 }
424 }
425 pub fn plain(inner: RecvStream) -> Self {
426 Self::Plain(inner)
427 }
428
429 pub fn encrypted(inner: RecvStream, key: [u8; APPLICATION_KEY_BYTES]) -> io::Result<Self> {
430 Ok(Self::Encrypted(CryptoRecvStream::new(inner, key)?))
431 }
432
433 pub fn encrypted_with_prefix(
434 inner: RecvStream,
435 key: [u8; APPLICATION_KEY_BYTES],
436 prefix: &[u8],
437 ) -> io::Result<Self> {
438 Ok(Self::Encrypted(CryptoRecvStream::new_with_prefix(
439 inner, key, prefix,
440 )?))
441 }
442
443 pub fn is_encrypted(&self) -> bool {
444 match self {
445 Self::Encrypted(_) => true,
446 Self::Prefixed { inner, .. } => inner.is_encrypted(),
447 Self::Plain(_) => false,
448 }
449 }
450
451 pub fn with_plaintext_prefix(self, prefix: impl IntoIterator<Item = u8>) -> Self {
453 let prefix = prefix.into_iter().collect::<VecDeque<_>>();
454 if prefix.is_empty() {
455 self
456 } else {
457 Self::Prefixed {
458 prefix,
459 inner: Box::new(self),
460 }
461 }
462 }
463
464 pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
465 self.check_access()?;
467 let mut current = self;
468 loop {
469 match current {
470 Self::Plain(stream) => return read_recv_stream(stream, buf).await,
471 Self::Encrypted(stream) => return stream.read(buf).await,
472 Self::Prefixed { prefix, inner } => {
473 if !prefix.is_empty() {
474 let copied = prefix.len().min(buf.len());
475 for slot in &mut buf[..copied] {
476 *slot = prefix.pop_front().expect("prefix length was checked");
477 }
478 return Ok(copied);
479 } else {
480 current = inner.as_mut();
481 }
482 }
483 }
484 }
485 }
486
487 pub fn into_plain(self) -> RecvStream {
488 match self {
489 Self::Plain(stream) => stream,
490 Self::Encrypted(stream) => stream.into_inner(),
491 Self::Prefixed { inner, .. } => inner.into_plain(),
492 }
493 }
494}
495
496pub fn wrap_peer_streams(
497 key: Option<[u8; APPLICATION_KEY_BYTES]>,
498 send: SendStream,
499 recv: RecvStream,
500) -> io::Result<(PeerSendStream, PeerRecvStream)> {
501 match key {
502 Some(key) => Ok((
503 PeerSendStream::encrypted(send, key),
504 PeerRecvStream::encrypted(recv, key)?,
505 )),
506 None => Ok((PeerSendStream::plain(send), PeerRecvStream::plain(recv))),
507 }
508}
509
510pub fn wrap_send_stream(
511 key: Option<[u8; APPLICATION_KEY_BYTES]>,
512 send: SendStream,
513) -> PeerSendStream {
514 match key {
515 Some(key) => PeerSendStream::encrypted(send, key),
516 None => PeerSendStream::plain(send),
517 }
518}
519
520async fn read_recv_stream(stream: &mut RecvStream, buf: &mut [u8]) -> io::Result<usize> {
521 match stream.read(buf).await {
522 Ok(Some(0)) => Ok(0),
523 Ok(Some(read_bytes)) => Ok(read_bytes),
524 Ok(None) => Ok(0),
525 Err(error) => Err(map_read_error(error)),
526 }
527}
528
529fn map_write_error(error: WriteError) -> io::Error {
530 io::Error::other(error)
531}
532
533fn map_read_error(error: ReadError) -> io::Error {
534 io::Error::new(io::ErrorKind::Other, error.to_string())
535}
536
537fn map_finish_error(error: ClosedStream) -> io::Error {
538 io::Error::new(io::ErrorKind::NotConnected, error.to_string())
539}
540
541fn crypto_io_error(error: crate::application_crypto::CryptoError) -> io::Error {
542 io::Error::new(io::ErrorKind::InvalidData, format!("{error:?}"))
543}
544
545fn crypto_stream_io_error(error: CryptoStreamError) -> io::Error {
546 io::Error::new(
547 io::ErrorKind::InvalidData,
548 ApplicationCryptoStreamIoError(error),
549 )
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn resumed_backpressure_cannot_poll_io_after_access_is_lost() {
558 use std::{
559 cell::Cell,
560 future::Future,
561 task::{Context, Poll, Waker},
562 };
563 let allowed = Arc::new(AtomicBool::new(true));
564 let observed = allowed.clone();
565 let access = Some(ApplicationStreamAccess::new(move || {
566 observed.load(Ordering::Acquire)
567 }));
568 let polls = Cell::new(0);
569 let pending = std::future::poll_fn(|_| {
570 polls.set(polls.get() + 1);
571 Poll::<io::Result<()>>::Pending
572 });
573 let mut guarded = std::pin::pin!(with_access(&access, pending));
574 let mut cx = Context::from_waker(Waker::noop());
575 assert!(guarded.as_mut().poll(&mut cx).is_pending());
576 allowed.store(false, Ordering::Release);
577 assert!(
578 matches!(guarded.as_mut().poll(&mut cx), Poll::Ready(Err(error)) if error.kind() == io::ErrorKind::PermissionDenied)
579 );
580 assert_eq!(
581 polls.get(),
582 1,
583 "a resumed write must not enqueue unauthorized bytes"
584 );
585 allowed.store(true, Ordering::Release);
586 assert!(
587 access.as_ref().unwrap().check().is_err(),
588 "denied streams stay closed after reauthorization"
589 );
590 }
591
592 #[test]
593 fn access_lost_during_io_does_not_return_its_result() {
594 use std::{
595 future::Future,
596 task::{Context, Poll, Waker},
597 };
598 let allowed = Arc::new(AtomicBool::new(true));
599 let observed = allowed.clone();
600 let access = Some(ApplicationStreamAccess::new(move || {
601 observed.load(Ordering::Acquire)
602 }));
603 let operation = async {
604 allowed.store(false, Ordering::Release);
605 Ok(b"private bytes")
606 };
607 let mut guarded = std::pin::pin!(with_access(&access, operation));
608 assert!(
609 matches!(guarded.as_mut().poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(Err(error)) if error.kind() == io::ErrorKind::PermissionDenied)
610 );
611 }
612
613 #[test]
614 fn distinguishes_authentication_failure_from_incomplete_shutdown_frame() {
615 let authentication = crypto_stream_io_error(CryptoStreamError::AuthenticationFailed(
616 crate::application_crypto::CryptoError::DecryptFailed,
617 ));
618 let incomplete = crypto_stream_io_error(CryptoStreamError::IncompleteFrame);
619
620 assert!(is_crypto_auth_failure(&authentication));
621 assert!(!is_crypto_auth_failure(&incomplete));
622 }
623
624 #[test]
625 fn transport_errors_are_not_crypto_authentication_failures() {
626 let transport = io::Error::new(io::ErrorKind::ConnectionReset, "connection lost");
627 assert!(!is_crypto_auth_failure(&transport));
628 }
629
630 #[test]
631 fn preserves_peer_stop_code_without_string_parsing() {
632 let stopped = map_write_error(WriteError::Stopped(iroh::endpoint::VarInt::from_u32(0)));
633 assert_eq!(peer_stop_code(&stopped), Some(0));
634
635 let lost = io::Error::other("connection lost");
636 assert_eq!(peer_stop_code(&lost), None);
637 }
638}