openrtc/
explicit_transfer_crypto.rs1use iroh::endpoint::{RecvStream, SendStream};
13
14use crate::application_crypto::{
15 is_encrypted_payload, open_payload as open_crypto_payload,
16 protect_payload as protect_crypto_payload, CryptoError, APPLICATION_KEY_BYTES,
17 RAW_STREAM_TYPE_ID,
18};
19use crate::application_crypto_streams::{CryptoRecvStream, CryptoSendStream};
20
21pub const EXPLICIT_FILE_PROTOCOL_BYTE: u8 = 0x02;
22
23pub fn protect_payload(
24 key: &[u8; APPLICATION_KEY_BYTES],
25 payload: &[u8],
26) -> Result<Vec<u8>, CryptoError> {
27 protect_crypto_payload(key, RAW_STREAM_TYPE_ID, payload)
28}
29
30pub fn open_payload(
31 key: &[u8; APPLICATION_KEY_BYTES],
32 payload: &[u8],
33) -> Result<Vec<u8>, CryptoError> {
34 open_crypto_payload(key, RAW_STREAM_TYPE_ID, payload, true)
35}
36
37pub fn open_payload_optional(
38 key: Option<&[u8; APPLICATION_KEY_BYTES]>,
39 payload: &[u8],
40) -> Result<Vec<u8>, String> {
41 match key {
42 Some(key) => open_payload(key, payload).map_err(|error| format!("{error:?}")),
43 None if is_encrypted_payload(payload) => Err(
44 "encrypted explicit transfer payload rejected because no application crypto key is installed"
45 .to_string(),
46 ),
47 None => Ok(payload.to_vec()),
48 }
49}
50
51pub enum TransferSender {
52 Plain(SendStream),
53 Encrypted(CryptoSendStream),
54}
55
56impl TransferSender {
57 pub async fn write_protocol_byte(&mut self) -> Result<(), String> {
58 self.write_all(&[EXPLICIT_FILE_PROTOCOL_BYTE]).await
59 }
60
61 pub async fn write_all(&mut self, buf: &[u8]) -> Result<(), String> {
62 match self {
63 Self::Plain(stream) => stream
64 .write_all(buf)
65 .await
66 .map_err(|error| error.to_string()),
67 Self::Encrypted(stream) => stream
68 .write_all(buf)
69 .await
70 .map_err(|error| error.to_string()),
71 }
72 }
73
74 pub fn finish(self) -> Result<(), String> {
75 match self {
76 Self::Plain(mut stream) => stream.finish().map_err(|error| error.to_string()),
77 Self::Encrypted(stream) => stream.finish().map_err(|error| error.to_string()),
78 }
79 }
80}
81
82pub fn transfer_sender(
83 stream: SendStream,
84 key: Option<[u8; APPLICATION_KEY_BYTES]>,
85) -> TransferSender {
86 match key {
87 Some(key) => TransferSender::Encrypted(CryptoSendStream::new(stream, key)),
88 None => TransferSender::Plain(stream),
89 }
90}
91
92pub struct TransferReceiver {
93 inner: CryptoRecvStream,
94}
95
96impl TransferReceiver {
97 pub fn new(stream: RecvStream, key: [u8; APPLICATION_KEY_BYTES]) -> Result<Self, String> {
98 CryptoRecvStream::new(stream, key)
99 .map(|inner| Self { inner })
100 .map_err(|error| error.to_string())
101 }
102
103 pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, String> {
104 self.inner
105 .read(buf)
106 .await
107 .map_err(|error| error.to_string())
108 }
109
110 pub async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), String> {
111 let mut offset = 0;
112 while offset < buf.len() {
113 let read_bytes = self.read(&mut buf[offset..]).await?;
114 if read_bytes == 0 {
115 return Err("unexpected end of encrypted explicit transfer stream".to_string());
116 }
117 offset += read_bytes;
118 }
119 Ok(())
120 }
121}
122
123pub async fn read_transfer_header(receiver: &mut TransferReceiver) -> Result<Vec<u8>, String> {
124 let mut len_buf = [0u8; 4];
125 receiver.read_exact(&mut len_buf).await?;
126 let len = u32::from_be_bytes(len_buf) as usize;
127 if len == 0 {
128 return Err("invalid explicit transfer header length: 0 bytes".to_string());
129 }
130 let mut header_buf = vec![0u8; len];
131 receiver.read_exact(&mut header_buf).await?;
132 Ok(header_buf)
133}
134
135pub fn header_is_encrypted(len_buf: [u8; 4], available: &[u8]) -> bool {
136 let frame_len = u32::from_be_bytes(len_buf) as usize;
137 if frame_len < 32 || frame_len > crate::application_crypto::DEFAULT_MAX_RAW_FRAME_BYTES {
138 return false;
139 }
140 if available.len() < 4 + 6 {
141 return false;
142 }
143 is_encrypted_payload(&available[4..])
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::application_crypto::protect_frame;
150
151 #[test]
152 fn webrtc_round_trip_matches_application_payload_type() {
153 let key = [7u8; APPLICATION_KEY_BYTES];
154 let payload = br#"{"transfer_id":"tx-1","filename":"a.txt","size":3,"mime_type":"application/octet-stream"}"#;
155 let protected = protect_payload(&key, payload).expect("protect");
156 let opened = open_payload(&key, &protected).expect("open");
157 assert_eq!(opened, payload);
158 }
159
160 #[test]
161 fn iroh_header_frame_round_trip_uses_raw_stream_type() {
162 let key = [9u8; APPLICATION_KEY_BYTES];
163 let header_json = br#"{"transfer_id":"tx-2","filename":"b.bin","size":10,"mime_type":"application/octet-stream"}"#;
164 let mut header_frame = Vec::with_capacity(4 + header_json.len());
165 header_frame.extend_from_slice(&(header_json.len() as u32).to_be_bytes());
166 header_frame.extend_from_slice(header_json);
167 let wire = protect_frame(&key, &header_frame).expect("frame");
168 let opened = open_crypto_payload(&key, RAW_STREAM_TYPE_ID, &wire[4..], true)
169 .expect("open frame body");
170 assert_eq!(opened, header_frame);
171 }
172}