1use serde::{Deserialize, Serialize};
4use std::net::{IpAddr, SocketAddr};
5
6pub type SessionId = [u8; 16];
7pub fn random_session_id() -> SessionId {
8 rand::random()
9}
10
11pub const DIRECT_VERSION: u16 = 1;
12pub const MAX_CANDIDATES: usize = 8;
13pub const MAX_CERT_LEN: usize = 2048;
14pub const SETUP_TTL_MS: u64 = 8_000;
15
16#[derive(Clone, Copy, Serialize, Deserialize)]
17pub struct SessionSecret(pub [u8; 32]);
18
19impl PartialEq for SessionSecret {
20 fn eq(&self, other: &Self) -> bool {
21 use subtle::ConstantTimeEq;
22 bool::from(self.0.ct_eq(&other.0))
23 }
24}
25impl Eq for SessionSecret {}
26impl SessionSecret {
27 pub fn random() -> Self {
28 Self(rand::random())
29 }
30}
31
32impl std::fmt::Debug for SessionSecret {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.write_str("[redacted]")
35 }
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
39pub struct DirectCapability {
40 pub version: u16,
41 pub stun_addr: SocketAddr,
44}
45
46pub fn stun_target(advertised: SocketAddr, relay: IpAddr) -> SocketAddr {
53 if advertised.ip().is_unspecified() {
54 SocketAddr::new(relay.to_canonical(), advertised.port())
55 } else {
56 advertised
57 }
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
61pub struct PeerInfo {
62 pub certificate: Vec<u8>,
63 pub candidates: Vec<SocketAddr>,
64}
65
66impl PeerInfo {
67 pub fn valid(&self) -> bool {
68 !self.certificate.is_empty()
69 && self.certificate.len() <= MAX_CERT_LEN
70 && self.candidates.len() <= MAX_CANDIDATES
71 && self.candidates.iter().all(valid_candidate)
72 }
73}
74
75pub fn valid_candidate(addr: &SocketAddr) -> bool {
76 addr.port() != 0
77 && !addr.ip().is_unspecified()
78 && !addr.ip().is_multicast()
79 && match addr {
80 SocketAddr::V4(addr) => !addr.ip().is_broadcast() && !addr.ip().is_link_local(),
81 SocketAddr::V6(addr) => !addr.ip().is_unicast_link_local(),
82 }
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize)]
86pub struct SessionGrant {
87 pub id: SessionId,
88 pub server_instance: SessionId,
89 pub generation: u64,
90 pub namespace: u64,
91 pub service: String,
92 pub is_datagram: bool,
93 pub consumer_key: u64,
94 pub provider_key: u64,
95 pub consumer: PeerInfo,
96 pub provider: PeerInfo,
97 pub secret: SessionSecret,
98 pub ttl_ms: u64,
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize)]
102pub enum DirectClientMessage {
103 CancelOpen {
104 request: u64,
105 },
106 Open {
107 request: u64,
108 key: String,
109 is_datagram: bool,
110 peer: PeerInfo,
111 },
112 RelaySelect {
113 id: SessionId,
114 },
115 Cancel {
116 id: SessionId,
117 },
118 Report(SessionUpdate),
119 Ping,
120}
121
122#[derive(Clone, Debug, Serialize, Deserialize)]
123pub enum DirectProviderMessage {
124 Accept { id: SessionId, peer: PeerInfo },
125 Reject { id: SessionId, reason: String },
126 RelaySelected { id: SessionId },
127 Report(SessionUpdate),
128}
129
130#[derive(Clone, Debug, Serialize, Deserialize)]
131pub enum DirectEvent {
132 Ready(DirectCapability),
133 Offer {
134 grant: SessionGrant,
135 stun_addr: SocketAddr,
136 },
137 Granted {
138 request: u64,
139 grant: SessionGrant,
140 },
141 LegacyRelay {
142 request: u64,
143 },
144 Rejected {
145 request: u64,
146 reason: String,
147 },
148 RelaySelect {
149 id: SessionId,
150 },
151 RelaySelected {
152 id: SessionId,
153 },
154 Cancel {
155 id: SessionId,
156 },
157 Pong,
158}
159
160#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
161#[serde(rename_all = "snake_case")]
162pub enum SessionPath {
163 Direct,
164 Relay,
165}
166
167#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "snake_case")]
169pub enum SessionState {
170 Active,
171 Closed,
172}
173
174#[derive(Clone, Debug, Serialize, Deserialize)]
175pub struct SessionReport {
176 pub id: SessionId,
177 pub seq: u64,
178 pub path: SessionPath,
179 pub state: SessionState,
180 pub reason: Option<String>,
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
185pub struct SessionSummary {
186 pub server_instance: SessionId,
187 pub id: SessionId,
188 pub namespace: u64,
189 pub service: String,
190 pub is_datagram: bool,
191 pub consumer_key: u64,
192 pub provider_key: u64,
193}
194impl From<&SessionGrant> for SessionSummary {
195 fn from(grant: &SessionGrant) -> Self {
196 Self {
197 server_instance: grant.server_instance,
198 id: grant.id,
199 namespace: grant.namespace,
200 service: grant.service.clone(),
201 is_datagram: grant.is_datagram,
202 consumer_key: grant.consumer_key,
203 provider_key: grant.provider_key,
204 }
205 }
206}
207#[derive(Clone, Debug, Serialize, Deserialize)]
208pub struct SessionUpdate {
209 pub session: SessionSummary,
210 pub secret: SessionSecret,
211 pub report: SessionReport,
212}
213
214#[derive(Clone, Debug, Serialize, Deserialize)]
215pub struct ObservedReport {
216 pub report: SessionReport,
217 pub age_ms: u64,
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
221pub struct SessionObservation {
222 pub server_instance: SessionId,
223 pub restored: bool,
225 pub id: SessionId,
226 pub service: String,
227 pub is_datagram: bool,
228 pub consumer: Option<ObservedReport>,
229 pub provider: Option<ObservedReport>,
230 pub stale: bool,
231 pub conflicting: bool,
232}
233
234macro_rules! wire_message {
235 ($($name:ty),+ $(,)?) => {$ (
236 impl crate::command::MessageSerializer for $name {
237 fn encode(&self) -> pb_mapper_core::error::Result<Vec<u8>> {
238 serde_json::to_vec(self).map_err(|e| crate::secure::protocol_error(e.to_string()))
239 }
240 fn decode(bytes: &[u8]) -> pb_mapper_core::error::Result<Self> {
241 serde_json::from_slice(bytes).map_err(|e| crate::secure::protocol_error(e.to_string()))
242 }
243 }
244 )+};
245}
246wire_message!(
247 DirectClientMessage,
248 DirectProviderMessage,
249 DirectEvent,
250 SessionGrant
251);
252
253#[cfg(test)]
254mod tests {
255 use crate::command::{
256 MessageSerializer, PbConnRequest, PbConnResponse, PbConnStatusReq, PbConnStatusResp,
257 };
258 #[test]
259 fn disabled_direct_fields_are_absent_from_existing_wire_messages() {
260 let request = PbConnRequest::Register {
261 direct: None,
262 key: "legacy".into(),
263 need_codec: false,
264 is_datagram: false,
265 protocol_version: None,
266 client_instance_id: None,
267 heartbeat_interval_ms: None,
268 heartbeat_tolerance_ms: None,
269 };
270 let expected = br#"{"Register":{"need_codec":false,"is_datagram":false,"key":"legacy"}}"#;
271 assert_eq!(request.encode().unwrap(), expected);
272 assert_eq!(
273 serde_json::to_vec(&PbConnStatusReq::Service {
274 key: "legacy".into(),
275 direct_query: None
276 })
277 .unwrap(),
278 br#"{"Service":{"key":"legacy"}}"#
279 );
280 let response = PbConnResponse::RegisterV2 {
281 conn_id: 1,
282 generation: 2,
283 lease_ttl_ms: 3000,
284 direct: None,
285 };
286 assert_eq!(
287 response.encode().unwrap(),
288 br#"{"RegisterV2":{"conn_id":1,"generation":2,"lease_ttl_ms":3000}}"#
289 );
290 let response = PbConnStatusResp::Service {
291 key: "legacy".into(),
292 connections: vec![],
293 direct: None,
294 };
295 assert_eq!(
296 serde_json::to_vec(&response).unwrap(),
297 br#"{"Service":{"key":"legacy","connections":[]}}"#
298 );
299 }
300
301 #[test]
302 fn unspecified_stun_address_takes_the_dialed_relay_ip() {
303 use super::stun_target;
304 let relay: std::net::IpAddr = "203.0.113.7".parse().unwrap();
305 assert_eq!(
306 stun_target("0.0.0.0:7666".parse().unwrap(), relay),
307 "203.0.113.7:7666".parse().unwrap()
308 );
309 assert_eq!(
310 stun_target("[::]:7666".parse().unwrap(), relay),
311 "203.0.113.7:7666".parse().unwrap()
312 );
313 assert_eq!(
315 stun_target(
316 "0.0.0.0:7666".parse().unwrap(),
317 "::ffff:203.0.113.7".parse().unwrap()
318 ),
319 "203.0.113.7:7666".parse().unwrap()
320 );
321 assert_eq!(
323 stun_target("198.51.100.1:10811".parse().unwrap(), relay),
324 "198.51.100.1:10811".parse().unwrap()
325 );
326 }
327}