@desklink/host 0.5.3 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +33 -19
  2. package/dist/bridge.js +3 -3
  3. package/dist/bridge.js.map +1 -1
  4. package/dist/engineProcess.d.ts +5 -0
  5. package/dist/engineProcess.d.ts.map +1 -1
  6. package/dist/engineProcess.js +6 -3
  7. package/dist/engineProcess.js.map +1 -1
  8. package/dist/protocol.d.ts +39 -0
  9. package/dist/protocol.d.ts.map +1 -1
  10. package/dist/protocol.js.map +1 -1
  11. package/docs/BOUNDARY.md +4 -2
  12. package/docs/PROTOCOL.md +70 -7
  13. package/engine/Cargo.lock +6 -8
  14. package/engine/Cargo.toml +13 -1
  15. package/engine/src/audio.rs +398 -0
  16. package/engine/src/clipboard.rs +0 -34
  17. package/engine/src/convert.rs +0 -170
  18. package/engine/src/cursor.rs +0 -23
  19. package/engine/src/input.rs +0 -65
  20. package/engine/src/input_mac.rs +0 -11
  21. package/engine/src/keeper.rs +0 -137
  22. package/engine/src/keymap.rs +0 -107
  23. package/engine/src/keymap_mac.rs +0 -25
  24. package/engine/src/mac.rs +1 -28
  25. package/engine/src/main.rs +22 -0
  26. package/engine/src/peer.rs +297 -0
  27. package/engine/src/portal.rs +0 -18
  28. package/engine/src/protocol.rs +22 -162
  29. package/engine/src/session.rs +176 -3
  30. package/engine/src/win.rs +1 -0
  31. package/engine/src/x11.rs +0 -13
  32. package/engine/vendor/rtc-ice/Cargo.toml +137 -0
  33. package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +111 -0
  34. package/engine/vendor/rtc-ice/LICENSE-APACHE +201 -0
  35. package/engine/vendor/rtc-ice/LICENSE-MIT +21 -0
  36. package/engine/vendor/rtc-ice/README.md +30 -0
  37. package/engine/vendor/rtc-ice/examples/ping_pong.rs +376 -0
  38. package/engine/vendor/rtc-ice/src/agent/agent_config.rs +126 -0
  39. package/engine/vendor/rtc-ice/src/agent/agent_proto.rs +172 -0
  40. package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +647 -0
  41. package/engine/vendor/rtc-ice/src/agent/agent_stats.rs +292 -0
  42. package/engine/vendor/rtc-ice/src/agent/agent_test.rs +3594 -0
  43. package/engine/vendor/rtc-ice/src/agent/mod.rs +1753 -0
  44. package/engine/vendor/rtc-ice/src/attributes/control/control_test.rs +168 -0
  45. package/engine/vendor/rtc-ice/src/attributes/control/mod.rs +142 -0
  46. package/engine/vendor/rtc-ice/src/attributes/mod.rs +6 -0
  47. package/engine/vendor/rtc-ice/src/attributes/priority/mod.rs +37 -0
  48. package/engine/vendor/rtc-ice/src/attributes/priority/priority_test.rs +39 -0
  49. package/engine/vendor/rtc-ice/src/attributes/use_candidate/mod.rs +33 -0
  50. package/engine/vendor/rtc-ice/src/attributes/use_candidate/use_candidate_test.rs +19 -0
  51. package/engine/vendor/rtc-ice/src/candidate/candidate_host.rs +53 -0
  52. package/engine/vendor/rtc-ice/src/candidate/candidate_pair.rs +215 -0
  53. package/engine/vendor/rtc-ice/src/candidate/candidate_pair_test.rs +201 -0
  54. package/engine/vendor/rtc-ice/src/candidate/candidate_peer_reflexive.rs +49 -0
  55. package/engine/vendor/rtc-ice/src/candidate/candidate_relay.rs +53 -0
  56. package/engine/vendor/rtc-ice/src/candidate/candidate_relay_test.rs +114 -0
  57. package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive.rs +53 -0
  58. package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive_test.rs +91 -0
  59. package/engine/vendor/rtc-ice/src/candidate/candidate_test.rs +540 -0
  60. package/engine/vendor/rtc-ice/src/candidate/mod.rs +679 -0
  61. package/engine/vendor/rtc-ice/src/dl_trace.rs +49 -0
  62. package/engine/vendor/rtc-ice/src/lib.rs +88 -0
  63. package/engine/vendor/rtc-ice/src/mdns/mdns_test.rs +150 -0
  64. package/engine/vendor/rtc-ice/src/mdns/mod.rs +69 -0
  65. package/engine/vendor/rtc-ice/src/network_type/mod.rs +162 -0
  66. package/engine/vendor/rtc-ice/src/network_type/network_type_test.rs +95 -0
  67. package/engine/vendor/rtc-ice/src/rand/mod.rs +91 -0
  68. package/engine/vendor/rtc-ice/src/rand/rand_test.rs +115 -0
  69. package/engine/vendor/rtc-ice/src/state/mod.rs +106 -0
  70. package/engine/vendor/rtc-ice/src/state/state_test.rs +46 -0
  71. package/engine/vendor/rtc-ice/src/stats/mod.rs +166 -0
  72. package/engine/vendor/rtc-ice/src/tcp_type/mod.rs +47 -0
  73. package/engine/vendor/rtc-ice/src/tcp_type/tcp_type_test.rs +18 -0
  74. package/engine/vendor/rtc-ice/src/url/mod.rs +266 -0
  75. package/engine/vendor/rtc-ice/src/url/url_test.rs +142 -0
  76. package/package.json +8 -3
