1use std::time::Duration;
2
3use iroh::PublicKey;
4use serde::{Deserialize, Serialize};
5use tokio::{
6 io::{AsyncReadExt, AsyncWriteExt},
7 net::TcpStream,
8 sync::Mutex,
9 time::timeout,
10};
11
12use crate::error::{Result, SyncwebError};
13use crate::node::identity::DeviceId;
14
15const MAX_FRAME_SIZE: usize = 1024 * 1024;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18#[non_exhaustive]
19pub struct JoinRelayRequest {
20 pub device_id: DeviceId,
21}
22
23impl JoinRelayRequest {
24 #[must_use]
25 pub const fn new(device_id: DeviceId) -> Self {
26 Self { device_id }
27 }
28}
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31#[non_exhaustive]
32pub struct SessionInvitation {
33 pub session_key: [u8; 32],
34 pub server_socket: bool,
35}
36
37impl SessionInvitation {
38 #[must_use]
39 pub const fn new(session_key: [u8; 32], server_socket: bool) -> Self {
40 Self {
41 session_key,
42 server_socket,
43 }
44 }
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48#[non_exhaustive]
49pub struct JoinSessionRequest {
50 pub session_key: [u8; 32],
51 pub device_id: DeviceId,
52}
53
54impl JoinSessionRequest {
55 #[must_use]
56 pub const fn new(session_key: [u8; 32], device_id: DeviceId) -> Self {
57 Self { session_key, device_id }
58 }
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62#[non_exhaustive]
63pub enum RelayMessage {
64 JoinRelayRequest(JoinRelayRequest),
65 SessionInvitation(SessionInvitation),
66 JoinSessionRequest(JoinSessionRequest),
67 ResponseSuccess,
68 ResponseNotFound,
69 RelayFull,
70}
71
72impl RelayMessage {
73 #[must_use]
74 pub fn encode(&self) -> Vec<u8> {
75 let mut bytes = Vec::with_capacity(66);
76 match self {
77 Self::JoinRelayRequest(request) => {
78 bytes.push(1);
79 bytes.extend_from_slice(request.device_id.node_id().as_bytes());
80 }
81 Self::SessionInvitation(invitation) => {
82 bytes.push(2);
83 bytes.extend_from_slice(&invitation.session_key);
84 bytes.push(u8::from(invitation.server_socket));
85 }
86 Self::JoinSessionRequest(request) => {
87 bytes.push(3);
88 bytes.extend_from_slice(&request.session_key);
89 bytes.extend_from_slice(request.device_id.node_id().as_bytes());
90 }
91 Self::ResponseSuccess => bytes.push(4),
92 Self::ResponseNotFound => bytes.push(5),
93 Self::RelayFull => bytes.push(6),
94 }
95 bytes
96 }
97
98 pub fn decode(bytes: &[u8]) -> Result<Self> {
102 let (tag, body) = bytes
103 .split_first()
104 .ok_or_else(|| SyncwebError::RelayDecode("message is missing a type tag".to_owned()))?;
105 match (*tag, body) {
106 (1, device_id) if device_id.len() == 32 => Ok(Self::JoinRelayRequest(JoinRelayRequest {
107 device_id: device_id_from_bytes(device_id)?,
108 })),
109 (2, [session_key_bytes @ .., server_socket]) if session_key_bytes.len() == 32 => {
110 if *server_socket > 1 {
111 return Err(SyncwebError::RelayDecode(
112 "invalid session invitation socket flag".to_owned(),
113 ));
114 }
115 let session_key = session_key_bytes
116 .try_into()
117 .map_err(|error| SyncwebError::RelayDecode(format!("invalid session key length: {error}")))?;
118 Ok(Self::SessionInvitation(SessionInvitation {
119 session_key,
120 server_socket: *server_socket == 1,
121 }))
122 }
123 (3, body_bytes) if body_bytes.len() == 64 => {
124 let session_key_bytes = body_bytes
125 .get(..32)
126 .ok_or_else(|| SyncwebError::RelayDecode("invalid body length".to_owned()))?;
127 let device_id = body_bytes
128 .get(32..)
129 .ok_or_else(|| SyncwebError::RelayDecode("invalid body length".to_owned()))?;
130 let session_key = session_key_bytes
131 .try_into()
132 .map_err(|error| SyncwebError::RelayDecode(format!("invalid session key length: {error}")))?;
133 Ok(Self::JoinSessionRequest(JoinSessionRequest {
134 session_key,
135 device_id: device_id_from_bytes(device_id)?,
136 }))
137 }
138 (4, []) => Ok(Self::ResponseSuccess),
139 (5, []) => Ok(Self::ResponseNotFound),
140 (6, []) => Ok(Self::RelayFull),
141 _ => Err(SyncwebError::RelayDecode("invalid message encoding".to_owned())),
142 }
143 }
144}
145
146fn device_id_from_bytes(bytes: &[u8]) -> Result<DeviceId> {
147 let arr: [u8; 32] = bytes
148 .try_into()
149 .map_err(|error| SyncwebError::RelayDecode(format!("device ID must be 32 bytes: {error}")))?;
150 let public_key = PublicKey::from_bytes(&arr).map_err(|error| SyncwebError::RelayDecode(error.to_string()))?;
151 Ok(DeviceId::from_node_id(public_key))
152}
153
154#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
155#[non_exhaustive]
156pub struct RelayConfig {
157 #[serde(default)]
158 pub relay_urls: Vec<String>,
159 #[serde(with = "duration_seconds")]
160 pub timeout: Duration,
161 #[serde(default = "default_auto_fallback")]
162 pub auto_fallback: bool,
163}
164
165impl Default for RelayConfig {
166 fn default() -> Self {
167 Self {
168 relay_urls: Vec::new(),
169 timeout: Duration::from_secs(10),
170 auto_fallback: true,
171 }
172 }
173}
174
175const fn default_auto_fallback() -> bool {
176 true
177}
178
179mod duration_seconds {
180 use std::time::Duration;
181
182 use serde::{Deserialize, Deserializer, Serializer};
183
184 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
185 where
186 S: Serializer,
187 {
188 serializer.serialize_u64(duration.as_secs())
189 }
190
191 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
192 where
193 D: Deserializer<'de>,
194 {
195 let seconds = u64::deserialize(deserializer)?;
196 Ok(Duration::from_secs(seconds))
197 }
198}
199
200pub struct SyncthingRelayTransport {
206 stream: Mutex<TcpStream>,
207 pub relay_url: String,
208 pub session_key: [u8; 32],
209}
210
211impl SyncthingRelayTransport {
212 pub async fn connect(
216 relay_url_in: impl Into<String>,
217 device_id: DeviceId,
218 timeout_duration: Duration,
219 ) -> Result<Self> {
220 let relay_url = relay_url_in.into();
221 let address = relay_address(&relay_url)?;
222 let stream = timeout(timeout_duration, TcpStream::connect(&address))
223 .await
224 .map_err(|error| SyncwebError::operation("relay connection timed out", error))?
225 .map_err(|error| SyncwebError::operation("failed to connect to relay", error))?;
226 let transport = Self {
227 stream: Mutex::new(stream),
228 relay_url,
229 session_key: [0; 32],
230 };
231 transport
232 .send_message(&RelayMessage::JoinRelayRequest(JoinRelayRequest { device_id }))
233 .await?;
234 Ok(transport)
235 }
236
237 pub async fn send_message(&self, message: &RelayMessage) -> Result<()> {
241 self.write_frame(&message.encode()).await
242 }
243
244 pub async fn recv_message(&self) -> Result<RelayMessage> {
248 RelayMessage::decode(&self.read_frame().await?)
249 }
250
251 pub async fn send_packet(&self, packet: &[u8]) -> Result<()> {
255 self.write_frame(packet).await
256 }
257
258 pub async fn recv_packet(&self) -> Result<Vec<u8>> {
262 self.read_frame().await
263 }
264
265 async fn write_frame(&self, payload: &[u8]) -> Result<()> {
266 if payload.len() > MAX_FRAME_SIZE {
267 return Err(SyncwebError::RelayFrameTooLarge { max: MAX_FRAME_SIZE });
268 }
269 let len = u32::try_from(payload.len())
270 .map_err(|error| SyncwebError::operation("payload length exceeds u32::MAX", error))?;
271 let mut stream = self.stream.lock().await;
272 stream.write_u32(len).await?;
273 stream.write_all(payload).await?;
274 stream.flush().await?;
275 drop(stream);
276 Ok(())
277 }
278
279 async fn read_frame(&self) -> Result<Vec<u8>> {
280 let mut stream = self.stream.lock().await;
281 let length = usize::try_from(stream.read_u32().await?)
282 .map_err(|error| SyncwebError::operation("frame length exceeds usize::MAX", error))?;
283 if length > MAX_FRAME_SIZE {
284 return Err(SyncwebError::RelayFrameTooLarge { max: MAX_FRAME_SIZE });
285 }
286 let mut payload = vec![0; length];
287 stream.read_exact(&mut payload).await?;
288 drop(stream);
289 Ok(payload)
290 }
291}
292
293#[derive(Clone, Debug, Default)]
294pub struct TransportFallback {
295 config: RelayConfig,
296}
297
298impl TransportFallback {
299 #[must_use]
300 pub const fn new(config: RelayConfig) -> Self {
301 Self { config }
302 }
303
304 #[must_use]
305 pub const fn config(&self) -> &RelayConfig {
306 &self.config
307 }
308
309 pub async fn connect_relay(&self, device_id: DeviceId) -> Result<SyncthingRelayTransport> {
313 if !self.config.auto_fallback {
314 return Err(SyncwebError::RelayDisabled);
315 }
316 let mut failures = Vec::new();
317 for relay_url in &self.config.relay_urls {
318 match SyncthingRelayTransport::connect(relay_url, device_id, self.config.timeout).await {
319 Ok(transport) => return Ok(transport),
320 Err(error) => failures.push(format!("{relay_url}: {error}")),
321 }
322 }
323 Err(SyncwebError::RelayUnreachable {
324 reasons: if failures.is_empty() {
325 "no relay URLs configured".to_owned()
326 } else {
327 failures.join("; ")
328 },
329 })
330 }
331}
332
333fn relay_address(relay_url: &str) -> Result<String> {
334 let address = relay_url.strip_prefix("tcp://").ok_or(SyncwebError::RelayBadScheme)?;
335 if address.is_empty() || address.contains('/') {
336 return Err(SyncwebError::RelayBadAddress(relay_url.to_owned()));
337 }
338 if address.parse::<std::net::SocketAddr>().is_err() && !address.contains(':') {
339 return Err(SyncwebError::RelayBadAddress(relay_url.to_owned()));
340 }
341 Ok(address.to_owned())
342}