@@ -0,0 +1,1753 @@
1
+ //! The Sans-I/O ICE agent.
2
+ //!
3
+ //! An [`Agent`] is given local and remote candidates and inbound datagrams; it produces the
4
+ //! connectivity checks to send, the state transitions to report, and eventually a selected
5
+ //! candidate pair. It owns no sockets and no clock — the caller drives time with
6
+ //! `handle_timeout`.
7
+ //!
8
+ //! Two roles exist: the controlling agent nominates the pair that will carry media, the
9
+ //! controlled agent accepts that choice. Which side controls is decided by which offered, and the
10
+ //! two must not agree — see [`Credentials`] and [`Agent::set_role`].
11
+ #[cfg(test)]
12
+ mod agent_test;
13
+
14
+ /// Configuration for a new [`Agent`]: servers, timeouts and role.
15
+ pub mod agent_config;
16
+ mod agent_proto;
17
+ /// Pair selection and nomination — which candidate pair becomes the selected one.
18
+ pub mod agent_selector;
19
+ /// Snapshot statistics for an agent's candidates and pairs.
20
+ pub mod agent_stats;
21
+
22
+ use agent_config::*;
23
+ use bytes::BytesMut;
24
+ use crypto::{RTCCrypto, RTCCryptoProvider};
25
+ use log::{debug, error, info, trace, warn};
26
+ use mdns::{Mdns, QueryId};
27
+ use sansio::Protocol;
28
+ use std::collections::{HashMap, VecDeque};
29
+ use std::net::{IpAddr, SocketAddr};
30
+ use std::sync::Arc;
31
+ use std::time::{Duration, Instant};
32
+ use stun::attributes::*;
33
+ use stun::fingerprint::*;
34
+ use stun::integrity::*;
35
+ use stun::message::*;
36
+ use stun::textattrs::*;
37
+ use stun::xoraddr::*;
38
+
39
+ use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
40
+ use crate::candidate::{candidate_pair::*, *};
41
+ // desklink scout trace (dl-roam-postnom-attr):
42
+ use crate::dl_trace;
43
+ use crate::dl_trace::dl_ice;
44
+ use crate::mdns::{MulticastDnsMode, create_multicast_dns, generate_multicast_dns_name};
45
+ use crate::network_type::NetworkType;
46
+ use crate::rand::*;
47
+ use crate::state::*;
48
+ use crate::tcp_type::TcpType;
49
+ use crate::url::*;
50
+ use shared::error::*;
51
+ use shared::{TaggedBytesMut, TransportContext, TransportProtocol};
52
+
53
+ const ZERO_DURATION: Duration = Duration::from_secs(0);
54
+
55
+ #[derive(Debug, Clone)]
56
+ pub(crate) struct BindingRequest {
57
+ pub(crate) timestamp: Instant,
58
+ pub(crate) transaction_id: TransactionId,
59
+ pub(crate) destination: SocketAddr,
60
+ pub(crate) is_use_candidate: bool,
61
+ }
62
+
63
+ #[derive(Default, Clone)]
64
+ /// The ICE credentials for one side of a session, exchanged in SDP.
65
+ pub struct Credentials {
66
+ /// The username fragment, echoed in every STUN check so a receiver can demultiplex.
67
+ pub ufrag: String,
68
+ /// The password, used as the `MESSAGE-INTEGRITY` key.
69
+ pub pwd: String,
70
+ }
71
+
72
+ #[derive(Default, Clone)]
73
+ pub(crate) struct UfragPwd {
74
+ pub(crate) local_credentials: Credentials,
75
+ pub(crate) remote_credentials: Option<Credentials>,
76
+ /// Credentials generated for an ICE restart that has not been applied yet.
77
+ ///
78
+ /// JSEP requires `createOffer` to be free of side effects, so the offer advertises these
79
+ /// while the live session keeps authenticating with `local_credentials`. They are installed
80
+ /// by [`Agent::apply_restart`] when the local description is set. If the offer is discarded,
81
+ /// they are simply dropped and the existing session is undisturbed.
82
+ pub(crate) pending_local_credentials: Option<Credentials>,
83
+ }
84
+
85
+ fn assert_inbound_username(m: &Message, expected_username: &str) -> Result<()> {
86
+ let mut username = Username::new(ATTR_USERNAME, String::new());
87
+ username.get_from(m)?;
88
+
89
+ if username.to_string() != expected_username {
90
+ return Err(Error::Other(format!(
91
+ "{:?} expected({}) actual({})",
92
+ Error::ErrMismatchUsername,
93
+ expected_username,
94
+ username,
95
+ )));
96
+ }
97
+
98
+ Ok(())
99
+ }
100
+
101
+ fn assert_inbound_message_integrity(
102
+ m: &mut Message,
103
+ key: &[u8],
104
+ crypto: &dyn RTCCrypto,
105
+ ) -> Result<()> {
106
+ MessageIntegrity::check(m, key, crypto)
107
+ }
108
+
109
+ /// What the agent reports to its caller.
110
+ #[non_exhaustive]
111
+ pub enum Event {
112
+ /// The agent's connection state changed.
113
+ ConnectionStateChange(ConnectionState),
114
+ /// A new pair was selected, carrying the local and remote candidates.
115
+ ///
116
+ /// Media should be sent on this pair from now on.
117
+ SelectedCandidatePairChange(Box<Candidate>, Box<Candidate>),
118
+ /// Emitted when the ICE role switches due to a role conflict (RFC 8445 §7.3.1.1).
119
+ /// The bool is `true` if the agent is now controlling, `false` if now controlled.
120
+ RoleChange(bool),
121
+ }
122
+
123
+ /// An [`Event`] together with the instant its condition was observed at.
124
+ ///
125
+ /// The agent's events are produced inside `handle_read` / `handle_timeout` / `close`, all of
126
+ /// which know the time, but they are consumed from `poll_event`, which does not. Carrying the
127
+ /// instant in the payload means the consumer is *told* when the condition happened rather than
128
+ /// having to retain an instant of its own and guess — the same channel `TaggedBytesMut` already
129
+ /// provides on the read path.
130
+ pub struct TaggedEvent {
131
+ /// When the condition this event reports was observed.
132
+ pub now: Instant,
133
+ /// The event itself.
134
+ pub event: Event,
135
+ }
136
+
137
+ /// Represents the ICE agent.
138
+ pub struct Agent {
139
+ pub(crate) crypto_provider: Arc<dyn RTCCryptoProvider>,
140
+ pub(crate) tie_breaker: u64,
141
+ pub(crate) is_controlling: bool,
142
+ pub(crate) lite: bool,
143
+
144
+ pub(crate) start_time: Instant,
145
+
146
+ pub(crate) connection_state: ConnectionState,
147
+ pub(crate) last_connection_state: ConnectionState,
148
+
149
+ //pub(crate) started_ch_tx: Mutex<Option<broadcast::Sender<()>>>,
150
+ pub(crate) ufrag_pwd: UfragPwd,
151
+
152
+ pub(crate) local_candidates: Vec<Candidate>,
153
+ pub(crate) remote_candidates: Vec<Candidate>,
154
+ pub(crate) candidate_pairs: Vec<CandidatePair>,
155
+ pub(crate) nominated_pair: Option<usize>,
156
+ pub(crate) selected_pair: Option<usize>,
157
+
158
+ // desklink patch: the selected pair media keeps using through an ICE
159
+ // restart, until the restarted generation nominates a new one. Held as
160
+ // owned clones because `apply_restart` clears `candidate_pairs`,
161
+ // `local_candidates` and `remote_candidates`, so the old indexes dangle.
162
+ // See DESKLINK-PATCH.md.
163
+ pub(crate) restart_media_pair: Option<(Candidate, Candidate)>,
164
+
165
+ // LRU of outbound Binding request Transaction IDs
166
+ pub(crate) pending_binding_requests: Vec<BindingRequest>,
167
+
168
+ // the following variables won't be changed after init_with_defaults()
169
+ pub(crate) insecure_skip_verify: bool,
170
+ pub(crate) max_binding_requests: u16,
171
+ pub(crate) host_acceptance_min_wait: Duration,
172
+ pub(crate) srflx_acceptance_min_wait: Duration,
173
+ pub(crate) prflx_acceptance_min_wait: Duration,
174
+ pub(crate) relay_acceptance_min_wait: Duration,
175
+ // How long connectivity checks can fail before the ICE Agent
176
+ // goes to disconnected
177
+ pub(crate) disconnected_timeout: Duration,
178
+ // How long connectivity checks can fail before the ICE Agent
179
+ // goes to failed
180
+ pub(crate) failed_timeout: Duration,
181
+ // How often should we send keepalive packets?
182
+ // 0 means never
183
+ pub(crate) keepalive_interval: Duration,
184
+ // When the last STUN consent ping was sent.
185
+ pub(crate) last_consent_sent: Instant,
186
+ // How often should we run our internal taskLoop to check for state changes when connecting
187
+ pub(crate) check_interval: Duration,
188
+ pub(crate) checking_duration: Instant,
189
+ pub(crate) last_checking_time: Instant,
190
+ // When set, a connectivity check has been requested and must be run on the next
191
+ // `handle_timeout`. Checks are deferred (rather than run inline) so that adding a
192
+ // candidate can never re-enter `contact()` and mutate agent state - e.g. transition
193
+ // to `Failed` and drop candidates - while a caller still holds indices into the
194
+ // candidate vectors. See issue #88.
195
+ pub(crate) force_candidate_contact: bool,
196
+
197
+ pub(crate) mdns: Option<Mdns>,
198
+ pub(crate) mdns_queries: HashMap<QueryId, Candidate>,
199
+
200
+ pub(crate) mdns_mode: MulticastDnsMode,
201
+ pub(crate) mdns_local_name: String,
202
+ pub(crate) mdns_local_ip: Option<IpAddr>,
203
+
204
+ pub(crate) candidate_types: Vec<CandidateType>,
205
+ pub(crate) network_types: Vec<NetworkType>,
206
+ pub(crate) urls: Vec<Url>,
207
+
208
+ pub(crate) write_outs: VecDeque<TaggedBytesMut>,
209
+ pub(crate) event_outs: VecDeque<TaggedEvent>,
210
+ }
211
+
212
+ impl Agent {
213
+ /// Creates a new Agent.
214
+ ///
215
+ /// The crypto provider is supplied by the caller; this crate never resolves a default.
216
+ /// Builds an agent whose clock starts at `now`.
217
+ ///
218
+ /// `now` is a constructor argument rather than an `AgentConfig` field because it is a value
219
+ /// consumed once at construction, not a setting that persists for the agent's lifetime — and
220
+ /// because `Instant` has no `Default`, so a config field would force `AgentConfig` to drop
221
+ /// `#[derive(Default)]` and break every `..Default::default()` in its 31 call sites.
222
+ pub fn new(
223
+ now: Instant,
224
+ config: Arc<AgentConfig>,
225
+ crypto_provider: Arc<dyn RTCCryptoProvider>,
226
+ ) -> Result<Self> {
227
+ let tie_breaker = generate_tie_breaker(crypto_provider.random())?;
228
+
229
+ let mut mdns_local_name = config.multicast_dns_local_name.clone();
230
+ if mdns_local_name.is_empty() {
231
+ mdns_local_name = generate_multicast_dns_name();
232
+ }
233
+
234
+ if !mdns_local_name.ends_with(".local") || mdns_local_name.split('.').count() != 2 {
235
+ return Err(Error::ErrInvalidMulticastDnshostName);
236
+ }
237
+
238
+ let mdns_mode = config.multicast_dns_mode;
239
+ let mdns = create_multicast_dns(
240
+ now,
241
+ mdns_mode,
242
+ &mdns_local_name,
243
+ &config.multicast_dns_local_ip,
244
+ &config.multicast_dns_query_timeout,
245
+ )
246
+ .unwrap_or_else(|err| {
247
+ // Opportunistic mDNS: If we can't open the connection, that's ok: we
248
+ // can continue without it.
249
+ warn!("Failed to initialize mDNS {mdns_local_name}: {err}");
250
+ None
251
+ });
252
+
253
+ let candidate_types = if config.candidate_types.is_empty() {
254
+ default_candidate_types()
255
+ } else {
256
+ config.candidate_types.clone()
257
+ };
258
+
259
+ if config.lite && (candidate_types.len() != 1 || candidate_types[0] != CandidateType::Host)
260
+ {
261
+ return Err(Error::ErrLiteUsingNonHostCandidates);
262
+ }
263
+
264
+ if !config.urls.is_empty()
265
+ && !contains_candidate_type(CandidateType::ServerReflexive, &candidate_types)
266
+ && !contains_candidate_type(CandidateType::Relay, &candidate_types)
267
+ {
268
+ return Err(Error::ErrUselessUrlsProvided);
269
+ }
270
+
271
+ let mut agent = Self {
272
+ crypto_provider,
273
+ tie_breaker,
274
+ is_controlling: config.is_controlling,
275
+ lite: config.lite,
276
+
277
+ start_time: now,
278
+
279
+ nominated_pair: None,
280
+ selected_pair: None,
281
+ // desklink patch: nothing is preserved across a restart yet.
282
+ restart_media_pair: None,
283
+ candidate_pairs: vec![],
284
+
285
+ connection_state: ConnectionState::New,
286
+
287
+ insecure_skip_verify: config.insecure_skip_verify,
288
+
289
+ //started_ch_tx: MuteSome(started_ch_tx)),
290
+
291
+ //won't change after init_with_defaults()
292
+ max_binding_requests: if let Some(max_binding_requests) = config.max_binding_requests {
293
+ max_binding_requests
294
+ } else {
295
+ DEFAULT_MAX_BINDING_REQUESTS
296
+ },
297
+ host_acceptance_min_wait: if let Some(host_acceptance_min_wait) =
298
+ config.host_acceptance_min_wait
299
+ {
300
+ host_acceptance_min_wait
301
+ } else {
302
+ DEFAULT_HOST_ACCEPTANCE_MIN_WAIT
303
+ },
304
+ srflx_acceptance_min_wait: if let Some(srflx_acceptance_min_wait) =
305
+ config.srflx_acceptance_min_wait
306
+ {
307
+ srflx_acceptance_min_wait
308
+ } else {
309
+ DEFAULT_SRFLX_ACCEPTANCE_MIN_WAIT
310
+ },
311
+ prflx_acceptance_min_wait: if let Some(prflx_acceptance_min_wait) =
312
+ config.prflx_acceptance_min_wait
313
+ {
314
+ prflx_acceptance_min_wait
315
+ } else {
316
+ DEFAULT_PRFLX_ACCEPTANCE_MIN_WAIT
317
+ },
318
+ relay_acceptance_min_wait: if let Some(relay_acceptance_min_wait) =
319
+ config.relay_acceptance_min_wait
320
+ {
321
+ relay_acceptance_min_wait
322
+ } else {
323
+ DEFAULT_RELAY_ACCEPTANCE_MIN_WAIT
324
+ },
325
+
326
+ // How long connectivity checks can fail before the ICE Agent
327
+ // goes to disconnected
328
+ disconnected_timeout: if let Some(disconnected_timeout) = config.disconnected_timeout {
329
+ disconnected_timeout
330
+ } else {
331
+ DEFAULT_DISCONNECTED_TIMEOUT
332
+ },
333
+
334
+ // How long connectivity checks can fail before the ICE Agent
335
+ // goes to failed
336
+ failed_timeout: if let Some(failed_timeout) = config.failed_timeout {
337
+ failed_timeout
338
+ } else {
339
+ DEFAULT_FAILED_TIMEOUT
340
+ },
341
+
342
+ // How often should we send keepalive packets?
343
+ // 0 means never
344
+ keepalive_interval: if let Some(keepalive_interval) = config.keepalive_interval {
345
+ keepalive_interval
346
+ } else {
347
+ DEFAULT_KEEPALIVE_INTERVAL
348
+ },
349
+
350
+ // How often should we run our internal taskLoop to check for state changes when connecting
351
+ check_interval: if config.check_interval == Duration::from_secs(0) {
352
+ DEFAULT_CHECK_INTERVAL
353
+ } else {
354
+ config.check_interval
355
+ },
356
+ last_consent_sent: now,
357
+ checking_duration: now,
358
+ last_checking_time: now,
359
+ force_candidate_contact: false,
360
+ last_connection_state: ConnectionState::Unspecified,
361
+
362
+ mdns,
363
+ mdns_queries: HashMap::new(),
364
+
365
+ mdns_mode,
366
+ mdns_local_name,
367
+ mdns_local_ip: config.multicast_dns_local_ip,
368
+
369
+ ufrag_pwd: UfragPwd::default(),
370
+
371
+ local_candidates: vec![],
372
+ remote_candidates: vec![],
373
+
374
+ // LRU of outbound Binding request Transaction IDs
375
+ pending_binding_requests: vec![],
376
+
377
+ candidate_types,
378
+ network_types: config.network_types.clone(),
379
+ urls: config.urls.clone(),
380
+
381
+ write_outs: VecDeque::new(),
382
+ event_outs: VecDeque::new(),
383
+ };
384
+
385
+ // Restart is also used to initialize the agent for the first time
386
+ if let Err(err) = agent.restart(
387
+ now,
388
+ config.local_ufrag.clone(),
389
+ config.local_pwd.clone(),
390
+ false,
391
+ ) {
392
+ let _ = agent.close();
393
+ return Err(err);
394
+ }
395
+
396
+ Ok(agent)
397
+ }
398
+
399
+ /// Adds a new local candidate.
400
+ pub fn add_local_candidate(&mut self, mut c: Candidate) -> Result<bool> {
401
+ // Filter by network type if network_types is configured
402
+ if !self.network_types.is_empty() {
403
+ let candidate_network_type = c.network_type();
404
+ if !self.network_types.contains(&candidate_network_type) {
405
+ debug!(
406
+ "Ignoring local candidate with network type {:?} (not in configured network types: {:?})",
407
+ candidate_network_type, self.network_types
408
+ );
409
+ return Ok(false);
410
+ }
411
+ }
412
+
413
+ // Filter by candidate type if candidate_types is configured.
414
+ let candidate_type = c.candidate_type();
415
+ if !self.candidate_types.is_empty() && !self.candidate_types.contains(&candidate_type) {
416
+ debug!(
417
+ "Ignoring local candidate with type {:?} (not in configured candidate types: {:?})",
418
+ candidate_type, self.candidate_types
419
+ );
420
+ return Ok(false);
421
+ }
422
+
423
+ if c.candidate_type() == CandidateType::Host
424
+ && self.mdns_mode == MulticastDnsMode::QueryAndGather
425
+ && c.network_type == NetworkType::Udp4
426
+ && self
427
+ .mdns_local_ip
428
+ .is_some_and(|local_ip| local_ip == c.addr().ip())
429
+ {
430
+ // only one .local mDNS host candidate per IPv4 is supported
431
+ // when registered local ip matches, use mdns_local_name to hide local host ip
432
+ trace!(
433
+ "mDNS hides local ip {} with local name {}",
434
+ c.address, self.mdns_local_name
435
+ );
436
+ c.address = self.mdns_local_name.clone();
437
+ }
438
+
439
+ for cand in &self.local_candidates {
440
+ if cand.equal(&c) {
441
+ return Ok(false);
442
+ }
443
+ }
444
+
445
+ self.local_candidates.push(c);
446
+ let local_index = self.local_candidates.len() - 1;
447
+
448
+ for remote_index in 0..self.remote_candidates.len() {
449
+ if self.local_candidates[local_index]
450
+ .can_pair_with(&self.remote_candidates[remote_index])
451
+ {
452
+ self.add_pair(local_index, remote_index);
453
+ }
454
+ }
455
+
456
+ self.request_connectivity_check();
457
+
458
+ Ok(true)
459
+ }
460
+
461
+ /// Adds a new remote candidate.
462
+ pub fn add_remote_candidate(&mut self, c: Candidate) -> Result<bool> {
463
+ // Filter by network type if network_types is configured
464
+ if !self.network_types.is_empty() {
465
+ let candidate_network_type = c.network_type();
466
+ if !self.network_types.contains(&candidate_network_type) {
467
+ debug!(
468
+ "Ignoring remote candidate with network type {:?} (not in configured network types: {:?})",
469
+ candidate_network_type, self.network_types
470
+ );
471
+ return Ok(false);
472
+ }
473
+ }
474
+
475
+ // TCP active candidates don't have a listening port - they initiate connections.
476
+ // The remote active side will probe our passive candidates, so we don't need
477
+ // to do anything with remote active candidates.
478
+ if c.tcp_type() == TcpType::Active {
479
+ debug!(
480
+ "Ignoring remote candidate with tcptype active: {}",
481
+ c.address()
482
+ );
483
+ return Ok(false);
484
+ }
485
+
486
+ // If we have a mDNS Candidate lets fully resolve it before adding it locally
487
+ if c.candidate_type() == CandidateType::Host && c.address().ends_with(".local") {
488
+ if self.mdns_mode == MulticastDnsMode::Disabled {
489
+ warn!(
490
+ "remote mDNS candidate added, but mDNS is disabled: ({})",
491
+ c.address()
492
+ );
493
+ return Ok(false);
494
+ }
495
+
496
+ if c.candidate_type() != CandidateType::Host {
497
+ return Err(Error::ErrAddressParseFailed);
498
+ }
499
+
500
+ if let Some(mdns_conn) = &mut self.mdns {
501
+ let query_id = mdns_conn.schedule_query(c.address());
502
+ self.mdns_queries.insert(query_id, c);
503
+ }
504
+
505
+ return Ok(false);
506
+ }
507
+
508
+ self.trigger_request_connectivity_check(vec![c]);
509
+ Ok(true)
510
+ }
511
+
512
+ fn trigger_request_connectivity_check(&mut self, remote_candidates: Vec<Candidate>) {
513
+ for c in remote_candidates {
514
+ if !self.remote_candidates.iter().any(|cand| cand.equal(&c)) {
515
+ self.remote_candidates.push(c);
516
+ let remote_index = self.remote_candidates.len() - 1;
517
+
518
+ for local_index in 0..self.local_candidates.len() {
519
+ if self.local_candidates[local_index]
520
+ .can_pair_with(&self.remote_candidates[remote_index])
521
+ {
522
+ self.add_pair(local_index, remote_index);
523
+ }
524
+ }
525
+
526
+ // desklink scout trace: a remote candidate landed and an
527
+ // immediate check pass was requested for it.
528
+ if dl_trace::enabled() {
529
+ dl_ice!(
530
+ "{} remote_candidate_added {} pairs={} force_check",
531
+ self.get_name(),
532
+ self.remote_candidates[remote_index].addr(),
533
+ self.candidate_pairs.len(),
534
+ );
535
+ }
536
+
537
+ self.request_connectivity_check();
538
+ }
539
+ }
540
+ }
541
+
542
+ /// Sets the credentials of the remote agent.
543
+ pub fn set_remote_credentials(
544
+ &mut self,
545
+ remote_ufrag: String,
546
+ remote_pwd: String,
547
+ ) -> Result<()> {
548
+ if remote_ufrag.is_empty() {
549
+ return Err(Error::ErrRemoteUfragEmpty);
550
+ } else if remote_pwd.is_empty() {
551
+ return Err(Error::ErrRemotePwdEmpty);
552
+ }
553
+
554
+ self.ufrag_pwd.remote_credentials = Some(Credentials {
555
+ ufrag: remote_ufrag,
556
+ pwd: remote_pwd,
557
+ });
558
+
559
+ Ok(())
560
+ }
561
+
562
+ /// Returns the remote credentials.
563
+ pub fn get_remote_credentials(&self) -> Option<&Credentials> {
564
+ self.ufrag_pwd.remote_credentials.as_ref()
565
+ }
566
+
567
+ /// Returns the local credentials.
568
+ /// The credentials to advertise, which is what SDP generation wants.
569
+ ///
570
+ /// Returns credentials staged by [`Agent::generate_restart_credentials`] when an ICE restart
571
+ /// is pending, so an offer carries the new ufrag/pwd. Inbound STUN validation deliberately
572
+ /// does *not* go through here — it reads `local_credentials` directly, so the live session
573
+ /// keeps working until [`Agent::apply_restart`] installs the new pair.
574
+ pub fn get_local_credentials(&self) -> &Credentials {
575
+ self.ufrag_pwd
576
+ .pending_local_credentials
577
+ .as_ref()
578
+ .unwrap_or(&self.ufrag_pwd.local_credentials)
579
+ }
580
+
581
+ /// Whether this agent is the controlling one, which decides nomination.
582
+ pub fn role(&self) -> bool {
583
+ self.is_controlling
584
+ }
585
+
586
+ /// Sets the controlling role.
587
+ ///
588
+ /// Determined by which side offered; the two agents must not agree, or nomination stalls.
589
+ pub fn set_role(&mut self, is_controlling: bool) {
590
+ self.is_controlling = is_controlling;
591
+ }
592
+
593
+ /// The agent's current connection state.
594
+ pub fn state(&self) -> ConnectionState {
595
+ self.connection_state
596
+ }
597
+
598
+ /// Whether a non-STUN datagram on `transport` should be accepted as media.
599
+ ///
600
+ /// Guards against accepting media from an address that has not passed a connectivity check.
601
+ pub fn is_valid_non_stun_traffic(&mut self, now: Instant, transport: TransportContext) -> bool {
602
+ self.find_local_candidate(transport.local_addr, transport.transport_protocol)
603
+ .is_some()
604
+ && self.validate_non_stun_traffic(now, transport.peer_addr)
605
+ }
606
+
607
+ fn get_timeout_interval(&self) -> Duration {
608
+ let (check_interval, keepalive_interval, disconnected_timeout, failed_timeout) = (
609
+ self.check_interval,
610
+ self.keepalive_interval,
611
+ self.disconnected_timeout,
612
+ self.failed_timeout,
613
+ );
614
+ let mut interval = DEFAULT_CHECK_INTERVAL;
615
+
616
+ let mut update_interval = |x: Duration| {
617
+ if x != ZERO_DURATION && (interval == ZERO_DURATION || interval > x) {
618
+ interval = x;
619
+ }
620
+ };
621
+
622
+ match self.last_connection_state {
623
+ ConnectionState::New | ConnectionState::Checking => {
624
+ // While connecting, check candidates more frequently
625
+ update_interval(check_interval);
626
+ }
627
+ ConnectionState::Connected | ConnectionState::Disconnected => {
628
+ update_interval(keepalive_interval);
629
+ }
630
+ _ => {}
631
+ };
632
+ // Ensure we run our task loop as quickly as the minimum of our various configured timeouts
633
+ update_interval(disconnected_timeout);
634
+ update_interval(failed_timeout);
635
+ interval
636
+ }
637
+
638
+ /// Returns the selected pair (local_candidate, remote_candidate) or none
639
+ pub fn get_selected_candidate_pair(&self) -> Option<(&Candidate, &Candidate)> {
640
+ if let Some(pair_index) = self.get_selected_pair() {
641
+ let candidate_pair = &self.candidate_pairs[pair_index];
642
+ Some((
643
+ &self.local_candidates[candidate_pair.local_index],
644
+ &self.remote_candidates[candidate_pair.remote_index],
645
+ ))
646
+ } else if let Some((local, remote)) = &self.restart_media_pair {
647
+ // desklink patch: an ICE restart is in flight and has not nominated
648
+ // its new pair yet, so media keeps flowing on the pair it was on.
649
+ // RFC 8445 keeps the previous selected pair until the new one is
650
+ // validated. The transport layer reads this to stamp outgoing
651
+ // DTLS/SRTP and to pass up inbound packets.
652
+ Some((local, remote))
653
+ } else {
654
+ None
655
+ }
656
+ }
657
+
658
+ /// The highest-priority pair that is still usable, whether or not it has been nominated.
659
+ pub fn get_best_available_candidate_pair(&self) -> Option<(&Candidate, &Candidate)> {
660
+ if let Some(pair_index) = self.get_best_available_pair() {
661
+ let candidate_pair = &self.candidate_pairs[pair_index];
662
+ Some((
663
+ &self.local_candidates[candidate_pair.local_index],
664
+ &self.remote_candidates[candidate_pair.remote_index],
665
+ ))
666
+ } else {
667
+ None
668
+ }
669
+ }
670
+
671
+ /// start connectivity checks
672
+ pub fn start_connectivity_checks(
673
+ &mut self,
674
+ now: Instant,
675
+ is_controlling: bool,
676
+ remote_ufrag: String,
677
+ remote_pwd: String,
678
+ ) -> Result<()> {
679
+ debug!(
680
+ "Started agent: isControlling? {}, remoteUfrag: {}, remotePwd: {}",
681
+ is_controlling, remote_ufrag, remote_pwd
682
+ );
683
+ self.set_remote_credentials(remote_ufrag, remote_pwd)?;
684
+ self.is_controlling = is_controlling;
685
+ self.start(now);
686
+
687
+ self.update_connection_state(Some(now), ConnectionState::Checking);
688
+ self.request_connectivity_check();
689
+
690
+ Ok(())
691
+ }
692
+
693
+ /// Restarts the ICE Agent with the provided ufrag/pwd
694
+ /// If no ufrag/pwd is provided the Agent will generate one itself.
695
+ /// Stages new local credentials for an ICE restart, without disturbing the live session.
696
+ ///
697
+ /// This is the half of an ICE restart that an offerer needs *before* generating SDP: the offer
698
+ /// must carry the new ufrag/pwd. It deliberately does not touch `local_credentials`,
699
+ /// candidate pairs, or connection state, because JSEP requires `createOffer` to be free of
700
+ /// side effects — and because inbound STUN is still being validated against the current
701
+ /// credentials until the local description is applied.
702
+ ///
703
+ /// Empty `ufrag` / `pwd` are filled from the crypto provider's RNG. Call
704
+ /// [`Agent::apply_restart`] to install them.
705
+ pub fn generate_restart_credentials(
706
+ &mut self,
707
+ mut ufrag: String,
708
+ mut pwd: String,
709
+ ) -> Result<()> {
710
+ if ufrag.is_empty() {
711
+ ufrag = generate_ufrag_with_random(self.crypto_provider.random())?;
712
+ }
713
+ if pwd.is_empty() {
714
+ pwd = generate_pwd_with_random(self.crypto_provider.random())?;
715
+ }
716
+
717
+ if ufrag.len() * 8 < 24 {
718
+ return Err(Error::ErrLocalUfragInsufficientBits);
719
+ }
720
+ if pwd.len() * 8 < 128 {
721
+ return Err(Error::ErrLocalPwdInsufficientBits);
722
+ }
723
+
724
+ self.ufrag_pwd.pending_local_credentials = Some(Credentials { ufrag, pwd });
725
+
726
+ Ok(())
727
+ }
728
+
729
+ /// Whether [`Agent::generate_restart_credentials`] has staged a restart that is not yet applied.
730
+ pub fn has_pending_restart(&self) -> bool {
731
+ self.ufrag_pwd.pending_local_credentials.is_some()
732
+ }
733
+
734
+ /// Applies a staged ICE restart, tearing down the old session and starting a new one at `now`.
735
+ ///
736
+ /// Installs the credentials staged by [`Agent::generate_restart_credentials`], if any, then
737
+ /// discards the remote credentials, pending binding requests, candidate pairs and selected
738
+ /// pair, and restarts the agent's timers. With nothing staged, the current credentials are
739
+ /// kept and only the session is restarted.
740
+ pub fn apply_restart(&mut self, now: Instant, keep_local_candidates: bool) -> Result<()> {
741
+ // desklink scout trace: restart applied — the new generation begins.
742
+ if dl_trace::enabled() {
743
+ dl_ice!(
744
+ "{} APPLY_RESTART keep_local={keep_local_candidates} role={} state={:?} local_cands={} remote_cands={} pairs={}",
745
+ self.get_name(),
746
+ if self.is_controlling { "controlling" } else { "controlled" },
747
+ self.connection_state,
748
+ self.local_candidates.len(),
749
+ self.remote_candidates.len(),
750
+ self.candidate_pairs.len(),
751
+ );
752
+ }
753
+ // desklink patch: remember the pair media is on before the teardown
754
+ // below clears it, so the ICE transport keeps sending and receiving on
755
+ // it until the restarted generation nominates a pair (see
756
+ // `get_selected_candidate_pair`). Cleared when a new pair is selected,
757
+ // or when the connection fails or closes.
758
+ let keep_media_on = self
759
+ .get_selected_candidate_pair()
760
+ .map(|(local, remote)| (local.clone(), remote.clone()));
761
+ self.restart_media_pair = keep_media_on;
762
+
763
+ if let Some(credentials) = self.ufrag_pwd.pending_local_credentials.take() {
764
+ self.ufrag_pwd.local_credentials = credentials;
765
+ }
766
+ self.ufrag_pwd.remote_credentials = None;
767
+
768
+ self.pending_binding_requests = vec![];
769
+
770
+ self.candidate_pairs = vec![];
771
+
772
+ self.set_selected_pair(Some(now), None);
773
+ self.delete_all_candidates(keep_local_candidates);
774
+ self.start(now);
775
+
776
+ // Restart is used by NewAgent. Accept/Connect should be used to move to checking
777
+ // for new Agents
778
+ if self.connection_state != ConnectionState::New {
779
+ self.update_connection_state(Some(now), ConnectionState::Checking);
780
+ }
781
+
782
+ Ok(())
783
+ }
784
+
785
+ /// Generates and immediately applies an ICE restart.
786
+ ///
787
+ /// Equivalent to [`Agent::generate_restart_credentials`] followed by
788
+ /// [`Agent::apply_restart`]. A JSEP-conformant offerer wants the two halves separately —
789
+ /// credentials at `createOffer`, application at `setLocalDescription` — but this remains the
790
+ /// right call for anything that restarts in one step.
791
+ pub fn restart(
792
+ &mut self,
793
+ now: Instant,
794
+ ufrag: String,
795
+ pwd: String,
796
+ keep_local_candidates: bool,
797
+ ) -> Result<()> {
798
+ self.generate_restart_credentials(ufrag, pwd)?;
799
+ self.apply_restart(now, keep_local_candidates)
800
+ }
801
+
802
+ /// Returns the local candidates.
803
+ pub fn get_local_candidates(&self) -> &[Candidate] {
804
+ &self.local_candidates
805
+ }
806
+
807
+ /// The remote candidates this agent has been given, in the order they were added.
808
+ pub fn get_remote_candidates(&self) -> &[Candidate] {
809
+ &self.remote_candidates
810
+ }
811
+
812
+ fn contact(&mut self, now: Instant) {
813
+ // desklink scout trace: one line per agent contact() pass, so the tick
814
+ // cadence (and any forced pass) is measurable.
815
+ if dl_trace::enabled() {
816
+ dl_ice!(
817
+ "{} tick_begin state={:?} sel={:?} nom={:?} pairs={} outstanding={} force={}",
818
+ self.get_name(),
819
+ self.connection_state,
820
+ self.selected_pair,
821
+ self.nominated_pair,
822
+ self.candidate_pairs.len(),
823
+ self.pending_binding_requests.len(),
824
+ self.force_candidate_contact,
825
+ );
826
+ }
827
+ // Consume any pending deferred-check request now that we are running one.
828
+ // Reset before the early returns below so a failed/settled agent does not
829
+ // keep asking `poll_timeout` for an immediate wake-up.
830
+ self.force_candidate_contact = false;
831
+
832
+ if self.connection_state == ConnectionState::Failed {
833
+ // The connection is currently failed so don't send any checks
834
+ // In the future it may be restarted though
835
+ self.last_connection_state = self.connection_state;
836
+ return;
837
+ }
838
+ if self.connection_state == ConnectionState::Checking {
839
+ // We have just entered checking for the first time so update our checking timer
840
+ if self.last_connection_state != self.connection_state {
841
+ self.checking_duration = now;
842
+ }
843
+
844
+ // We have been in checking longer then Disconnect+Failed timeout, set the connection to Failed
845
+ if self.failed_timeout != ZERO_DURATION
846
+ && now
847
+ .checked_duration_since(self.checking_duration)
848
+ .unwrap_or_else(|| Duration::from_secs(0))
849
+ > self.disconnected_timeout + self.failed_timeout
850
+ {
851
+ self.update_connection_state(Some(now), ConnectionState::Failed);
852
+ self.last_connection_state = self.connection_state;
853
+ return;
854
+ }
855
+ }
856
+
857
+ self.contact_candidates(now);
858
+
859
+ self.last_connection_state = self.connection_state;
860
+ self.last_checking_time = now;
861
+ }
862
+
863
+ pub(crate) fn update_connection_state(
864
+ &mut self,
865
+ now: Option<Instant>,
866
+ new_state: ConnectionState,
867
+ ) {
868
+ if self.connection_state != new_state {
869
+ // desklink scout trace: every connection-state transition, with the
870
+ // selection context that explains it.
871
+ if dl_trace::enabled() {
872
+ dl_ice!(
873
+ "{} state {:?} -> {:?} sel={:?} nom={:?} pairs={}",
874
+ self.get_name(),
875
+ self.connection_state,
876
+ new_state,
877
+ self.selected_pair,
878
+ self.nominated_pair,
879
+ self.candidate_pairs.len(),
880
+ );
881
+ }
882
+ // Connection has gone to failed, release all gathered candidates
883
+ if new_state == ConnectionState::Failed {
884
+ self.set_selected_pair(now, None);
885
+ self.delete_all_candidates(false);
886
+ }
887
+ // desklink patch: once the connection has failed or closed there is
888
+ // no reachable pair, so media must stop instead of keeping the pair
889
+ // preserved for a restart that will not nominate one.
890
+ if matches!(new_state, ConnectionState::Failed | ConnectionState::Closed) {
891
+ self.restart_media_pair = None;
892
+ }
893
+
894
+ info!(
895
+ "[{}]: Setting new connection state: {}",
896
+ self.get_name(),
897
+ new_state
898
+ );
899
+ self.connection_state = new_state;
900
+ if let Some(now) = now {
901
+ self.event_outs.push_back(TaggedEvent {
902
+ now,
903
+ event: Event::ConnectionStateChange(new_state),
904
+ });
905
+ }
906
+ }
907
+ }
908
+
909
+ pub(crate) fn set_selected_pair(&mut self, now: Option<Instant>, selected_pair: Option<usize>) {
910
+ if let Some(pair_index) = selected_pair {
911
+ trace!(
912
+ "[{}]: Set selected candidate pair: {:?}",
913
+ self.get_name(),
914
+ self.candidate_pairs[pair_index]
915
+ );
916
+
917
+ // desklink scout trace: nomination landed on a pair — the moment the
918
+ // agent moves to Connected.
919
+ if dl_trace::enabled() {
920
+ let p = &self.candidate_pairs[pair_index];
921
+ dl_ice!(
922
+ "{} SELECTED_PAIR local={} remote={} pairs={}",
923
+ self.get_name(),
924
+ self.local_candidates[p.local_index].addr(),
925
+ self.remote_candidates[p.remote_index].addr(),
926
+ self.candidate_pairs.len(),
927
+ );
928
+ }
929
+
930
+ self.candidate_pairs[pair_index].nominated = true;
931
+ self.selected_pair = Some(pair_index);
932
+ // desklink patch: the restarted generation now has a nominated pair,
933
+ // so media moves onto it and the preserved pair is released.
934
+ self.restart_media_pair = None;
935
+
936
+ self.update_connection_state(now, ConnectionState::Connected);
937
+
938
+ // Notify when the selected pair changes
939
+ let candidate_pair = &self.candidate_pairs[pair_index];
940
+ if let Some(now) = now {
941
+ self.event_outs.push_back(TaggedEvent {
942
+ now,
943
+ event: Event::SelectedCandidatePairChange(
944
+ Box::new(self.local_candidates[candidate_pair.local_index].clone()),
945
+ Box::new(self.remote_candidates[candidate_pair.remote_index].clone()),
946
+ ),
947
+ });
948
+ }
949
+ } else {
950
+ self.selected_pair = None;
951
+ }
952
+ }
953
+
954
+ pub(crate) fn ping_all_candidates(&mut self, now: Instant) {
955
+ let mut pairs: Vec<(usize, usize)> = vec![];
956
+
957
+ let name = self.get_name().to_string();
958
+ if self.candidate_pairs.is_empty() {
959
+ warn!(
960
+ "[{}]: pingAllCandidates called with no candidate pairs. Connection is not possible yet.",
961
+ name,
962
+ );
963
+ }
964
+ for p in &mut self.candidate_pairs {
965
+ if p.state == CandidatePairState::Waiting {
966
+ p.state = CandidatePairState::InProgress;
967
+ } else if p.state != CandidatePairState::InProgress {
968
+ continue;
969
+ }
970
+
971
+ if p.binding_request_count > self.max_binding_requests {
972
+ trace!(
973
+ "[{}]: max requests reached for pair {} (local_addr {} <-> remote_addr {}), marking it as failed",
974
+ name,
975
+ *p,
976
+ self.local_candidates[p.local_index].addr(),
977
+ self.remote_candidates[p.remote_index].addr()
978
+ );
979
+ p.state = CandidatePairState::Failed;
980
+ } else {
981
+ p.binding_request_count += 1;
982
+ let local = p.local_index;
983
+ let remote = p.remote_index;
984
+ pairs.push((local, remote));
985
+ }
986
+ }
987
+
988
+ if !pairs.is_empty() {
989
+ trace!(
990
+ "[{}]: pinging all {} candidates",
991
+ self.get_name(),
992
+ pairs.len()
993
+ );
994
+ }
995
+
996
+ for (local, remote) in pairs {
997
+ self.ping_candidate(now, local, remote);
998
+ }
999
+ }
1000
+
1001
+ pub(crate) fn add_pair(&mut self, local_index: usize, remote_index: usize) {
1002
+ let p = CandidatePair::new(
1003
+ local_index,
1004
+ remote_index,
1005
+ self.local_candidates[local_index].priority(),
1006
+ self.remote_candidates[remote_index].priority(),
1007
+ self.is_controlling,
1008
+ );
1009
+ self.candidate_pairs.push(p);
1010
+ }
1011
+
1012
+ pub(crate) fn find_pair(&self, local_index: usize, remote_index: usize) -> Option<usize> {
1013
+ for (index, p) in self.candidate_pairs.iter().enumerate() {
1014
+ if p.local_index == local_index && p.remote_index == remote_index {
1015
+ return Some(index);
1016
+ }
1017
+ }
1018
+ None
1019
+ }
1020
+
1021
+ /// Checks if the selected pair is (still) valid.
1022
+ /// Note: the caller should hold the agent lock.
1023
+ /// Re-evaluates the selected pair's liveness as of `now`.
1024
+ ///
1025
+ /// `now` comes from the caller so that this decision and the keepalive decision below are
1026
+ /// made against the same instant as the `contact(now)` that reached them.
1027
+ pub(crate) fn validate_selected_pair(&mut self, now: Instant) -> bool {
1028
+ let (valid, disconnected_time) = {
1029
+ self.selected_pair.as_ref().map_or_else(
1030
+ || (false, Duration::from_secs(0)),
1031
+ |&pair_index| {
1032
+ let remote_index = self.candidate_pairs[pair_index].remote_index;
1033
+
1034
+ let last_received = self.remote_candidates[remote_index]
1035
+ .last_received()
1036
+ .unwrap_or(self.start_time);
1037
+ let disconnected_time = now.saturating_duration_since(last_received);
1038
+ (true, disconnected_time)
1039
+ },
1040
+ )
1041
+ };
1042
+
1043
+ if valid {
1044
+ // Only allow transitions to fail if a.failedTimeout is non-zero
1045
+ let mut total_time_to_failure = self.failed_timeout;
1046
+ if total_time_to_failure != Duration::from_secs(0) {
1047
+ total_time_to_failure += self.disconnected_timeout;
1048
+ }
1049
+
1050
+ if total_time_to_failure != Duration::from_secs(0)
1051
+ && disconnected_time > total_time_to_failure
1052
+ {
1053
+ self.update_connection_state(Some(now), ConnectionState::Failed);
1054
+ } else if self.disconnected_timeout != Duration::from_secs(0)
1055
+ && disconnected_time > self.disconnected_timeout
1056
+ {
1057
+ self.update_connection_state(Some(now), ConnectionState::Disconnected);
1058
+ } else {
1059
+ self.update_connection_state(Some(now), ConnectionState::Connected);
1060
+ }
1061
+ }
1062
+
1063
+ valid
1064
+ }
1065
+
1066
+ /// Sends STUN Binding Requests to the selected pair at `keepalive_interval` to
1067
+ /// maintain consent freshness (RFC 7675).
1068
+ pub(crate) fn check_keepalive(&mut self, now: Instant) {
1069
+ let (local_index, remote_index, pair_index) = {
1070
+ self.selected_pair
1071
+ .as_ref()
1072
+ .map_or((None, None, None), |&pair_index| {
1073
+ let p = &self.candidate_pairs[pair_index];
1074
+ (Some(p.local_index), Some(p.remote_index), Some(pair_index))
1075
+ })
1076
+ };
1077
+
1078
+ if let (Some(local_index), Some(remote_index), Some(pair_index)) =
1079
+ (local_index, remote_index, pair_index)
1080
+ && self.keepalive_interval != Duration::from_secs(0)
1081
+ && now.saturating_duration_since(self.last_consent_sent) >= self.keepalive_interval
1082
+ {
1083
+ self.last_consent_sent = now;
1084
+ self.candidate_pairs[pair_index].on_consent_request_sent();
1085
+ self.ping_candidate(now, local_index, remote_index);
1086
+ }
1087
+ }
1088
+
1089
+ fn request_connectivity_check(&mut self) {
1090
+ // Defer the connectivity check to `handle_timeout` instead of running it
1091
+ // inline. Running `contact()` here is re-entrant: it can advance the state
1092
+ // to `Failed`, which calls `delete_all_candidates()` and wipes the candidate
1093
+ // and pair vectors while a caller still holds indices into them (for example
1094
+ // `handle_inbound` while adding a peer-reflexive candidate). See issue #88.
1095
+ self.force_candidate_contact = true;
1096
+ }
1097
+
1098
+ /// Remove all candidates.
1099
+ /// This closes any listening sockets and removes both the local and remote candidate lists.
1100
+ ///
1101
+ /// This is used for restarts, failures and on close.
1102
+ pub(crate) fn delete_all_candidates(&mut self, keep_local_candidates: bool) {
1103
+ if !keep_local_candidates {
1104
+ self.local_candidates.clear();
1105
+ }
1106
+ self.remote_candidates.clear();
1107
+
1108
+ // Candidate pairs reference candidates by index, so once the candidates are
1109
+ // removed every pair - and the `selected_pair`/`nominated_pair` indices into
1110
+ // the pair list - is dangling and must be dropped to avoid out-of-bounds
1111
+ // access. `remote_candidates` is always cleared here, so no pair can remain
1112
+ // valid even when the local candidates are kept. See issue #88.
1113
+ self.candidate_pairs.clear();
1114
+ self.selected_pair = None;
1115
+ self.nominated_pair = None;
1116
+ }
1117
+
1118
+ pub(crate) fn find_remote_candidate(&self, addr: SocketAddr) -> Option<usize> {
1119
+ for (index, c) in self.remote_candidates.iter().enumerate() {
1120
+ if c.addr() == addr {
1121
+ return Some(index);
1122
+ }
1123
+ }
1124
+ None
1125
+ }
1126
+
1127
+ pub(crate) fn find_local_candidate(
1128
+ &self,
1129
+ addr: SocketAddr,
1130
+ transport_protocol: TransportProtocol,
1131
+ ) -> Option<usize> {
1132
+ for (index, c) in self.local_candidates.iter().enumerate() {
1133
+ if c.network_type().to_protocol() != transport_protocol {
1134
+ continue;
1135
+ }
1136
+
1137
+ // For TCP active candidates, match by IP only (ignore port).
1138
+ // TCP active candidates use port 9 as placeholder in signaling,
1139
+ // but the actual connection uses an ephemeral port.
1140
+ if c.tcp_type() == TcpType::Active && transport_protocol == TransportProtocol::TCP {
1141
+ if c.addr().ip() == addr.ip() {
1142
+ return Some(index);
1143
+ }
1144
+ } else if c.addr() == addr {
1145
+ return Some(index);
1146
+ } else if let Some(related_address) = c.related_address()
1147
+ && related_address.address == addr.ip().to_string()
1148
+ && related_address.port == addr.port()
1149
+ {
1150
+ return Some(index);
1151
+ }
1152
+ }
1153
+ None
1154
+ }
1155
+
1156
+ pub(crate) fn send_binding_request(
1157
+ &mut self,
1158
+ now: Instant,
1159
+ m: &Message,
1160
+ local_index: usize,
1161
+ remote_index: usize,
1162
+ ) {
1163
+ trace!(
1164
+ "[{}]: ping STUN from {} to {}",
1165
+ self.get_name(),
1166
+ self.local_candidates[local_index],
1167
+ self.remote_candidates[remote_index],
1168
+ );
1169
+
1170
+ // desklink scout trace: every outbound connectivity check / nomination,
1171
+ // with the transaction id, so the relay's wire log can be matched to it.
1172
+ if dl_trace::enabled() {
1173
+ dl_ice!(
1174
+ "{} SEND_REQUEST txn={:?} use_candidate={} local={} remote={} outstanding={}",
1175
+ self.get_name(),
1176
+ m.transaction_id,
1177
+ m.contains(ATTR_USE_CANDIDATE),
1178
+ self.local_candidates[local_index].addr(),
1179
+ self.remote_candidates[remote_index].addr(),
1180
+ self.pending_binding_requests.len(),
1181
+ );
1182
+ }
1183
+
1184
+ self.invalidate_pending_binding_requests(now);
1185
+
1186
+ self.pending_binding_requests.push(BindingRequest {
1187
+ timestamp: now,
1188
+ transaction_id: m.transaction_id,
1189
+ destination: self.remote_candidates[remote_index].addr(),
1190
+ is_use_candidate: m.contains(ATTR_USE_CANDIDATE),
1191
+ });
1192
+
1193
+ // Track request sent on the candidate pair
1194
+ if let Some(pair_index) = self.find_pair(local_index, remote_index) {
1195
+ self.candidate_pairs[pair_index].on_request_sent();
1196
+ }
1197
+
1198
+ self.send_stun(now, m, local_index, remote_index);
1199
+ }
1200
+
1201
+ pub(crate) fn send_binding_success(
1202
+ &mut self,
1203
+ now: Instant,
1204
+ m: &Message,
1205
+ local_index: usize,
1206
+ remote_index: usize,
1207
+ ) {
1208
+ // desklink scout trace: the answer to an inbound connectivity check, and
1209
+ // whether the request it answered carried the nomination.
1210
+ if dl_trace::enabled() {
1211
+ dl_ice!(
1212
+ "{} SEND_SUCCESS role={} txn={:?} use_candidate={}",
1213
+ self.get_name(),
1214
+ if self.is_controlling { "controlling" } else { "controlled" },
1215
+ m.transaction_id,
1216
+ m.contains(ATTR_USE_CANDIDATE),
1217
+ );
1218
+ }
1219
+ let addr = self.remote_candidates[remote_index].addr();
1220
+ let (ip, port) = (addr.ip(), addr.port());
1221
+ let local_pwd = self.ufrag_pwd.local_credentials.pwd.clone();
1222
+
1223
+ let (out, result) = {
1224
+ let mut out = Message::new();
1225
+ let result = out.build(&[
1226
+ Box::new(m.clone()),
1227
+ Box::new(BINDING_SUCCESS),
1228
+ Box::new(XorMappedAddress { ip, port }),
1229
+ Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
1230
+ local_pwd,
1231
+ self.crypto_provider.crypto(),
1232
+ )),
1233
+ Box::new(FINGERPRINT),
1234
+ ]);
1235
+ (out, result)
1236
+ };
1237
+
1238
+ if let Err(err) = result {
1239
+ warn!(
1240
+ "[{}]: Failed to handle inbound ICE from: {} to: {} error: {}",
1241
+ self.get_name(),
1242
+ self.local_candidates[local_index],
1243
+ self.remote_candidates[remote_index],
1244
+ err
1245
+ );
1246
+ } else {
1247
+ // Track response sent on the candidate pair
1248
+ if let Some(pair_index) = self.find_pair(local_index, remote_index) {
1249
+ self.candidate_pairs[pair_index].on_response_sent();
1250
+ }
1251
+ self.send_stun(now, &out, local_index, remote_index);
1252
+ }
1253
+ }
1254
+
1255
+ /// Sends a 487 (Role Conflict) error response.
1256
+ /// RFC 8445 Section 7.3.1.1
1257
+ pub(crate) fn send_role_conflict_error(
1258
+ &mut self,
1259
+ now: Instant,
1260
+ m: &Message,
1261
+ local_index: usize,
1262
+ remote_index: usize,
1263
+ ) {
1264
+ use stun::error_code::*;
1265
+
1266
+ let local_pwd = self.ufrag_pwd.local_credentials.pwd.clone();
1267
+
1268
+ let (out, result) = {
1269
+ let mut out = Message::new();
1270
+ let result = out.build(&[
1271
+ Box::new(m.clone()),
1272
+ Box::new(stun::message::BINDING_ERROR),
1273
+ Box::new(CODE_ROLE_CONFLICT),
1274
+ Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
1275
+ local_pwd,
1276
+ self.crypto_provider.crypto(),
1277
+ )),
1278
+ Box::new(FINGERPRINT),
1279
+ ]);
1280
+ (out, result)
1281
+ };
1282
+
1283
+ if let Err(err) = result {
1284
+ warn!(
1285
+ "[{}]: Failed to send role conflict error from: {} to: {} error: {}",
1286
+ self.get_name(),
1287
+ self.local_candidates[local_index],
1288
+ self.remote_candidates[remote_index],
1289
+ err
1290
+ );
1291
+ } else {
1292
+ debug!(
1293
+ "[{}]: Sent 487 Role Conflict error from {} to {}",
1294
+ self.get_name(),
1295
+ self.local_candidates[local_index],
1296
+ self.remote_candidates[remote_index]
1297
+ );
1298
+ self.send_stun(now, &out, local_index, remote_index);
1299
+ }
1300
+ }
1301
+
1302
+ /// Switches the ICE agent role and recomputes all candidate pair priorities.
1303
+ /// RFC 8445 Section 7.3.1.1
1304
+ pub(crate) fn switch_role(&mut self, now: Instant) {
1305
+ self.is_controlling = !self.is_controlling;
1306
+
1307
+ // Recompute priorities for all candidate pairs
1308
+ // The priority calculation depends on ice_role_controlling
1309
+ for pair in &mut self.candidate_pairs {
1310
+ pair.ice_role_controlling = self.is_controlling;
1311
+ }
1312
+
1313
+ // Clear nominated pair when switching roles
1314
+ self.nominated_pair = None;
1315
+
1316
+ info!(
1317
+ "[{}]: Role switched, recomputed {} candidate pair priorities",
1318
+ self.get_name(),
1319
+ self.candidate_pairs.len()
1320
+ );
1321
+
1322
+ self.event_outs.push_back(TaggedEvent {
1323
+ now,
1324
+ event: Event::RoleChange(self.is_controlling),
1325
+ });
1326
+ }
1327
+
1328
+ /// Removes pending binding requests that are over `maxBindingRequestTimeout` old Let HTO be the
1329
+ /// transaction timeout, which SHOULD be 2*RTT if RTT is known or 500 ms otherwise.
1330
+ ///
1331
+ /// reference: (IETF ref-8445)[https://tools.ietf.org/html/rfc8445#appendix-B.1].
1332
+ pub(crate) fn invalidate_pending_binding_requests(&mut self, filter_time: Instant) {
1333
+ let pending_binding_requests = &mut self.pending_binding_requests;
1334
+ let initial_size = pending_binding_requests.len();
1335
+
1336
+ let mut temp = vec![];
1337
+ for binding_request in pending_binding_requests.drain(..) {
1338
+ if filter_time
1339
+ .checked_duration_since(binding_request.timestamp)
1340
+ .map(|duration| duration < MAX_BINDING_REQUEST_TIMEOUT)
1341
+ .unwrap_or(true)
1342
+ {
1343
+ temp.push(binding_request);
1344
+ }
1345
+ }
1346
+
1347
+ *pending_binding_requests = temp;
1348
+ let bind_requests_remaining = pending_binding_requests.len();
1349
+ let bind_requests_removed = initial_size - bind_requests_remaining;
1350
+ if bind_requests_removed > 0 {
1351
+ trace!(
1352
+ "[{}]: Discarded {} binding requests because they expired, still {} remaining",
1353
+ self.get_name(),
1354
+ bind_requests_removed,
1355
+ bind_requests_remaining,
1356
+ );
1357
+ }
1358
+ }
1359
+
1360
+ /// Assert that the passed `TransactionID` is in our `pendingBindingRequests` and returns the
1361
+ /// destination, If the bindingRequest was valid remove it from our pending cache.
1362
+ pub(crate) fn handle_inbound_binding_success(
1363
+ &mut self,
1364
+ now: Instant,
1365
+ id: TransactionId,
1366
+ ) -> Option<BindingRequest> {
1367
+ self.invalidate_pending_binding_requests(now);
1368
+
1369
+ let pending_binding_requests = &mut self.pending_binding_requests;
1370
+ for i in 0..pending_binding_requests.len() {
1371
+ if pending_binding_requests[i].transaction_id == id {
1372
+ let valid_binding_request = pending_binding_requests.remove(i);
1373
+ return Some(valid_binding_request);
1374
+ }
1375
+ }
1376
+ None
1377
+ }
1378
+
1379
+ /// Processes STUN traffic from a remote candidate.
1380
+ pub(crate) fn handle_inbound(
1381
+ &mut self,
1382
+ now: Instant,
1383
+ m: &mut Message,
1384
+ local_index: usize,
1385
+ remote_addr: SocketAddr,
1386
+ ) -> Result<()> {
1387
+ if m.typ.method != METHOD_BINDING
1388
+ || !(m.typ.class == CLASS_SUCCESS_RESPONSE
1389
+ || m.typ.class == CLASS_REQUEST
1390
+ || m.typ.class == CLASS_INDICATION)
1391
+ {
1392
+ trace!(
1393
+ "[{}]: unhandled STUN from {} to {} class({}) method({})",
1394
+ self.get_name(),
1395
+ remote_addr,
1396
+ self.local_candidates[local_index],
1397
+ m.typ.class,
1398
+ m.typ.method
1399
+ );
1400
+ return Err(Error::ErrUnhandledStunpacket);
1401
+ }
1402
+
1403
+ // RFC 8445 Section 7.3.1.1 - Detecting and Repairing Role Conflicts
1404
+ if self.is_controlling {
1405
+ if m.contains(ATTR_ICE_CONTROLLING) {
1406
+ // Both agents are controlling - role conflict detected
1407
+ let mut remote_controlling = crate::attributes::control::AttrControlling::default();
1408
+ if let Err(err) = remote_controlling.get_from(m) {
1409
+ warn!(
1410
+ "[{}]: Failed to get remote ICE-CONTROLLING attribute: {}",
1411
+ self.get_name(),
1412
+ err
1413
+ );
1414
+ return Err(err);
1415
+ }
1416
+
1417
+ debug!(
1418
+ "[{}]: Role conflict detected (both controlling), local tiebreaker: {}, remote tiebreaker: {}",
1419
+ self.get_name(),
1420
+ self.tie_breaker,
1421
+ remote_controlling.0
1422
+ );
1423
+
1424
+ // Only process if this is a request (not a response)
1425
+ if m.typ.class == CLASS_REQUEST {
1426
+ // Send 487 Role Conflict error
1427
+ if let Some(remote_index) = self.find_remote_candidate(remote_addr) {
1428
+ self.send_role_conflict_error(now, m, local_index, remote_index);
1429
+ }
1430
+
1431
+ // Compare tiebreakers - if ours is smaller, we switch to controlled
1432
+ if self.tie_breaker < remote_controlling.0 {
1433
+ info!(
1434
+ "[{}]: Switching from controlling to controlled due to role conflict (smaller tiebreaker)",
1435
+ self.get_name()
1436
+ );
1437
+ self.switch_role(now);
1438
+ }
1439
+ }
1440
+ // Continue processing the message after handling role conflict
1441
+ } else if m.contains(ATTR_USE_CANDIDATE) {
1442
+ debug!(
1443
+ "[{}]: useCandidate && a.isControlling == true",
1444
+ self.get_name(),
1445
+ );
1446
+ return Err(Error::ErrUnexpectedStunrequestMessage);
1447
+ }
1448
+ } else if m.contains(ATTR_ICE_CONTROLLED) {
1449
+ // Both agents are controlled - role conflict detected
1450
+ let mut remote_controlled = crate::attributes::control::AttrControlled::default();
1451
+ if let Err(err) = remote_controlled.get_from(m) {
1452
+ warn!(
1453
+ "[{}]: Failed to get remote ICE-CONTROLLED attribute: {}",
1454
+ self.get_name(),
1455
+ err
1456
+ );
1457
+ return Err(err);
1458
+ }
1459
+
1460
+ debug!(
1461
+ "[{}]: Role conflict detected (both controlled), local tiebreaker: {}, remote tiebreaker: {}",
1462
+ self.get_name(),
1463
+ self.tie_breaker,
1464
+ remote_controlled.0
1465
+ );
1466
+
1467
+ // Only process if this is a request (not a response)
1468
+ if m.typ.class == CLASS_REQUEST {
1469
+ // Send 487 Role Conflict error
1470
+ if let Some(remote_index) = self.find_remote_candidate(remote_addr) {
1471
+ self.send_role_conflict_error(now, m, local_index, remote_index);
1472
+ }
1473
+
1474
+ // Compare tiebreakers - if ours is larger, we switch to controlling
1475
+ if self.tie_breaker > remote_controlled.0 {
1476
+ info!(
1477
+ "[{}]: Switching from controlled to controlling due to role conflict (larger tiebreaker)",
1478
+ self.get_name()
1479
+ );
1480
+ self.switch_role(now);
1481
+ }
1482
+ }
1483
+ // Continue processing the message after handling role conflict
1484
+ }
1485
+
1486
+ let Some(remote_credentials) = &self.ufrag_pwd.remote_credentials else {
1487
+ debug!(
1488
+ "[{}]: ufrag_pwd.remote_credentials.is_none",
1489
+ self.get_name(),
1490
+ );
1491
+ return Err(Error::ErrPasswordEmpty);
1492
+ };
1493
+
1494
+ let mut remote_candidate_index = self.find_remote_candidate(remote_addr);
1495
+ if m.typ.class == CLASS_SUCCESS_RESPONSE {
1496
+ if let Err(err) = assert_inbound_message_integrity(
1497
+ m,
1498
+ remote_credentials.pwd.as_bytes(),
1499
+ self.crypto_provider.crypto(),
1500
+ ) {
1501
+ warn!(
1502
+ "[{}]: discard message from ({}), {}",
1503
+ self.get_name(),
1504
+ remote_addr,
1505
+ err
1506
+ );
1507
+ return Err(err);
1508
+ }
1509
+
1510
+ if let Some(remote_index) = &remote_candidate_index {
1511
+ self.handle_success_response(now, m, local_index, *remote_index, remote_addr);
1512
+ } else {
1513
+ warn!(
1514
+ "[{}]: discard success message from ({}), no such remote",
1515
+ self.get_name(),
1516
+ remote_addr
1517
+ );
1518
+ return Err(Error::ErrUnhandledStunpacket);
1519
+ }
1520
+ } else if m.typ.class == CLASS_REQUEST {
1521
+ {
1522
+ let username = self.ufrag_pwd.local_credentials.ufrag.clone()
1523
+ + ":"
1524
+ + remote_credentials.ufrag.as_str();
1525
+ if let Err(err) = assert_inbound_username(m, &username) {
1526
+ warn!(
1527
+ "[{}]: discard message from ({}), {}",
1528
+ self.get_name(),
1529
+ remote_addr,
1530
+ err
1531
+ );
1532
+ return Err(err);
1533
+ } else if let Err(err) = assert_inbound_message_integrity(
1534
+ m,
1535
+ self.ufrag_pwd.local_credentials.pwd.as_bytes(),
1536
+ self.crypto_provider.crypto(),
1537
+ ) {
1538
+ warn!(
1539
+ "[{}]: discard message from ({}), {}",
1540
+ self.get_name(),
1541
+ remote_addr,
1542
+ err
1543
+ );
1544
+ return Err(err);
1545
+ }
1546
+ }
1547
+
1548
+ if remote_candidate_index.is_none() {
1549
+ // Use the local candidate's network type for the peer-reflexive candidate
1550
+ let network_type = self.local_candidates[local_index].network_type();
1551
+ let (ip, port) = (remote_addr.ip(), remote_addr.port());
1552
+
1553
+ let prflx_candidate_config = CandidatePeerReflexiveConfig {
1554
+ base_config: CandidateConfig {
1555
+ network: network_type.to_string(),
1556
+ address: ip.to_string(),
1557
+ port,
1558
+ component: self.local_candidates[local_index].component(),
1559
+ ..CandidateConfig::default()
1560
+ },
1561
+ rel_addr: "".to_owned(),
1562
+ rel_port: 0,
1563
+ };
1564
+
1565
+ match prflx_candidate_config.new_candidate_peer_reflexive() {
1566
+ Ok(prflx_candidate) => {
1567
+ if let Ok(added) = self.add_remote_candidate(prflx_candidate)
1568
+ && added
1569
+ {
1570
+ // Look the candidate up by address rather than assuming it
1571
+ // is the last element: `add_remote_candidate` may not have
1572
+ // appended it (e.g. a duplicate), and this stays correct if
1573
+ // the vector is ever mutated underneath us. See issue #88.
1574
+ remote_candidate_index = self.find_remote_candidate(remote_addr);
1575
+ }
1576
+ }
1577
+ Err(err) => {
1578
+ error!(
1579
+ "[{}]: Failed to create new remote prflx candidate ({})",
1580
+ self.get_name(),
1581
+ err
1582
+ );
1583
+ return Err(err);
1584
+ }
1585
+ };
1586
+
1587
+ debug!(
1588
+ "[{}]: adding a new peer-reflexive candidate: {} ",
1589
+ self.get_name(),
1590
+ remote_addr
1591
+ );
1592
+ }
1593
+
1594
+ trace!(
1595
+ "[{}]: inbound STUN (Request) from {} to {}",
1596
+ self.get_name(),
1597
+ remote_addr,
1598
+ self.local_candidates[local_index]
1599
+ );
1600
+
1601
+ if let Some(remote_index) = &remote_candidate_index {
1602
+ self.handle_binding_request(now, m, local_index, *remote_index);
1603
+ }
1604
+ }
1605
+
1606
+ if let Some(remote_index) = remote_candidate_index {
1607
+ self.remote_candidates[remote_index].seen(now, false);
1608
+ }
1609
+
1610
+ Ok(())
1611
+ }
1612
+
1613
+ // Processes non STUN traffic from a remote candidate, and returns true if it is an actual
1614
+ // remote candidate.
1615
+ pub(crate) fn validate_non_stun_traffic(
1616
+ &mut self,
1617
+ now: Instant,
1618
+ remote_addr: SocketAddr,
1619
+ ) -> bool {
1620
+ self.find_remote_candidate(remote_addr)
1621
+ .is_some_and(|remote_index| {
1622
+ self.remote_candidates[remote_index].seen(now, false);
1623
+ true
1624
+ })
1625
+ }
1626
+
1627
+ pub(crate) fn send_stun(
1628
+ &mut self,
1629
+ now: Instant,
1630
+ msg: &Message,
1631
+ local_index: usize,
1632
+ remote_index: usize,
1633
+ ) {
1634
+ let peer_addr = self.remote_candidates[remote_index].addr();
1635
+ // RFC 8445 §6.1.2: checks for a (server/peer-)reflexive candidate must
1636
+ // be sent from its base, the bound local socket the candidate was
1637
+ // derived from; the mapped address is not a local socket.
1638
+ let local_addr = self.local_candidates[local_index].base_addr();
1639
+ let transport_protocol = if self.local_candidates[local_index].network_type().is_tcp() {
1640
+ TransportProtocol::TCP
1641
+ } else {
1642
+ TransportProtocol::UDP
1643
+ };
1644
+
1645
+ self.write_outs.push_back(TaggedBytesMut {
1646
+ now,
1647
+ transport: TransportContext {
1648
+ local_addr,
1649
+ peer_addr,
1650
+ ecn: None,
1651
+ transport_protocol,
1652
+ },
1653
+ message: BytesMut::from(&msg.raw[..]),
1654
+ });
1655
+
1656
+ self.local_candidates[local_index].seen(now, true);
1657
+ }
1658
+
1659
+ fn handle_inbound_candidate_msg(
1660
+ &mut self,
1661
+ local_index: usize,
1662
+ msg: TaggedBytesMut,
1663
+ ) -> Result<()> {
1664
+ if is_stun_message(&msg.message) {
1665
+ let mut m = Message {
1666
+ raw: msg.message.to_vec(),
1667
+ ..Message::default()
1668
+ };
1669
+
1670
+ if let Err(err) = m.decode() {
1671
+ warn!(
1672
+ "[{}]: Failed to handle decode ICE from {} to {}: {}",
1673
+ self.get_name(),
1674
+ msg.transport.local_addr,
1675
+ msg.transport.peer_addr,
1676
+ err
1677
+ );
1678
+ Err(err)
1679
+ } else {
1680
+ self.handle_inbound(msg.now, &mut m, local_index, msg.transport.peer_addr)
1681
+ }
1682
+ } else {
1683
+ if !self.validate_non_stun_traffic(msg.now, msg.transport.peer_addr) {
1684
+ warn!(
1685
+ "[{}]: Discarded message, not a valid remote candidate from {}",
1686
+ self.get_name(),
1687
+ msg.transport.peer_addr,
1688
+ );
1689
+ } else {
1690
+ warn!(
1691
+ "[{}]: non-STUN traffic message from a valid remote candidate from {}",
1692
+ self.get_name(),
1693
+ msg.transport.peer_addr
1694
+ );
1695
+ }
1696
+ Err(Error::ErrNonStunmessage)
1697
+ }
1698
+ }
1699
+
1700
+ pub(crate) fn get_name(&self) -> &str {
1701
+ if self.is_controlling {
1702
+ "controlling"
1703
+ } else {
1704
+ "controlled"
1705
+ }
1706
+ }
1707
+
1708
+ pub(crate) fn get_selected_pair(&self) -> Option<usize> {
1709
+ self.selected_pair
1710
+ }
1711
+
1712
+ pub(crate) fn get_best_available_pair(&self) -> Option<usize> {
1713
+ let mut best_pair_index: Option<usize> = None;
1714
+
1715
+ for (index, p) in self.candidate_pairs.iter().enumerate() {
1716
+ if p.state == CandidatePairState::Failed {
1717
+ continue;
1718
+ }
1719
+
1720
+ if let Some(pair_index) = &mut best_pair_index {
1721
+ let b = &self.candidate_pairs[*pair_index];
1722
+ if b.priority() < p.priority() {
1723
+ *pair_index = index;
1724
+ }
1725
+ } else {
1726
+ best_pair_index = Some(index);
1727
+ }
1728
+ }
1729
+
1730
+ best_pair_index
1731
+ }
1732
+
1733
+ pub(crate) fn get_best_valid_candidate_pair(&self) -> Option<usize> {
1734
+ let mut best_pair_index: Option<usize> = None;
1735
+
1736
+ for (index, p) in self.candidate_pairs.iter().enumerate() {
1737
+ if p.state != CandidatePairState::Succeeded {
1738
+ continue;
1739
+ }
1740
+
1741
+ if let Some(pair_index) = &mut best_pair_index {
1742
+ let b = &self.candidate_pairs[*pair_index];
1743
+ if b.priority() < p.priority() {
1744
+ *pair_index = index;
1745
+ }
1746
+ } else {
1747
+ best_pair_index = Some(index);
1748
+ }
1749
+ }
1750
+
1751
+ best_pair_index
1752
+ }
1753
+ }