@desklink/host 0.5.2 → 0.5.4
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.
- package/README.md +45 -18
- package/bin/desklink-host.mjs +5 -1
- package/dist/bridge.d.ts +7 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +55 -7
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts +9 -2
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +26 -15
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/BOUNDARY.md +4 -1
- package/docs/PROTOCOL.md +20 -5
- package/engine/Cargo.lock +6 -8
- package/engine/Cargo.toml +13 -1
- package/engine/src/clipboard.rs +0 -34
- package/engine/src/convert.rs +0 -170
- package/engine/src/cursor.rs +0 -23
- package/engine/src/input.rs +0 -65
- package/engine/src/input_mac.rs +0 -11
- package/engine/src/keeper.rs +0 -137
- package/engine/src/keymap.rs +0 -107
- package/engine/src/keymap_mac.rs +0 -25
- package/engine/src/mac.rs +0 -28
- package/engine/src/main.rs +3 -2
- package/engine/src/peer.rs +231 -1
- package/engine/src/portal.rs +0 -18
- package/engine/src/protocol.rs +0 -162
- package/engine/src/session.rs +37 -1
- package/engine/src/x11.rs +0 -13
- package/engine/vendor/rtc-ice/Cargo.toml +137 -0
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +59 -0
- package/engine/vendor/rtc-ice/LICENSE-APACHE +201 -0
- package/engine/vendor/rtc-ice/LICENSE-MIT +21 -0
- package/engine/vendor/rtc-ice/README.md +30 -0
- package/engine/vendor/rtc-ice/examples/ping_pong.rs +376 -0
- package/engine/vendor/rtc-ice/src/agent/agent_config.rs +126 -0
- package/engine/vendor/rtc-ice/src/agent/agent_proto.rs +172 -0
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +566 -0
- package/engine/vendor/rtc-ice/src/agent/agent_stats.rs +292 -0
- package/engine/vendor/rtc-ice/src/agent/agent_test.rs +3388 -0
- package/engine/vendor/rtc-ice/src/agent/mod.rs +1662 -0
- package/engine/vendor/rtc-ice/src/attributes/control/control_test.rs +168 -0
- package/engine/vendor/rtc-ice/src/attributes/control/mod.rs +142 -0
- package/engine/vendor/rtc-ice/src/attributes/mod.rs +6 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/mod.rs +37 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/priority_test.rs +39 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/mod.rs +33 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/use_candidate_test.rs +19 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_host.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair.rs +215 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair_test.rs +201 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_peer_reflexive.rs +49 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay_test.rs +114 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive_test.rs +91 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_test.rs +540 -0
- package/engine/vendor/rtc-ice/src/candidate/mod.rs +679 -0
- package/engine/vendor/rtc-ice/src/lib.rs +85 -0
- package/engine/vendor/rtc-ice/src/mdns/mdns_test.rs +150 -0
- package/engine/vendor/rtc-ice/src/mdns/mod.rs +69 -0
- package/engine/vendor/rtc-ice/src/network_type/mod.rs +162 -0
- package/engine/vendor/rtc-ice/src/network_type/network_type_test.rs +95 -0
- package/engine/vendor/rtc-ice/src/rand/mod.rs +91 -0
- package/engine/vendor/rtc-ice/src/rand/rand_test.rs +115 -0
- package/engine/vendor/rtc-ice/src/state/mod.rs +106 -0
- package/engine/vendor/rtc-ice/src/state/state_test.rs +46 -0
- package/engine/vendor/rtc-ice/src/stats/mod.rs +166 -0
- package/engine/vendor/rtc-ice/src/tcp_type/mod.rs +47 -0
- package/engine/vendor/rtc-ice/src/tcp_type/tcp_type_test.rs +18 -0
- package/engine/vendor/rtc-ice/src/url/mod.rs +266 -0
- package/engine/vendor/rtc-ice/src/url/url_test.rs +142 -0
- package/package.json +8 -3
|
@@ -0,0 +1,3388 @@
|
|
|
1
|
+
use std::str::FromStr;
|
|
2
|
+
use std::time::Instant;
|
|
3
|
+
use stun::message::*;
|
|
4
|
+
use stun::textattrs::Username;
|
|
5
|
+
|
|
6
|
+
use super::*;
|
|
7
|
+
use crate::attributes::{
|
|
8
|
+
control::AttrControlling, priority::PriorityAttr, use_candidate::UseCandidateAttr,
|
|
9
|
+
};
|
|
10
|
+
use crate::candidate::candidate_host::*;
|
|
11
|
+
use crate::candidate::candidate_peer_reflexive::*;
|
|
12
|
+
use crate::candidate::candidate_relay::CandidateRelayConfig;
|
|
13
|
+
use crate::candidate::candidate_server_reflexive::*;
|
|
14
|
+
use crate::candidate::*;
|
|
15
|
+
|
|
16
|
+
/// Explicit provider for tests. The default-resolving STUN constructors were removed before 1.0,
|
|
17
|
+
/// so every `MessageIntegrity` now names its provider.
|
|
18
|
+
fn test_crypto_provider() -> std::sync::Arc<dyn crypto::RTCCryptoProvider> {
|
|
19
|
+
crypto::default_provider().expect("a built-in crypto provider must be enabled for tests")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[test]
|
|
23
|
+
fn test_pair_search() -> Result<()> {
|
|
24
|
+
let config = Arc::new(AgentConfig::default());
|
|
25
|
+
let mut a = Agent::new(Instant::now(), config, test_crypto_provider())?;
|
|
26
|
+
|
|
27
|
+
assert!(
|
|
28
|
+
a.candidate_pairs.is_empty(),
|
|
29
|
+
"TestPairSearch is only a valid test if a.validPairs is empty on construction"
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
let cp = a.get_best_available_pair();
|
|
33
|
+
assert!(cp.is_none(), "No Candidate pairs should exist");
|
|
34
|
+
|
|
35
|
+
a.close()?;
|
|
36
|
+
|
|
37
|
+
Ok(())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[test]
|
|
41
|
+
fn test_pair_priority() -> Result<()> {
|
|
42
|
+
let mut a = Agent::new(
|
|
43
|
+
Instant::now(),
|
|
44
|
+
Arc::new(AgentConfig::default()),
|
|
45
|
+
test_crypto_provider(),
|
|
46
|
+
)?;
|
|
47
|
+
|
|
48
|
+
let host_config = CandidateHostConfig {
|
|
49
|
+
base_config: CandidateConfig {
|
|
50
|
+
network: "udp".to_owned(),
|
|
51
|
+
address: "192.168.1.1".to_owned(),
|
|
52
|
+
port: 19216,
|
|
53
|
+
component: 1,
|
|
54
|
+
..Default::default()
|
|
55
|
+
},
|
|
56
|
+
..Default::default()
|
|
57
|
+
};
|
|
58
|
+
let host_local = host_config.new_candidate_host()?;
|
|
59
|
+
a.local_candidates.push(host_local);
|
|
60
|
+
|
|
61
|
+
let relay_config = CandidateRelayConfig {
|
|
62
|
+
base_config: CandidateConfig {
|
|
63
|
+
network: "udp".to_owned(),
|
|
64
|
+
address: "1.2.3.4".to_owned(),
|
|
65
|
+
port: 12340,
|
|
66
|
+
component: 1,
|
|
67
|
+
..Default::default()
|
|
68
|
+
},
|
|
69
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
70
|
+
rel_port: 43210,
|
|
71
|
+
..Default::default()
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let relay_remote = relay_config.new_candidate_relay()?;
|
|
75
|
+
|
|
76
|
+
let srflx_config = CandidateServerReflexiveConfig {
|
|
77
|
+
base_config: CandidateConfig {
|
|
78
|
+
network: "udp".to_owned(),
|
|
79
|
+
address: "10.10.10.2".to_owned(),
|
|
80
|
+
port: 19218,
|
|
81
|
+
component: 1,
|
|
82
|
+
..Default::default()
|
|
83
|
+
},
|
|
84
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
85
|
+
rel_port: 43212,
|
|
86
|
+
..Default::default()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let srflx_remote = srflx_config.new_candidate_server_reflexive()?;
|
|
90
|
+
|
|
91
|
+
let prflx_config = CandidatePeerReflexiveConfig {
|
|
92
|
+
base_config: CandidateConfig {
|
|
93
|
+
network: "udp".to_owned(),
|
|
94
|
+
address: "10.10.10.2".to_owned(),
|
|
95
|
+
port: 19217,
|
|
96
|
+
component: 1,
|
|
97
|
+
..Default::default()
|
|
98
|
+
},
|
|
99
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
100
|
+
rel_port: 43211,
|
|
101
|
+
..Default::default()
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let prflx_remote = prflx_config.new_candidate_peer_reflexive()?;
|
|
105
|
+
|
|
106
|
+
let host_config = CandidateHostConfig {
|
|
107
|
+
base_config: CandidateConfig {
|
|
108
|
+
network: "udp".to_owned(),
|
|
109
|
+
address: "1.2.3.5".to_owned(),
|
|
110
|
+
port: 12350,
|
|
111
|
+
component: 1,
|
|
112
|
+
..Default::default()
|
|
113
|
+
},
|
|
114
|
+
..Default::default()
|
|
115
|
+
};
|
|
116
|
+
let host_remote = host_config.new_candidate_host()?;
|
|
117
|
+
|
|
118
|
+
let remotes: Vec<Candidate> = vec![relay_remote, srflx_remote, prflx_remote, host_remote];
|
|
119
|
+
for remote in remotes {
|
|
120
|
+
a.remote_candidates.push(remote);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
let local = 0;
|
|
125
|
+
for remote in 0..a.remote_candidates.len() {
|
|
126
|
+
if a.find_pair(local, remote).is_none() {
|
|
127
|
+
a.add_pair(local, remote);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if let Some(p) = a.find_pair(local, remote) {
|
|
131
|
+
a.candidate_pairs[p].state = CandidatePairState::Succeeded;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let Some(best_pair) = a.get_best_available_pair() {
|
|
135
|
+
assert_eq!(
|
|
136
|
+
a.candidate_pairs[best_pair].to_string(),
|
|
137
|
+
CandidatePair::new(
|
|
138
|
+
local,
|
|
139
|
+
remote,
|
|
140
|
+
a.local_candidates[local].priority(),
|
|
141
|
+
a.remote_candidates[remote].priority(),
|
|
142
|
+
a.is_controlling,
|
|
143
|
+
)
|
|
144
|
+
.to_string(),
|
|
145
|
+
"Unexpected bestPair {best_pair} (expected remote: {remote})",
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
panic!("expected Some, but got None");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
a.close()?;
|
|
154
|
+
Ok(())
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn pipe(
|
|
158
|
+
default_config0: Option<AgentConfig>,
|
|
159
|
+
default_config1: Option<AgentConfig>,
|
|
160
|
+
) -> Result<(Agent, Agent)> {
|
|
161
|
+
let mut cfg0 = if let Some(cfg) = default_config0 {
|
|
162
|
+
cfg
|
|
163
|
+
} else {
|
|
164
|
+
AgentConfig::default()
|
|
165
|
+
};
|
|
166
|
+
cfg0.urls = vec![];
|
|
167
|
+
|
|
168
|
+
let a_agent = Agent::new(Instant::now(), Arc::new(cfg0), test_crypto_provider())?;
|
|
169
|
+
|
|
170
|
+
let mut cfg1 = if let Some(cfg) = default_config1 {
|
|
171
|
+
cfg
|
|
172
|
+
} else {
|
|
173
|
+
AgentConfig::default()
|
|
174
|
+
};
|
|
175
|
+
cfg1.urls = vec![];
|
|
176
|
+
|
|
177
|
+
let b_agent = Agent::new(Instant::now(), Arc::new(cfg1), test_crypto_provider())?;
|
|
178
|
+
|
|
179
|
+
Ok((a_agent, b_agent))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn test_on_selected_candidate_pair_change() -> Result<()> {
|
|
184
|
+
let mut a = Agent::new(
|
|
185
|
+
Instant::now(),
|
|
186
|
+
Arc::new(AgentConfig::default()),
|
|
187
|
+
test_crypto_provider(),
|
|
188
|
+
)?;
|
|
189
|
+
|
|
190
|
+
let host_config = CandidateHostConfig {
|
|
191
|
+
base_config: CandidateConfig {
|
|
192
|
+
network: "udp".to_owned(),
|
|
193
|
+
address: "192.168.1.1".to_owned(),
|
|
194
|
+
port: 19216,
|
|
195
|
+
component: 1,
|
|
196
|
+
..Default::default()
|
|
197
|
+
},
|
|
198
|
+
..Default::default()
|
|
199
|
+
};
|
|
200
|
+
let host_local = host_config.new_candidate_host()?;
|
|
201
|
+
a.add_local_candidate(host_local)?;
|
|
202
|
+
|
|
203
|
+
let relay_config = CandidateRelayConfig {
|
|
204
|
+
base_config: CandidateConfig {
|
|
205
|
+
network: "udp".to_owned(),
|
|
206
|
+
address: "1.2.3.4".to_owned(),
|
|
207
|
+
port: 12340,
|
|
208
|
+
component: 1,
|
|
209
|
+
..Default::default()
|
|
210
|
+
},
|
|
211
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
212
|
+
rel_port: 43210,
|
|
213
|
+
..Default::default()
|
|
214
|
+
};
|
|
215
|
+
let relay_remote = relay_config.new_candidate_relay()?;
|
|
216
|
+
a.add_remote_candidate(relay_remote)?;
|
|
217
|
+
|
|
218
|
+
// select the pair
|
|
219
|
+
let (local, remote) = (0, 0);
|
|
220
|
+
a.add_pair(local, remote);
|
|
221
|
+
a.set_selected_pair(Some(Instant::now()), Some(0));
|
|
222
|
+
|
|
223
|
+
// ensure that the callback fired on setting the pair
|
|
224
|
+
let mut is_selected_candidate_pair_change_event_fired = false;
|
|
225
|
+
while let Some(event) = a.poll_event() {
|
|
226
|
+
if let Event::SelectedCandidatePairChange(_, _) = event.event {
|
|
227
|
+
is_selected_candidate_pair_change_event_fired = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
assert!(is_selected_candidate_pair_change_event_fired);
|
|
232
|
+
|
|
233
|
+
a.close()?;
|
|
234
|
+
Ok(())
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test]
|
|
238
|
+
fn test_handle_peer_reflexive_udp_pflx_candidate() -> Result<()> {
|
|
239
|
+
let mut a = Agent::new(
|
|
240
|
+
Instant::now(),
|
|
241
|
+
Arc::new(AgentConfig::default()),
|
|
242
|
+
test_crypto_provider(),
|
|
243
|
+
)?;
|
|
244
|
+
|
|
245
|
+
let host_config = CandidateHostConfig {
|
|
246
|
+
base_config: CandidateConfig {
|
|
247
|
+
network: "udp".to_owned(),
|
|
248
|
+
address: "192.168.0.2".to_owned(),
|
|
249
|
+
port: 777,
|
|
250
|
+
component: 1,
|
|
251
|
+
..Default::default()
|
|
252
|
+
},
|
|
253
|
+
..Default::default()
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
let local_candidate = host_config.new_candidate_host()?;
|
|
257
|
+
let local = 0;
|
|
258
|
+
let local_priority = local_candidate.priority();
|
|
259
|
+
let local_network_type = local_candidate.network_type();
|
|
260
|
+
a.add_local_candidate(local_candidate)?;
|
|
261
|
+
|
|
262
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:999")?;
|
|
263
|
+
|
|
264
|
+
let (username, local_pwd, tie_breaker) = {
|
|
265
|
+
a.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
266
|
+
ufrag: "".to_string(),
|
|
267
|
+
pwd: "".to_string(),
|
|
268
|
+
});
|
|
269
|
+
(
|
|
270
|
+
a.ufrag_pwd.local_credentials.ufrag.to_owned() + ":",
|
|
271
|
+
a.ufrag_pwd.local_credentials.pwd.clone(),
|
|
272
|
+
a.tie_breaker,
|
|
273
|
+
)
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
let mut msg = Message::new();
|
|
277
|
+
msg.build(&[
|
|
278
|
+
Box::new(BINDING_REQUEST),
|
|
279
|
+
Box::new(TransactionId::new()),
|
|
280
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
281
|
+
Box::new(UseCandidateAttr::new()),
|
|
282
|
+
Box::new(AttrControlling(tie_breaker)),
|
|
283
|
+
Box::new(PriorityAttr(local_priority)),
|
|
284
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
285
|
+
local_pwd,
|
|
286
|
+
test_crypto_provider().crypto(),
|
|
287
|
+
)),
|
|
288
|
+
Box::new(FINGERPRINT),
|
|
289
|
+
])?;
|
|
290
|
+
|
|
291
|
+
{
|
|
292
|
+
a.handle_inbound(Instant::now(), &mut msg, local, remote_addr)?;
|
|
293
|
+
|
|
294
|
+
// length of remote candidate list must be one now
|
|
295
|
+
assert_eq!(
|
|
296
|
+
a.remote_candidates.len(),
|
|
297
|
+
1,
|
|
298
|
+
"failed to add a network type to the remote candidate list"
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// length of remote candidate list for a network type must be 1
|
|
302
|
+
if let Some(remote_index) = a.find_remote_candidate(remote_addr) {
|
|
303
|
+
let c = &a.remote_candidates[remote_index];
|
|
304
|
+
|
|
305
|
+
assert_eq!(
|
|
306
|
+
c.candidate_type(),
|
|
307
|
+
CandidateType::PeerReflexive,
|
|
308
|
+
"candidate type must be prflx"
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
assert_eq!(c.address(), "172.17.0.3", "IP address mismatch");
|
|
312
|
+
|
|
313
|
+
assert_eq!(c.port(), 999, "Port number mismatch");
|
|
314
|
+
} else {
|
|
315
|
+
assert!(
|
|
316
|
+
false,
|
|
317
|
+
"expected non-empty remote candidate for network type {}",
|
|
318
|
+
local_network_type,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
a.close()?;
|
|
324
|
+
Ok(())
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[test]
|
|
328
|
+
fn test_handle_peer_reflexive_unknown_remote() -> Result<()> {
|
|
329
|
+
let mut a = Agent::new(
|
|
330
|
+
Instant::now(),
|
|
331
|
+
Arc::new(AgentConfig::default()),
|
|
332
|
+
test_crypto_provider(),
|
|
333
|
+
)?;
|
|
334
|
+
|
|
335
|
+
let mut tid = TransactionId::default();
|
|
336
|
+
tid.0[..3].copy_from_slice("ABC".as_bytes());
|
|
337
|
+
|
|
338
|
+
let remote_pwd = {
|
|
339
|
+
a.pending_binding_requests = vec![BindingRequest {
|
|
340
|
+
timestamp: Instant::now(),
|
|
341
|
+
transaction_id: tid,
|
|
342
|
+
destination: SocketAddr::from_str("0.0.0.0:0")?,
|
|
343
|
+
is_use_candidate: false,
|
|
344
|
+
}];
|
|
345
|
+
a.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
346
|
+
ufrag: "".to_string(),
|
|
347
|
+
pwd: "".to_string(),
|
|
348
|
+
});
|
|
349
|
+
"".to_string()
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
let host_config = CandidateHostConfig {
|
|
353
|
+
base_config: CandidateConfig {
|
|
354
|
+
network: "udp".to_owned(),
|
|
355
|
+
address: "192.168.0.2".to_owned(),
|
|
356
|
+
port: 777,
|
|
357
|
+
component: 1,
|
|
358
|
+
..Default::default()
|
|
359
|
+
},
|
|
360
|
+
..Default::default()
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
let local = host_config.new_candidate_host()?;
|
|
364
|
+
let local_index = 0;
|
|
365
|
+
a.add_local_candidate(local)?;
|
|
366
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:999")?;
|
|
367
|
+
|
|
368
|
+
let mut msg = Message::new();
|
|
369
|
+
msg.build(&[
|
|
370
|
+
Box::new(BINDING_SUCCESS),
|
|
371
|
+
Box::new(tid),
|
|
372
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
373
|
+
remote_pwd,
|
|
374
|
+
test_crypto_provider().crypto(),
|
|
375
|
+
)),
|
|
376
|
+
Box::new(FINGERPRINT),
|
|
377
|
+
])?;
|
|
378
|
+
|
|
379
|
+
let result = a.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr);
|
|
380
|
+
assert!(result.is_err());
|
|
381
|
+
|
|
382
|
+
assert_eq!(
|
|
383
|
+
a.remote_candidates.len(),
|
|
384
|
+
0,
|
|
385
|
+
"unknown remote was able to create a candidate"
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
a.close()?;
|
|
389
|
+
Ok(())
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* TODO:
|
|
393
|
+
fn gather_and_exchange_candidates(a_agent: &mut Agent, b_agent: &mut Agent) -> Result<()> {
|
|
394
|
+
let wg = WaitGroup::new();
|
|
395
|
+
|
|
396
|
+
let w1 = Arc::new(Mutex::new(Some(wg.worker())));
|
|
397
|
+
a_agent.on_candidate(Box::new(
|
|
398
|
+
move |candidate: Option<Arc<dyn Candidate + Send + Sync>>| {
|
|
399
|
+
let w3 = Arc::clone(&w1);
|
|
400
|
+
Box::pin(async move {
|
|
401
|
+
if candidate.is_none() {
|
|
402
|
+
let mut w = w3.lock().await;
|
|
403
|
+
w.take();
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
},
|
|
407
|
+
));
|
|
408
|
+
a_agent.gather_candidates()?;
|
|
409
|
+
|
|
410
|
+
let w2 = Arc::new(Mutex::new(Some(wg.worker())));
|
|
411
|
+
b_agent.on_candidate(Box::new(
|
|
412
|
+
move |candidate: Option<Arc<dyn Candidate + Send + Sync>>| {
|
|
413
|
+
let w3 = Arc::clone(&w2);
|
|
414
|
+
Box::pin(async move {
|
|
415
|
+
if candidate.is_none() {
|
|
416
|
+
let mut w = w3.lock().await;
|
|
417
|
+
w.take();
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
},
|
|
421
|
+
));
|
|
422
|
+
b_agent.gather_candidates()?;
|
|
423
|
+
|
|
424
|
+
wg.wait().await;
|
|
425
|
+
|
|
426
|
+
let candidates = a_agent.get_local_candidates().await?;
|
|
427
|
+
for c in candidates {
|
|
428
|
+
let c2: Arc<dyn Candidate + Send + Sync> =
|
|
429
|
+
Arc::new(unmarshal_candidate(c.marshal().as_str())?);
|
|
430
|
+
b_agent.add_remote_candidate(&c2)?;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let candidates = b_agent.get_local_candidates().await?;
|
|
434
|
+
for c in candidates {
|
|
435
|
+
let c2: Arc<dyn Candidate + Send + Sync> =
|
|
436
|
+
Arc::new(unmarshal_candidate(c.marshal().as_str())?);
|
|
437
|
+
a_agent.add_remote_candidate(&c2)?;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
Ok(())
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Assert that Agent on startup sends message, and doesn't wait for connectivityTicker to fire
|
|
444
|
+
#[test]
|
|
445
|
+
fn test_connectivity_on_startup() -> Result<()> {
|
|
446
|
+
let keepalive_interval = Some(Duration::from_secs(3600)); //time.Hour
|
|
447
|
+
let check_interval = Duration::from_secs(3600); //time.Hour
|
|
448
|
+
let cfg0 = AgentConfig {
|
|
449
|
+
keepalive_interval,
|
|
450
|
+
check_interval,
|
|
451
|
+
..Default::default()
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
let mut a_agent = Agent::new(Instant::now(), cfg0, test_crypto_provider())?;
|
|
455
|
+
|
|
456
|
+
let cfg1 = AgentConfig {
|
|
457
|
+
keepalive_interval,
|
|
458
|
+
check_interval,
|
|
459
|
+
..Default::default()
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
let mut b_agent = Agent::new(Instant::now(), cfg1, test_crypto_provider())?;
|
|
463
|
+
|
|
464
|
+
// Manual signaling
|
|
465
|
+
let (a_ufrag, a_pwd) = a_agent.get_local_user_credentials();
|
|
466
|
+
let (b_ufrag, b_pwd) = b_agent.get_local_user_credentials();
|
|
467
|
+
|
|
468
|
+
gather_and_exchange_candidates(&mut a_agent, &mut b_agent)?;
|
|
469
|
+
|
|
470
|
+
a_agent.on_connection_state_change(Box::new(move |s: ConnectionState| {
|
|
471
|
+
let accepted_tx_clone = Arc::clone(&accepting_tx);
|
|
472
|
+
Box::pin(async move {
|
|
473
|
+
if s == ConnectionState::Checking {
|
|
474
|
+
let mut tx = accepted_tx_clone.lock();
|
|
475
|
+
tx.take();
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
}));
|
|
479
|
+
|
|
480
|
+
tokio::spawn(async move {
|
|
481
|
+
let result = a_agent.accept(a_cancel_rx, b_ufrag, b_pwd);
|
|
482
|
+
assert!(result.is_ok(), "agent accept expected OK");
|
|
483
|
+
drop(accepted_tx);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
let _ = accepting_rx.recv();
|
|
487
|
+
|
|
488
|
+
let _ = b_agent.dial(b_cancel_rx, a_ufrag, a_pwd)?;
|
|
489
|
+
|
|
490
|
+
// Ensure accepted
|
|
491
|
+
let _ = accepted_rx.recv();
|
|
492
|
+
|
|
493
|
+
Ok(())
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
#[test]
|
|
498
|
+
fn test_connectivity_lite() -> Result<()> {
|
|
499
|
+
let stun_server_url = Url {
|
|
500
|
+
scheme: SchemeType::Stun,
|
|
501
|
+
host: "1.2.3.4".to_owned(),
|
|
502
|
+
port: 3478,
|
|
503
|
+
proto: ProtoType::Udp,
|
|
504
|
+
..Default::default()
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
let nat_type = nat::NatType {
|
|
508
|
+
mapping_behavior: nat::EndpointDependencyType::EndpointIndependent,
|
|
509
|
+
filtering_behavior: nat::EndpointDependencyType::EndpointIndependent,
|
|
510
|
+
..Default::default()
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
let v = build_vnet(nat_type, nat_type)?;
|
|
514
|
+
|
|
515
|
+
let (a_notifier, mut a_connected) = on_connected();
|
|
516
|
+
let (b_notifier, mut b_connected) = on_connected();
|
|
517
|
+
|
|
518
|
+
let cfg0 = AgentConfig {
|
|
519
|
+
urls: vec![stun_server_url],
|
|
520
|
+
network_types: supported_network_types(),
|
|
521
|
+
net: Some(Arc::clone(&v.net0)),
|
|
522
|
+
..Default::default()
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
526
|
+
a_agent.on_connection_state_change(a_notifier);
|
|
527
|
+
|
|
528
|
+
let cfg1 = AgentConfig {
|
|
529
|
+
urls: vec![],
|
|
530
|
+
lite: true,
|
|
531
|
+
candidate_types: vec![CandidateType::Host],
|
|
532
|
+
network_types: supported_network_types(),
|
|
533
|
+
net: Some(Arc::clone(&v.net1)),
|
|
534
|
+
..Default::default()
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
538
|
+
b_agent.on_connection_state_change(b_notifier);
|
|
539
|
+
|
|
540
|
+
let _ = connect_with_vnet(&a_agent, &b_agent)?;
|
|
541
|
+
|
|
542
|
+
// Ensure pair selected
|
|
543
|
+
// Note: this assumes ConnectionStateConnected is thrown after selecting the final pair
|
|
544
|
+
let _ = a_connected.recv();
|
|
545
|
+
let _ = b_connected.recv();
|
|
546
|
+
|
|
547
|
+
v.close()?;
|
|
548
|
+
|
|
549
|
+
Ok(())
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
struct MockPacketConn;
|
|
553
|
+
|
|
554
|
+
#[async_trait]
|
|
555
|
+
impl Conn for MockPacketConn {
|
|
556
|
+
fn connect(&self, _addr: SocketAddr) -> std::result::Result<(), util::Error> {
|
|
557
|
+
Ok(())
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
fn recv(&self, _buf: &mut [u8]) -> std::result::Result<usize, util::Error> {
|
|
561
|
+
Ok(0)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
fn recv_from(&self, _buf: &mut [u8]) -> std::result::Result<(usize, SocketAddr), util::Error> {
|
|
565
|
+
Ok((0, SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0)))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
fn send(&self, _buf: &[u8]) -> std::result::Result<usize, util::Error> {
|
|
569
|
+
Ok(0)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> std::result::Result<usize, util::Error> {
|
|
573
|
+
Ok(0)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
fn local_addr(&self) -> std::result::Result<SocketAddr, util::Error> {
|
|
577
|
+
Ok(SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0))
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
fn remote_addr(&self) -> Option<SocketAddr> {
|
|
581
|
+
None
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
fn close(&self) -> std::result::Result<(), util::Error> {
|
|
585
|
+
Ok(())
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
fn build_msg(c: MessageClass, username: String, key: String) -> Result<Message> {
|
|
590
|
+
let mut msg = Message::new();
|
|
591
|
+
msg.build(&[
|
|
592
|
+
Box::new(MessageType::new(METHOD_BINDING, c)),
|
|
593
|
+
Box::new(TransactionId::new()),
|
|
594
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
595
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(key, test_crypto_provider().crypto())),
|
|
596
|
+
Box::new(FINGERPRINT),
|
|
597
|
+
])?;
|
|
598
|
+
Ok(msg)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
#[test]
|
|
602
|
+
fn test_inbound_validity() -> Result<()> {
|
|
603
|
+
/*env_logger::Builder::new()
|
|
604
|
+
.format(|buf, record| {
|
|
605
|
+
writeln!(
|
|
606
|
+
buf,
|
|
607
|
+
"{}:{} [{}] {} - {}",
|
|
608
|
+
record.file().unwrap_or("unknown"),
|
|
609
|
+
record.line().unwrap_or(0),
|
|
610
|
+
record.level(),
|
|
611
|
+
chrono::Local::now().format("%H:%M:%S.%6f"),
|
|
612
|
+
record.args()
|
|
613
|
+
)
|
|
614
|
+
})
|
|
615
|
+
.filter(None, LevelFilter::Trace)
|
|
616
|
+
.init();*/
|
|
617
|
+
|
|
618
|
+
let remote = SocketAddr::from_str("172.17.0.3:999")?;
|
|
619
|
+
let local: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
620
|
+
CandidateHostConfig {
|
|
621
|
+
base_config: CandidateConfig {
|
|
622
|
+
network: "udp".to_owned(),
|
|
623
|
+
address: "192.168.0.2".to_owned(),
|
|
624
|
+
port: 777,
|
|
625
|
+
component: 1,
|
|
626
|
+
conn: Some(Arc::new(MockPacketConn {})),
|
|
627
|
+
..Default::default()
|
|
628
|
+
},
|
|
629
|
+
..Default::default()
|
|
630
|
+
}
|
|
631
|
+
.new_candidate_host()?,
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
//"Invalid Binding requests should be discarded"
|
|
635
|
+
{
|
|
636
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
637
|
+
|
|
638
|
+
{
|
|
639
|
+
let local_pwd = {
|
|
640
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
641
|
+
ufrag_pwd.local_pwd.clone()
|
|
642
|
+
};
|
|
643
|
+
a.internal.handle_inbound(
|
|
644
|
+
&mut build_msg(CLASS_REQUEST, "invalid".to_owned(), local_pwd)?,
|
|
645
|
+
&local,
|
|
646
|
+
remote,
|
|
647
|
+
);
|
|
648
|
+
{
|
|
649
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
650
|
+
assert_ne!(
|
|
651
|
+
remote_candidates.len(),
|
|
652
|
+
1,
|
|
653
|
+
"Binding with invalid Username was able to create prflx candidate"
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
let username = {
|
|
658
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
659
|
+
format!("{}:{}", ufrag_pwd.local_ufrag, ufrag_pwd.remote_ufrag)
|
|
660
|
+
};
|
|
661
|
+
a.internal.handle_inbound(
|
|
662
|
+
&mut build_msg(CLASS_REQUEST, username, "Invalid".to_owned())?,
|
|
663
|
+
&local,
|
|
664
|
+
remote,
|
|
665
|
+
);
|
|
666
|
+
{
|
|
667
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
668
|
+
assert_ne!(
|
|
669
|
+
remote_candidates.len(),
|
|
670
|
+
1,
|
|
671
|
+
"Binding with invalid MessageIntegrity was able to create prflx candidate"
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
a.close()?;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
//"Invalid Binding success responses should be discarded"
|
|
680
|
+
{
|
|
681
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
682
|
+
|
|
683
|
+
{
|
|
684
|
+
let username = {
|
|
685
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
686
|
+
format!("{}:{}", ufrag_pwd.local_ufrag, ufrag_pwd.remote_ufrag)
|
|
687
|
+
};
|
|
688
|
+
a.internal.handle_inbound(
|
|
689
|
+
&mut build_msg(CLASS_SUCCESS_RESPONSE, username, "Invalid".to_owned())?,
|
|
690
|
+
&local,
|
|
691
|
+
remote,
|
|
692
|
+
);
|
|
693
|
+
{
|
|
694
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
695
|
+
assert_ne!(
|
|
696
|
+
remote_candidates.len(),
|
|
697
|
+
1,
|
|
698
|
+
"Binding with invalid Username was able to create prflx candidate"
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
a.close()?;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
//"Discard non-binding messages"
|
|
707
|
+
{
|
|
708
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
709
|
+
|
|
710
|
+
{
|
|
711
|
+
let username = {
|
|
712
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
713
|
+
format!("{}:{}", ufrag_pwd.local_ufrag, ufrag_pwd.remote_ufrag)
|
|
714
|
+
};
|
|
715
|
+
a.internal.handle_inbound(
|
|
716
|
+
&mut build_msg(CLASS_ERROR_RESPONSE, username, "Invalid".to_owned())?,
|
|
717
|
+
&local,
|
|
718
|
+
remote,
|
|
719
|
+
);
|
|
720
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
721
|
+
assert_ne!(
|
|
722
|
+
remote_candidates.len(),
|
|
723
|
+
1,
|
|
724
|
+
"non-binding message was able to create prflxRemote"
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
a.close()?;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
//"Valid bind request"
|
|
732
|
+
{
|
|
733
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
734
|
+
|
|
735
|
+
{
|
|
736
|
+
let (username, local_pwd) = {
|
|
737
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
738
|
+
(
|
|
739
|
+
format!("{}:{}", ufrag_pwd.local_ufrag, ufrag_pwd.remote_ufrag),
|
|
740
|
+
ufrag_pwd.local_pwd.clone(),
|
|
741
|
+
)
|
|
742
|
+
};
|
|
743
|
+
a.internal.handle_inbound(
|
|
744
|
+
&mut build_msg(CLASS_REQUEST, username, local_pwd)?,
|
|
745
|
+
&local,
|
|
746
|
+
remote,
|
|
747
|
+
);
|
|
748
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
749
|
+
assert_eq!(
|
|
750
|
+
remote_candidates.len(),
|
|
751
|
+
1,
|
|
752
|
+
"Binding with valid values was unable to create prflx candidate"
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
a.close()?;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
//"Valid bind without fingerprint"
|
|
760
|
+
{
|
|
761
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
762
|
+
|
|
763
|
+
{
|
|
764
|
+
let (username, local_pwd) = {
|
|
765
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
766
|
+
(
|
|
767
|
+
format!("{}:{}", ufrag_pwd.local_ufrag, ufrag_pwd.remote_ufrag),
|
|
768
|
+
ufrag_pwd.local_pwd.clone(),
|
|
769
|
+
)
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
let mut msg = Message::new();
|
|
773
|
+
msg.build(&[
|
|
774
|
+
Box::new(BINDING_REQUEST),
|
|
775
|
+
Box::new(TransactionId::new()),
|
|
776
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
777
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(local_pwd, test_crypto_provider())),
|
|
778
|
+
])?;
|
|
779
|
+
|
|
780
|
+
a.internal.handle_inbound(&mut msg, &local, remote);
|
|
781
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
782
|
+
assert_eq!(
|
|
783
|
+
remote_candidates.len(),
|
|
784
|
+
1,
|
|
785
|
+
"Binding with valid values (but no fingerprint) was unable to create prflx candidate"
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
a.close()?;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
//"Success with invalid TransactionID"
|
|
793
|
+
{
|
|
794
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
795
|
+
|
|
796
|
+
{
|
|
797
|
+
let remote = SocketAddr::from_str("172.17.0.3:999")?;
|
|
798
|
+
|
|
799
|
+
let mut t_id = TransactionId::default();
|
|
800
|
+
t_id.0[..3].copy_from_slice(b"ABC");
|
|
801
|
+
|
|
802
|
+
let remote_pwd = {
|
|
803
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
804
|
+
ufrag_pwd.remote_pwd.clone()
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
let mut msg = Message::new();
|
|
808
|
+
msg.build(&[
|
|
809
|
+
Box::new(BINDING_SUCCESS),
|
|
810
|
+
Box::new(t_id),
|
|
811
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(remote_pwd, test_crypto_provider())),
|
|
812
|
+
Box::new(FINGERPRINT),
|
|
813
|
+
])?;
|
|
814
|
+
|
|
815
|
+
a.internal.handle_inbound(&mut msg, &local, remote);
|
|
816
|
+
|
|
817
|
+
{
|
|
818
|
+
let remote_candidates = a.internal.remote_candidates.lock();
|
|
819
|
+
assert_eq!(
|
|
820
|
+
remote_candidates.len(),
|
|
821
|
+
0,
|
|
822
|
+
"unknown remote was able to create a candidate"
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
a.close()?;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
Ok(())
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
#[test]
|
|
834
|
+
fn test_invalid_agent_starts() -> Result<()> {
|
|
835
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
836
|
+
|
|
837
|
+
let (_cancel_tx1, cancel_rx1) = mpsc::channel(1);
|
|
838
|
+
let result = a.dial(cancel_rx1, "".to_owned(), "bar".to_owned());
|
|
839
|
+
assert!(result.is_err());
|
|
840
|
+
if let Err(err) = result {
|
|
841
|
+
assert_eq!(Error::ErrRemoteUfragEmpty, err);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
let (_cancel_tx2, cancel_rx2) = mpsc::channel(1);
|
|
845
|
+
let result = a.dial(cancel_rx2, "foo".to_owned(), "".to_owned());
|
|
846
|
+
assert!(result.is_err());
|
|
847
|
+
if let Err(err) = result {
|
|
848
|
+
assert_eq!(Error::ErrRemotePwdEmpty, err);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
let (cancel_tx3, cancel_rx3) = mpsc::channel(1);
|
|
852
|
+
tokio::spawn(async move {
|
|
853
|
+
tokio::time::sleep(Duration::from_millis(100));
|
|
854
|
+
drop(cancel_tx3);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
let result = a.dial(cancel_rx3, "foo".to_owned(), "bar".to_owned());
|
|
858
|
+
assert!(result.is_err());
|
|
859
|
+
if let Err(err) = result {
|
|
860
|
+
assert_eq!(Error::ErrCanceledByCaller, err);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
let (_cancel_tx4, cancel_rx4) = mpsc::channel(1);
|
|
864
|
+
let result = a.dial(cancel_rx4, "foo".to_owned(), "bar".to_owned());
|
|
865
|
+
assert!(result.is_err());
|
|
866
|
+
if let Err(err) = result {
|
|
867
|
+
assert_eq!(Error::ErrMultipleStart, err);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
a.close()?;
|
|
871
|
+
|
|
872
|
+
Ok(())
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
//use std::io::Write;
|
|
876
|
+
|
|
877
|
+
// Assert that Agent emits Connecting/Connected/Disconnected/Failed/Closed messages
|
|
878
|
+
#[test]
|
|
879
|
+
fn test_connection_state_callback() -> Result<()> {
|
|
880
|
+
/*env_logger::Builder::new()
|
|
881
|
+
.format(|buf, record| {
|
|
882
|
+
writeln!(
|
|
883
|
+
buf,
|
|
884
|
+
"{}:{} [{}] {} - {}",
|
|
885
|
+
record.file().unwrap_or("unknown"),
|
|
886
|
+
record.line().unwrap_or(0),
|
|
887
|
+
record.level(),
|
|
888
|
+
chrono::Local::now().format("%H:%M:%S.%6f"),
|
|
889
|
+
record.args()
|
|
890
|
+
)
|
|
891
|
+
})
|
|
892
|
+
.filter(None, LevelFilter::Trace)
|
|
893
|
+
.init();*/
|
|
894
|
+
|
|
895
|
+
let disconnected_duration = Duration::from_secs(1);
|
|
896
|
+
let failed_duration = Duration::from_secs(1);
|
|
897
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
898
|
+
|
|
899
|
+
let cfg0 = AgentConfig {
|
|
900
|
+
urls: vec![],
|
|
901
|
+
network_types: supported_network_types(),
|
|
902
|
+
disconnected_timeout: Some(disconnected_duration),
|
|
903
|
+
failed_timeout: Some(failed_duration),
|
|
904
|
+
keepalive_interval: Some(keepalive_interval),
|
|
905
|
+
..Default::default()
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
let cfg1 = AgentConfig {
|
|
909
|
+
urls: vec![],
|
|
910
|
+
network_types: supported_network_types(),
|
|
911
|
+
disconnected_timeout: Some(disconnected_duration),
|
|
912
|
+
failed_timeout: Some(failed_duration),
|
|
913
|
+
keepalive_interval: Some(keepalive_interval),
|
|
914
|
+
..Default::default()
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
918
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
919
|
+
|
|
920
|
+
let (is_checking_tx, mut is_checking_rx) = mpsc::channel::<()>(1);
|
|
921
|
+
let (is_connected_tx, mut is_connected_rx) = mpsc::channel::<()>(1);
|
|
922
|
+
let (is_disconnected_tx, mut is_disconnected_rx) = mpsc::channel::<()>(1);
|
|
923
|
+
let (is_failed_tx, mut is_failed_rx) = mpsc::channel::<()>(1);
|
|
924
|
+
let (is_closed_tx, mut is_closed_rx) = mpsc::channel::<()>(1);
|
|
925
|
+
|
|
926
|
+
let is_checking_tx = Arc::new(Mutex::new(Some(is_checking_tx)));
|
|
927
|
+
let is_connected_tx = Arc::new(Mutex::new(Some(is_connected_tx)));
|
|
928
|
+
let is_disconnected_tx = Arc::new(Mutex::new(Some(is_disconnected_tx)));
|
|
929
|
+
let is_failed_tx = Arc::new(Mutex::new(Some(is_failed_tx)));
|
|
930
|
+
let is_closed_tx = Arc::new(Mutex::new(Some(is_closed_tx)));
|
|
931
|
+
|
|
932
|
+
a_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
933
|
+
let is_checking_tx_clone = Arc::clone(&is_checking_tx);
|
|
934
|
+
let is_connected_tx_clone = Arc::clone(&is_connected_tx);
|
|
935
|
+
let is_disconnected_tx_clone = Arc::clone(&is_disconnected_tx);
|
|
936
|
+
let is_failed_tx_clone = Arc::clone(&is_failed_tx);
|
|
937
|
+
let is_closed_tx_clone = Arc::clone(&is_closed_tx);
|
|
938
|
+
Box::pin(async move {
|
|
939
|
+
match c {
|
|
940
|
+
ConnectionState::Checking => {
|
|
941
|
+
debug!("drop is_checking_tx");
|
|
942
|
+
let mut tx = is_checking_tx_clone.lock();
|
|
943
|
+
tx.take();
|
|
944
|
+
}
|
|
945
|
+
ConnectionState::Connected => {
|
|
946
|
+
debug!("drop is_connected_tx");
|
|
947
|
+
let mut tx = is_connected_tx_clone.lock();
|
|
948
|
+
tx.take();
|
|
949
|
+
}
|
|
950
|
+
ConnectionState::Disconnected => {
|
|
951
|
+
debug!("drop is_disconnected_tx");
|
|
952
|
+
let mut tx = is_disconnected_tx_clone.lock();
|
|
953
|
+
tx.take();
|
|
954
|
+
}
|
|
955
|
+
ConnectionState::Failed => {
|
|
956
|
+
debug!("drop is_failed_tx");
|
|
957
|
+
let mut tx = is_failed_tx_clone.lock();
|
|
958
|
+
tx.take();
|
|
959
|
+
}
|
|
960
|
+
ConnectionState::Closed => {
|
|
961
|
+
debug!("drop is_closed_tx");
|
|
962
|
+
let mut tx = is_closed_tx_clone.lock();
|
|
963
|
+
tx.take();
|
|
964
|
+
}
|
|
965
|
+
_ => {}
|
|
966
|
+
};
|
|
967
|
+
})
|
|
968
|
+
}));
|
|
969
|
+
|
|
970
|
+
connect_with_vnet(&a_agent, &b_agent)?;
|
|
971
|
+
|
|
972
|
+
debug!("wait is_checking_tx");
|
|
973
|
+
let _ = is_checking_rx.recv();
|
|
974
|
+
debug!("wait is_connected_rx");
|
|
975
|
+
let _ = is_connected_rx.recv();
|
|
976
|
+
debug!("wait is_disconnected_rx");
|
|
977
|
+
let _ = is_disconnected_rx.recv();
|
|
978
|
+
debug!("wait is_failed_rx");
|
|
979
|
+
let _ = is_failed_rx.recv();
|
|
980
|
+
|
|
981
|
+
a_agent.close()?;
|
|
982
|
+
b_agent.close()?;
|
|
983
|
+
|
|
984
|
+
debug!("wait is_closed_rx");
|
|
985
|
+
let _ = is_closed_rx.recv();
|
|
986
|
+
|
|
987
|
+
Ok(())
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
#[test]
|
|
991
|
+
fn test_invalid_gather() -> Result<()> {
|
|
992
|
+
//"Gather with no OnCandidate should error"
|
|
993
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
994
|
+
|
|
995
|
+
if let Err(err) = a.gather_candidates() {
|
|
996
|
+
assert_eq!(
|
|
997
|
+
Error::ErrNoOnCandidateHandler,
|
|
998
|
+
err,
|
|
999
|
+
"trickle GatherCandidates succeeded without OnCandidate"
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
a.close()?;
|
|
1004
|
+
|
|
1005
|
+
Ok(())
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
#[test]
|
|
1009
|
+
fn test_candidate_pair_stats() -> Result<()> {
|
|
1010
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1011
|
+
|
|
1012
|
+
let host_local: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1013
|
+
CandidateHostConfig {
|
|
1014
|
+
base_config: CandidateConfig {
|
|
1015
|
+
network: "udp".to_owned(),
|
|
1016
|
+
address: "192.168.1.1".to_owned(),
|
|
1017
|
+
port: 19216,
|
|
1018
|
+
component: 1,
|
|
1019
|
+
..Default::default()
|
|
1020
|
+
},
|
|
1021
|
+
..Default::default()
|
|
1022
|
+
}
|
|
1023
|
+
.new_candidate_host()?,
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
let relay_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1027
|
+
CandidateRelayConfig {
|
|
1028
|
+
base_config: CandidateConfig {
|
|
1029
|
+
network: "udp".to_owned(),
|
|
1030
|
+
address: "1.2.3.4".to_owned(),
|
|
1031
|
+
port: 2340,
|
|
1032
|
+
component: 1,
|
|
1033
|
+
..Default::default()
|
|
1034
|
+
},
|
|
1035
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1036
|
+
rel_port: 43210,
|
|
1037
|
+
..Default::default()
|
|
1038
|
+
}
|
|
1039
|
+
.new_candidate_relay()?,
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
let srflx_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1043
|
+
CandidateServerReflexiveConfig {
|
|
1044
|
+
base_config: CandidateConfig {
|
|
1045
|
+
network: "udp".to_owned(),
|
|
1046
|
+
address: "10.10.10.2".to_owned(),
|
|
1047
|
+
port: 19218,
|
|
1048
|
+
component: 1,
|
|
1049
|
+
..Default::default()
|
|
1050
|
+
},
|
|
1051
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1052
|
+
rel_port: 43212,
|
|
1053
|
+
}
|
|
1054
|
+
.new_candidate_server_reflexive()?,
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
let prflx_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1058
|
+
CandidatePeerReflexiveConfig {
|
|
1059
|
+
base_config: CandidateConfig {
|
|
1060
|
+
network: "udp".to_owned(),
|
|
1061
|
+
address: "10.10.10.2".to_owned(),
|
|
1062
|
+
port: 19217,
|
|
1063
|
+
component: 1,
|
|
1064
|
+
..Default::default()
|
|
1065
|
+
},
|
|
1066
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1067
|
+
rel_port: 43211,
|
|
1068
|
+
}
|
|
1069
|
+
.new_candidate_peer_reflexive()?,
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
let host_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1073
|
+
CandidateHostConfig {
|
|
1074
|
+
base_config: CandidateConfig {
|
|
1075
|
+
network: "udp".to_owned(),
|
|
1076
|
+
address: "1.2.3.5".to_owned(),
|
|
1077
|
+
port: 12350,
|
|
1078
|
+
component: 1,
|
|
1079
|
+
..Default::default()
|
|
1080
|
+
},
|
|
1081
|
+
..Default::default()
|
|
1082
|
+
}
|
|
1083
|
+
.new_candidate_host()?,
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
for remote in &[
|
|
1087
|
+
Arc::clone(&relay_remote),
|
|
1088
|
+
Arc::clone(&srflx_remote),
|
|
1089
|
+
Arc::clone(&prflx_remote),
|
|
1090
|
+
Arc::clone(&host_remote),
|
|
1091
|
+
] {
|
|
1092
|
+
let p = a.internal.find_pair(&host_local, remote);
|
|
1093
|
+
|
|
1094
|
+
if p.is_none() {
|
|
1095
|
+
a.internal
|
|
1096
|
+
.add_pair(Arc::clone(&host_local), Arc::clone(remote));
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
{
|
|
1101
|
+
if let Some(p) = a.internal.find_pair(&host_local, &prflx_remote) {
|
|
1102
|
+
p.state
|
|
1103
|
+
.store(CandidatePairState::Failed as u8, Ordering::SeqCst);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
let stats = a.get_candidate_pairs_stats();
|
|
1108
|
+
assert_eq!(stats.len(), 4, "expected 4 candidate pairs stats");
|
|
1109
|
+
|
|
1110
|
+
let (mut relay_pair_stat, mut srflx_pair_stat, mut prflx_pair_stat, mut host_pair_stat) = (
|
|
1111
|
+
CandidatePairStats::default(),
|
|
1112
|
+
CandidatePairStats::default(),
|
|
1113
|
+
CandidatePairStats::default(),
|
|
1114
|
+
CandidatePairStats::default(),
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
for cps in stats {
|
|
1118
|
+
assert_eq!(
|
|
1119
|
+
cps.local_candidate_id,
|
|
1120
|
+
host_local.id(),
|
|
1121
|
+
"invalid local candidate id"
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
if cps.remote_candidate_id == relay_remote.id() {
|
|
1125
|
+
relay_pair_stat = cps;
|
|
1126
|
+
} else if cps.remote_candidate_id == srflx_remote.id() {
|
|
1127
|
+
srflx_pair_stat = cps;
|
|
1128
|
+
} else if cps.remote_candidate_id == prflx_remote.id() {
|
|
1129
|
+
prflx_pair_stat = cps;
|
|
1130
|
+
} else if cps.remote_candidate_id == host_remote.id() {
|
|
1131
|
+
host_pair_stat = cps;
|
|
1132
|
+
} else {
|
|
1133
|
+
panic!("invalid remote candidate ID");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
assert_eq!(
|
|
1138
|
+
relay_pair_stat.remote_candidate_id,
|
|
1139
|
+
relay_remote.id(),
|
|
1140
|
+
"missing host-relay pair stat"
|
|
1141
|
+
);
|
|
1142
|
+
assert_eq!(
|
|
1143
|
+
srflx_pair_stat.remote_candidate_id,
|
|
1144
|
+
srflx_remote.id(),
|
|
1145
|
+
"missing host-srflx pair stat"
|
|
1146
|
+
);
|
|
1147
|
+
assert_eq!(
|
|
1148
|
+
prflx_pair_stat.remote_candidate_id,
|
|
1149
|
+
prflx_remote.id(),
|
|
1150
|
+
"missing host-prflx pair stat"
|
|
1151
|
+
);
|
|
1152
|
+
assert_eq!(
|
|
1153
|
+
host_pair_stat.remote_candidate_id,
|
|
1154
|
+
host_remote.id(),
|
|
1155
|
+
"missing host-host pair stat"
|
|
1156
|
+
);
|
|
1157
|
+
assert_eq!(
|
|
1158
|
+
prflx_pair_stat.state,
|
|
1159
|
+
CandidatePairState::Failed,
|
|
1160
|
+
"expected host-prfflx pair to have state failed, it has state {} instead",
|
|
1161
|
+
prflx_pair_stat.state
|
|
1162
|
+
);
|
|
1163
|
+
|
|
1164
|
+
a.close()?;
|
|
1165
|
+
|
|
1166
|
+
Ok(())
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
#[test]
|
|
1170
|
+
fn test_local_candidate_stats() -> Result<()> {
|
|
1171
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1172
|
+
|
|
1173
|
+
let host_local: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1174
|
+
CandidateHostConfig {
|
|
1175
|
+
base_config: CandidateConfig {
|
|
1176
|
+
network: "udp".to_owned(),
|
|
1177
|
+
address: "192.168.1.1".to_owned(),
|
|
1178
|
+
port: 19216,
|
|
1179
|
+
component: 1,
|
|
1180
|
+
..Default::default()
|
|
1181
|
+
},
|
|
1182
|
+
..Default::default()
|
|
1183
|
+
}
|
|
1184
|
+
.new_candidate_host()?,
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
let srflx_local: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1188
|
+
CandidateServerReflexiveConfig {
|
|
1189
|
+
base_config: CandidateConfig {
|
|
1190
|
+
network: "udp".to_owned(),
|
|
1191
|
+
address: "192.168.1.1".to_owned(),
|
|
1192
|
+
port: 19217,
|
|
1193
|
+
component: 1,
|
|
1194
|
+
..Default::default()
|
|
1195
|
+
},
|
|
1196
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1197
|
+
rel_port: 43212,
|
|
1198
|
+
}
|
|
1199
|
+
.new_candidate_server_reflexive()?,
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
{
|
|
1203
|
+
let mut local_candidates = a.internal.local_candidates.lock();
|
|
1204
|
+
local_candidates.insert(
|
|
1205
|
+
NetworkType::Udp4,
|
|
1206
|
+
vec![Arc::clone(&host_local), Arc::clone(&srflx_local)],
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
let local_stats = a.get_local_candidates_stats();
|
|
1211
|
+
assert_eq!(
|
|
1212
|
+
local_stats.len(),
|
|
1213
|
+
2,
|
|
1214
|
+
"expected 2 local candidates stats, got {} instead",
|
|
1215
|
+
local_stats.len()
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
let (mut host_local_stat, mut srflx_local_stat) =
|
|
1219
|
+
(CandidateStats::default(), CandidateStats::default());
|
|
1220
|
+
for stats in local_stats {
|
|
1221
|
+
let candidate = if stats.id == host_local.id() {
|
|
1222
|
+
host_local_stat = stats.clone();
|
|
1223
|
+
Arc::clone(&host_local)
|
|
1224
|
+
} else if stats.id == srflx_local.id() {
|
|
1225
|
+
srflx_local_stat = stats.clone();
|
|
1226
|
+
Arc::clone(&srflx_local)
|
|
1227
|
+
} else {
|
|
1228
|
+
panic!("invalid local candidate ID");
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
assert_eq!(
|
|
1232
|
+
stats.candidate_type,
|
|
1233
|
+
candidate.candidate_type(),
|
|
1234
|
+
"invalid stats CandidateType"
|
|
1235
|
+
);
|
|
1236
|
+
assert_eq!(
|
|
1237
|
+
stats.priority,
|
|
1238
|
+
candidate.priority(),
|
|
1239
|
+
"invalid stats CandidateType"
|
|
1240
|
+
);
|
|
1241
|
+
assert_eq!(stats.ip, candidate.address(), "invalid stats IP");
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
assert_eq!(
|
|
1245
|
+
host_local_stat.id,
|
|
1246
|
+
host_local.id(),
|
|
1247
|
+
"missing host local stat"
|
|
1248
|
+
);
|
|
1249
|
+
assert_eq!(
|
|
1250
|
+
srflx_local_stat.id,
|
|
1251
|
+
srflx_local.id(),
|
|
1252
|
+
"missing srflx local stat"
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
a.close()?;
|
|
1256
|
+
|
|
1257
|
+
Ok(())
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
#[test]
|
|
1261
|
+
fn test_remote_candidate_stats() -> Result<()> {
|
|
1262
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1263
|
+
|
|
1264
|
+
let relay_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1265
|
+
CandidateRelayConfig {
|
|
1266
|
+
base_config: CandidateConfig {
|
|
1267
|
+
network: "udp".to_owned(),
|
|
1268
|
+
address: "1.2.3.4".to_owned(),
|
|
1269
|
+
port: 12340,
|
|
1270
|
+
component: 1,
|
|
1271
|
+
..Default::default()
|
|
1272
|
+
},
|
|
1273
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1274
|
+
rel_port: 43210,
|
|
1275
|
+
..Default::default()
|
|
1276
|
+
}
|
|
1277
|
+
.new_candidate_relay()?,
|
|
1278
|
+
);
|
|
1279
|
+
|
|
1280
|
+
let srflx_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1281
|
+
CandidateServerReflexiveConfig {
|
|
1282
|
+
base_config: CandidateConfig {
|
|
1283
|
+
network: "udp".to_owned(),
|
|
1284
|
+
address: "10.10.10.2".to_owned(),
|
|
1285
|
+
port: 19218,
|
|
1286
|
+
component: 1,
|
|
1287
|
+
..Default::default()
|
|
1288
|
+
},
|
|
1289
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1290
|
+
rel_port: 43212,
|
|
1291
|
+
}
|
|
1292
|
+
.new_candidate_server_reflexive()?,
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
let prflx_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1296
|
+
CandidatePeerReflexiveConfig {
|
|
1297
|
+
base_config: CandidateConfig {
|
|
1298
|
+
network: "udp".to_owned(),
|
|
1299
|
+
address: "10.10.10.2".to_owned(),
|
|
1300
|
+
port: 19217,
|
|
1301
|
+
component: 1,
|
|
1302
|
+
..Default::default()
|
|
1303
|
+
},
|
|
1304
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
1305
|
+
rel_port: 43211,
|
|
1306
|
+
}
|
|
1307
|
+
.new_candidate_peer_reflexive()?,
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
let host_remote: Arc<dyn Candidate + Send + Sync> = Arc::new(
|
|
1311
|
+
CandidateHostConfig {
|
|
1312
|
+
base_config: CandidateConfig {
|
|
1313
|
+
network: "udp".to_owned(),
|
|
1314
|
+
address: "1.2.3.5".to_owned(),
|
|
1315
|
+
port: 12350,
|
|
1316
|
+
component: 1,
|
|
1317
|
+
..Default::default()
|
|
1318
|
+
},
|
|
1319
|
+
..Default::default()
|
|
1320
|
+
}
|
|
1321
|
+
.new_candidate_host()?,
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
{
|
|
1325
|
+
let mut remote_candidates = a.internal.remote_candidates.lock();
|
|
1326
|
+
remote_candidates.insert(
|
|
1327
|
+
NetworkType::Udp4,
|
|
1328
|
+
vec![
|
|
1329
|
+
Arc::clone(&relay_remote),
|
|
1330
|
+
Arc::clone(&srflx_remote),
|
|
1331
|
+
Arc::clone(&prflx_remote),
|
|
1332
|
+
Arc::clone(&host_remote),
|
|
1333
|
+
],
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
let remote_stats = a.get_remote_candidates_stats();
|
|
1338
|
+
assert_eq!(
|
|
1339
|
+
remote_stats.len(),
|
|
1340
|
+
4,
|
|
1341
|
+
"expected 4 remote candidates stats, got {} instead",
|
|
1342
|
+
remote_stats.len()
|
|
1343
|
+
);
|
|
1344
|
+
|
|
1345
|
+
let (mut relay_remote_stat, mut srflx_remote_stat, mut prflx_remote_stat, mut host_remote_stat) = (
|
|
1346
|
+
CandidateStats::default(),
|
|
1347
|
+
CandidateStats::default(),
|
|
1348
|
+
CandidateStats::default(),
|
|
1349
|
+
CandidateStats::default(),
|
|
1350
|
+
);
|
|
1351
|
+
for stats in remote_stats {
|
|
1352
|
+
let candidate = if stats.id == relay_remote.id() {
|
|
1353
|
+
relay_remote_stat = stats.clone();
|
|
1354
|
+
Arc::clone(&relay_remote)
|
|
1355
|
+
} else if stats.id == srflx_remote.id() {
|
|
1356
|
+
srflx_remote_stat = stats.clone();
|
|
1357
|
+
Arc::clone(&srflx_remote)
|
|
1358
|
+
} else if stats.id == prflx_remote.id() {
|
|
1359
|
+
prflx_remote_stat = stats.clone();
|
|
1360
|
+
Arc::clone(&prflx_remote)
|
|
1361
|
+
} else if stats.id == host_remote.id() {
|
|
1362
|
+
host_remote_stat = stats.clone();
|
|
1363
|
+
Arc::clone(&host_remote)
|
|
1364
|
+
} else {
|
|
1365
|
+
panic!("invalid remote candidate ID");
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
assert_eq!(
|
|
1369
|
+
stats.candidate_type,
|
|
1370
|
+
candidate.candidate_type(),
|
|
1371
|
+
"invalid stats CandidateType"
|
|
1372
|
+
);
|
|
1373
|
+
assert_eq!(
|
|
1374
|
+
stats.priority,
|
|
1375
|
+
candidate.priority(),
|
|
1376
|
+
"invalid stats CandidateType"
|
|
1377
|
+
);
|
|
1378
|
+
assert_eq!(stats.ip, candidate.address(), "invalid stats IP");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
assert_eq!(
|
|
1382
|
+
relay_remote_stat.id,
|
|
1383
|
+
relay_remote.id(),
|
|
1384
|
+
"missing relay remote stat"
|
|
1385
|
+
);
|
|
1386
|
+
assert_eq!(
|
|
1387
|
+
srflx_remote_stat.id,
|
|
1388
|
+
srflx_remote.id(),
|
|
1389
|
+
"missing srflx remote stat"
|
|
1390
|
+
);
|
|
1391
|
+
assert_eq!(
|
|
1392
|
+
prflx_remote_stat.id,
|
|
1393
|
+
prflx_remote.id(),
|
|
1394
|
+
"missing prflx remote stat"
|
|
1395
|
+
);
|
|
1396
|
+
assert_eq!(
|
|
1397
|
+
host_remote_stat.id,
|
|
1398
|
+
host_remote.id(),
|
|
1399
|
+
"missing host remote stat"
|
|
1400
|
+
);
|
|
1401
|
+
|
|
1402
|
+
a.close()?;
|
|
1403
|
+
|
|
1404
|
+
Ok(())
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
#[test]
|
|
1408
|
+
fn test_binding_request_timeout() -> Result<()> {
|
|
1409
|
+
const EXPECTED_REMOVAL_COUNT: usize = 2;
|
|
1410
|
+
|
|
1411
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1412
|
+
|
|
1413
|
+
let now = Instant::now();
|
|
1414
|
+
{
|
|
1415
|
+
{
|
|
1416
|
+
let mut pending_binding_requests = a.internal.pending_binding_requests.lock();
|
|
1417
|
+
pending_binding_requests.push(BindingRequest {
|
|
1418
|
+
timestamp: now, // valid
|
|
1419
|
+
..Default::default()
|
|
1420
|
+
});
|
|
1421
|
+
pending_binding_requests.push(BindingRequest {
|
|
1422
|
+
timestamp: now.sub(Duration::from_millis(3900)), // valid
|
|
1423
|
+
..Default::default()
|
|
1424
|
+
});
|
|
1425
|
+
pending_binding_requests.push(BindingRequest {
|
|
1426
|
+
timestamp: now.sub(Duration::from_millis(4100)), // invalid
|
|
1427
|
+
..Default::default()
|
|
1428
|
+
});
|
|
1429
|
+
pending_binding_requests.push(BindingRequest {
|
|
1430
|
+
timestamp: now.sub(Duration::from_secs(75)), // invalid
|
|
1431
|
+
..Default::default()
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
a.internal.invalidate_pending_binding_requests(now);
|
|
1436
|
+
{
|
|
1437
|
+
let pending_binding_requests = a.internal.pending_binding_requests.lock();
|
|
1438
|
+
assert_eq!(pending_binding_requests.len(), EXPECTED_REMOVAL_COUNT, "Binding invalidation due to timeout did not remove the correct number of binding requests")
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
a.close()?;
|
|
1443
|
+
|
|
1444
|
+
Ok(())
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// test_agent_credentials checks if local username fragments and passwords (if set) meet RFC standard
|
|
1448
|
+
// and ensure it's backwards compatible with previous versions of the rtc/ice
|
|
1449
|
+
#[test]
|
|
1450
|
+
fn test_agent_credentials() -> Result<()> {
|
|
1451
|
+
// Agent should not require any of the usernames and password to be set
|
|
1452
|
+
// If set, they should follow the default 16/128 bits random number generator strategy
|
|
1453
|
+
|
|
1454
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1455
|
+
{
|
|
1456
|
+
let ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
1457
|
+
assert!(ufrag_pwd.local_ufrag.as_bytes().len() * 8 >= 24);
|
|
1458
|
+
assert!(ufrag_pwd.local_pwd.as_bytes().len() * 8 >= 128);
|
|
1459
|
+
}
|
|
1460
|
+
a.close()?;
|
|
1461
|
+
|
|
1462
|
+
// Should honor RFC standards
|
|
1463
|
+
// Local values MUST be unguessable, with at least 128 bits of
|
|
1464
|
+
// random number generator output used to generate the password, and
|
|
1465
|
+
// at least 24 bits of output to generate the username fragment.
|
|
1466
|
+
|
|
1467
|
+
if let Err(err) = Agent::new(Instant::now(), AgentConfig {
|
|
1468
|
+
local_ufrag: "xx".to_owned(),
|
|
1469
|
+
..Default::default()
|
|
1470
|
+
}) {
|
|
1471
|
+
assert_eq!(Error::ErrLocalUfragInsufficientBits, err);
|
|
1472
|
+
} else {
|
|
1473
|
+
panic!("expected error, but got ok");
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if let Err(err) = Agent::new(Instant::now(), AgentConfig {
|
|
1477
|
+
local_pwd: "xxxxxx".to_owned(),
|
|
1478
|
+
..Default::default()
|
|
1479
|
+
}) {
|
|
1480
|
+
assert_eq!(Error::ErrLocalPwdInsufficientBits, err);
|
|
1481
|
+
} else {
|
|
1482
|
+
panic!("expected error, but got ok");
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
Ok(())
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Assert that Agent on Failure deletes all existing candidates
|
|
1489
|
+
// User can then do an ICE Restart to bring agent back
|
|
1490
|
+
#[test]
|
|
1491
|
+
fn test_connection_state_failed_delete_all_candidates() -> Result<()> {
|
|
1492
|
+
let one_second = Duration::from_secs(1);
|
|
1493
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1494
|
+
|
|
1495
|
+
let cfg0 = AgentConfig {
|
|
1496
|
+
network_types: supported_network_types(),
|
|
1497
|
+
disconnected_timeout: Some(one_second),
|
|
1498
|
+
failed_timeout: Some(one_second),
|
|
1499
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1500
|
+
..Default::default()
|
|
1501
|
+
};
|
|
1502
|
+
let cfg1 = AgentConfig {
|
|
1503
|
+
network_types: supported_network_types(),
|
|
1504
|
+
disconnected_timeout: Some(one_second),
|
|
1505
|
+
failed_timeout: Some(one_second),
|
|
1506
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1507
|
+
..Default::default()
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
1511
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
1512
|
+
|
|
1513
|
+
let (is_failed_tx, mut is_failed_rx) = mpsc::channel::<()>(1);
|
|
1514
|
+
let is_failed_tx = Arc::new(Mutex::new(Some(is_failed_tx)));
|
|
1515
|
+
a_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
1516
|
+
let is_failed_tx_clone = Arc::clone(&is_failed_tx);
|
|
1517
|
+
Box::pin(async move {
|
|
1518
|
+
if c == ConnectionState::Failed {
|
|
1519
|
+
let mut tx = is_failed_tx_clone.lock();
|
|
1520
|
+
tx.take();
|
|
1521
|
+
}
|
|
1522
|
+
})
|
|
1523
|
+
}));
|
|
1524
|
+
|
|
1525
|
+
connect_with_vnet(&a_agent, &b_agent)?;
|
|
1526
|
+
let _ = is_failed_rx.recv();
|
|
1527
|
+
|
|
1528
|
+
{
|
|
1529
|
+
{
|
|
1530
|
+
let remote_candidates = a_agent.internal.remote_candidates.lock();
|
|
1531
|
+
assert_eq!(remote_candidates.len(), 0);
|
|
1532
|
+
}
|
|
1533
|
+
{
|
|
1534
|
+
let local_candidates = a_agent.internal.local_candidates.lock();
|
|
1535
|
+
assert_eq!(local_candidates.len(), 0);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
a_agent.close()?;
|
|
1540
|
+
b_agent.close()?;
|
|
1541
|
+
|
|
1542
|
+
Ok(())
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Assert that the ICE Agent can go directly from Connecting -> Failed on both sides
|
|
1546
|
+
#[test]
|
|
1547
|
+
fn test_connection_state_connecting_to_failed() -> Result<()> {
|
|
1548
|
+
let one_second = Duration::from_secs(1);
|
|
1549
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1550
|
+
|
|
1551
|
+
let cfg0 = AgentConfig {
|
|
1552
|
+
disconnected_timeout: Some(one_second),
|
|
1553
|
+
failed_timeout: Some(one_second),
|
|
1554
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1555
|
+
..Default::default()
|
|
1556
|
+
};
|
|
1557
|
+
let cfg1 = AgentConfig {
|
|
1558
|
+
disconnected_timeout: Some(one_second),
|
|
1559
|
+
failed_timeout: Some(one_second),
|
|
1560
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1561
|
+
..Default::default()
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
1565
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
1566
|
+
|
|
1567
|
+
let is_failed = WaitGroup::new();
|
|
1568
|
+
let is_checking = WaitGroup::new();
|
|
1569
|
+
|
|
1570
|
+
let connection_state_check = move |wf: Worker, wc: Worker| {
|
|
1571
|
+
let wf = Arc::new(Mutex::new(Some(wf)));
|
|
1572
|
+
let wc = Arc::new(Mutex::new(Some(wc)));
|
|
1573
|
+
let hdlr_fn: OnConnectionStateChangeHdlrFn = Box::new(move |c: ConnectionState| {
|
|
1574
|
+
let wf_clone = Arc::clone(&wf);
|
|
1575
|
+
let wc_clone = Arc::clone(&wc);
|
|
1576
|
+
Box::pin(async move {
|
|
1577
|
+
if c == ConnectionState::Failed {
|
|
1578
|
+
let mut f = wf_clone.lock();
|
|
1579
|
+
f.take();
|
|
1580
|
+
} else if c == ConnectionState::Checking {
|
|
1581
|
+
let mut c = wc_clone.lock();
|
|
1582
|
+
c.take();
|
|
1583
|
+
} else if c == ConnectionState::Connected || c == ConnectionState::Completed {
|
|
1584
|
+
panic!("Unexpected ConnectionState: {c}");
|
|
1585
|
+
}
|
|
1586
|
+
})
|
|
1587
|
+
});
|
|
1588
|
+
hdlr_fn
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
let (wf1, wc1) = (is_failed.worker(), is_checking.worker());
|
|
1592
|
+
a_agent.on_connection_state_change(connection_state_check(wf1, wc1));
|
|
1593
|
+
|
|
1594
|
+
let (wf2, wc2) = (is_failed.worker(), is_checking.worker());
|
|
1595
|
+
b_agent.on_connection_state_change(connection_state_check(wf2, wc2));
|
|
1596
|
+
|
|
1597
|
+
let agent_a = Arc::clone(&a_agent);
|
|
1598
|
+
tokio::spawn(async move {
|
|
1599
|
+
let (_cancel_tx, cancel_rx) = mpsc::channel(1);
|
|
1600
|
+
let result = agent_a.accept(cancel_rx, "InvalidFrag".to_owned(), "InvalidPwd".to_owned());
|
|
1601
|
+
assert!(result.is_err());
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
let agent_b = Arc::clone(&b_agent);
|
|
1605
|
+
tokio::spawn(async move {
|
|
1606
|
+
let (_cancel_tx, cancel_rx) = mpsc::channel(1);
|
|
1607
|
+
let result = agent_b.dial(cancel_rx, "InvalidFrag".to_owned(), "InvalidPwd".to_owned());
|
|
1608
|
+
assert!(result.is_err());
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
is_checking.wait();
|
|
1612
|
+
is_failed.wait();
|
|
1613
|
+
|
|
1614
|
+
a_agent.close()?;
|
|
1615
|
+
b_agent.close()?;
|
|
1616
|
+
|
|
1617
|
+
Ok(())
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
#[test]
|
|
1621
|
+
fn test_agent_restart_during_gather() -> Result<()> {
|
|
1622
|
+
//"Restart During Gather"
|
|
1623
|
+
|
|
1624
|
+
let agent = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1625
|
+
|
|
1626
|
+
agent
|
|
1627
|
+
.gathering_state
|
|
1628
|
+
.store(GatheringState::Gathering as u8, Ordering::SeqCst);
|
|
1629
|
+
|
|
1630
|
+
if let Err(err) = agent.restart(Instant::now(), "".to_owned(), "".to_owned()) {
|
|
1631
|
+
assert_eq!(Error::ErrRestartWhenGathering, err);
|
|
1632
|
+
} else {
|
|
1633
|
+
panic!("expected error, but got ok");
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
agent.close()?;
|
|
1637
|
+
|
|
1638
|
+
Ok(())
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
#[test]
|
|
1642
|
+
fn test_agent_restart_when_closed() -> Result<()> {
|
|
1643
|
+
//"Restart When Closed"
|
|
1644
|
+
|
|
1645
|
+
let agent = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1646
|
+
agent.close()?;
|
|
1647
|
+
|
|
1648
|
+
if let Err(err) = agent.restart(Instant::now(), "".to_owned(), "".to_owned()) {
|
|
1649
|
+
assert_eq!(Error::ErrClosed, err);
|
|
1650
|
+
} else {
|
|
1651
|
+
panic!("expected error, but got ok");
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
Ok(())
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
#[test]
|
|
1658
|
+
fn test_agent_restart_one_side() -> Result<()> {
|
|
1659
|
+
let one_second = Duration::from_secs(1);
|
|
1660
|
+
|
|
1661
|
+
//"Restart One Side"
|
|
1662
|
+
let (_, _, agent_a, agent_b) = pipe(
|
|
1663
|
+
Some(AgentConfig {
|
|
1664
|
+
disconnected_timeout: Some(one_second),
|
|
1665
|
+
failed_timeout: Some(one_second),
|
|
1666
|
+
..Default::default()
|
|
1667
|
+
}),
|
|
1668
|
+
Some(AgentConfig {
|
|
1669
|
+
disconnected_timeout: Some(one_second),
|
|
1670
|
+
failed_timeout: Some(one_second),
|
|
1671
|
+
..Default::default()
|
|
1672
|
+
}),
|
|
1673
|
+
)?;
|
|
1674
|
+
|
|
1675
|
+
let (cancel_tx, mut cancel_rx) = mpsc::channel::<()>(1);
|
|
1676
|
+
let cancel_tx = Arc::new(Mutex::new(Some(cancel_tx)));
|
|
1677
|
+
agent_b.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
1678
|
+
let cancel_tx_clone = Arc::clone(&cancel_tx);
|
|
1679
|
+
Box::pin(async move {
|
|
1680
|
+
if c == ConnectionState::Failed || c == ConnectionState::Disconnected {
|
|
1681
|
+
let mut tx = cancel_tx_clone.lock();
|
|
1682
|
+
tx.take();
|
|
1683
|
+
}
|
|
1684
|
+
})
|
|
1685
|
+
}));
|
|
1686
|
+
|
|
1687
|
+
agent_a.restart(Instant::now(), "".to_owned(), "".to_owned())?;
|
|
1688
|
+
|
|
1689
|
+
let _ = cancel_rx.recv();
|
|
1690
|
+
|
|
1691
|
+
agent_a.close()?;
|
|
1692
|
+
agent_b.close()?;
|
|
1693
|
+
|
|
1694
|
+
Ok(())
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
#[test]
|
|
1698
|
+
fn test_agent_restart_both_side() -> Result<()> {
|
|
1699
|
+
let one_second = Duration::from_secs(1);
|
|
1700
|
+
//"Restart Both Sides"
|
|
1701
|
+
|
|
1702
|
+
// Get all addresses of candidates concatenated
|
|
1703
|
+
let generate_candidate_address_strings =
|
|
1704
|
+
|res: Result<Vec<Arc<dyn Candidate + Send + Sync>>>| -> String {
|
|
1705
|
+
assert!(res.is_ok());
|
|
1706
|
+
|
|
1707
|
+
let mut out = String::new();
|
|
1708
|
+
if let Ok(candidates) = res {
|
|
1709
|
+
for c in candidates {
|
|
1710
|
+
out += c.address().as_str();
|
|
1711
|
+
out += ":";
|
|
1712
|
+
out += c.port().to_string().as_str();
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
out
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// Store the original candidates, confirm that after we reconnect we have new pairs
|
|
1719
|
+
let (_, _, agent_a, agent_b) = pipe(
|
|
1720
|
+
Some(AgentConfig {
|
|
1721
|
+
disconnected_timeout: Some(one_second),
|
|
1722
|
+
failed_timeout: Some(one_second),
|
|
1723
|
+
..Default::default()
|
|
1724
|
+
}),
|
|
1725
|
+
Some(AgentConfig {
|
|
1726
|
+
disconnected_timeout: Some(one_second),
|
|
1727
|
+
failed_timeout: Some(one_second),
|
|
1728
|
+
..Default::default()
|
|
1729
|
+
}),
|
|
1730
|
+
)?;
|
|
1731
|
+
|
|
1732
|
+
let conn_afirst_candidates = generate_candidate_address_strings(agent_a.get_local_candidates());
|
|
1733
|
+
let conn_bfirst_candidates = generate_candidate_address_strings(agent_b.get_local_candidates());
|
|
1734
|
+
|
|
1735
|
+
let (a_notifier, mut a_connected) = on_connected();
|
|
1736
|
+
agent_a.on_connection_state_change(a_notifier);
|
|
1737
|
+
|
|
1738
|
+
let (b_notifier, mut b_connected) = on_connected();
|
|
1739
|
+
agent_b.on_connection_state_change(b_notifier);
|
|
1740
|
+
|
|
1741
|
+
// Restart and Re-Signal
|
|
1742
|
+
agent_a.restart(Instant::now(), "".to_owned(), "".to_owned())?;
|
|
1743
|
+
agent_b.restart(Instant::now(), "".to_owned(), "".to_owned())?;
|
|
1744
|
+
|
|
1745
|
+
// Exchange Candidates and Credentials
|
|
1746
|
+
let (ufrag, pwd) = agent_b.get_local_user_credentials();
|
|
1747
|
+
agent_a.set_remote_credentials(ufrag, pwd)?;
|
|
1748
|
+
|
|
1749
|
+
let (ufrag, pwd) = agent_a.get_local_user_credentials();
|
|
1750
|
+
agent_b.set_remote_credentials(ufrag, pwd)?;
|
|
1751
|
+
|
|
1752
|
+
gather_and_exchange_candidates(&agent_a, &agent_b)?;
|
|
1753
|
+
|
|
1754
|
+
// Wait until both have gone back to connected
|
|
1755
|
+
let _ = a_connected.recv();
|
|
1756
|
+
let _ = b_connected.recv();
|
|
1757
|
+
|
|
1758
|
+
// Assert that we have new candiates each time
|
|
1759
|
+
assert_ne!(
|
|
1760
|
+
conn_afirst_candidates,
|
|
1761
|
+
generate_candidate_address_strings(agent_a.get_local_candidates())
|
|
1762
|
+
);
|
|
1763
|
+
assert_ne!(
|
|
1764
|
+
conn_bfirst_candidates,
|
|
1765
|
+
generate_candidate_address_strings(agent_b.get_local_candidates())
|
|
1766
|
+
);
|
|
1767
|
+
|
|
1768
|
+
agent_a.close()?;
|
|
1769
|
+
agent_b.close()?;
|
|
1770
|
+
|
|
1771
|
+
Ok(())
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
#[test]
|
|
1775
|
+
fn test_get_remote_credentials() -> Result<()> {
|
|
1776
|
+
let a = Agent::new(Instant::now(), AgentConfig::default())?;
|
|
1777
|
+
|
|
1778
|
+
let (remote_ufrag, remote_pwd) = {
|
|
1779
|
+
let mut ufrag_pwd = a.internal.ufrag_pwd.lock();
|
|
1780
|
+
ufrag_pwd.remote_ufrag = "remoteUfrag".to_owned();
|
|
1781
|
+
ufrag_pwd.remote_pwd = "remotePwd".to_owned();
|
|
1782
|
+
(
|
|
1783
|
+
ufrag_pwd.remote_ufrag.to_owned(),
|
|
1784
|
+
ufrag_pwd.remote_pwd.to_owned(),
|
|
1785
|
+
)
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
let (actual_ufrag, actual_pwd) = a.get_remote_user_credentials();
|
|
1789
|
+
|
|
1790
|
+
assert_eq!(actual_ufrag, remote_ufrag);
|
|
1791
|
+
assert_eq!(actual_pwd, remote_pwd);
|
|
1792
|
+
|
|
1793
|
+
a.close()?;
|
|
1794
|
+
|
|
1795
|
+
Ok(())
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
#[test]
|
|
1799
|
+
fn test_close_in_connection_state_callback() -> Result<()> {
|
|
1800
|
+
let disconnected_duration = Duration::from_secs(1);
|
|
1801
|
+
let failed_duration = Duration::from_secs(1);
|
|
1802
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1803
|
+
|
|
1804
|
+
let cfg0 = AgentConfig {
|
|
1805
|
+
urls: vec![],
|
|
1806
|
+
network_types: supported_network_types(),
|
|
1807
|
+
disconnected_timeout: Some(disconnected_duration),
|
|
1808
|
+
failed_timeout: Some(failed_duration),
|
|
1809
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1810
|
+
check_interval: Duration::from_millis(500),
|
|
1811
|
+
..Default::default()
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
let cfg1 = AgentConfig {
|
|
1815
|
+
urls: vec![],
|
|
1816
|
+
network_types: supported_network_types(),
|
|
1817
|
+
disconnected_timeout: Some(disconnected_duration),
|
|
1818
|
+
failed_timeout: Some(failed_duration),
|
|
1819
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1820
|
+
check_interval: Duration::from_millis(500),
|
|
1821
|
+
..Default::default()
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
1825
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
1826
|
+
|
|
1827
|
+
let (is_closed_tx, mut is_closed_rx) = mpsc::channel::<()>(1);
|
|
1828
|
+
let (is_connected_tx, mut is_connected_rx) = mpsc::channel::<()>(1);
|
|
1829
|
+
let is_closed_tx = Arc::new(Mutex::new(Some(is_closed_tx)));
|
|
1830
|
+
let is_connected_tx = Arc::new(Mutex::new(Some(is_connected_tx)));
|
|
1831
|
+
a_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
1832
|
+
let is_closed_tx_clone = Arc::clone(&is_closed_tx);
|
|
1833
|
+
let is_connected_tx_clone = Arc::clone(&is_connected_tx);
|
|
1834
|
+
Box::pin(async move {
|
|
1835
|
+
if c == ConnectionState::Connected {
|
|
1836
|
+
let mut tx = is_connected_tx_clone.lock();
|
|
1837
|
+
tx.take();
|
|
1838
|
+
} else if c == ConnectionState::Closed {
|
|
1839
|
+
let mut tx = is_closed_tx_clone.lock();
|
|
1840
|
+
tx.take();
|
|
1841
|
+
}
|
|
1842
|
+
})
|
|
1843
|
+
}));
|
|
1844
|
+
|
|
1845
|
+
connect_with_vnet(&a_agent, &b_agent)?;
|
|
1846
|
+
|
|
1847
|
+
let _ = is_connected_rx.recv();
|
|
1848
|
+
a_agent.close()?;
|
|
1849
|
+
|
|
1850
|
+
let _ = is_closed_rx.recv();
|
|
1851
|
+
b_agent.close()?;
|
|
1852
|
+
|
|
1853
|
+
Ok(())
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
#[test]
|
|
1857
|
+
fn test_run_task_in_connection_state_callback() -> Result<()> {
|
|
1858
|
+
let one_second = Duration::from_secs(1);
|
|
1859
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1860
|
+
|
|
1861
|
+
let cfg0 = AgentConfig {
|
|
1862
|
+
urls: vec![],
|
|
1863
|
+
network_types: supported_network_types(),
|
|
1864
|
+
disconnected_timeout: Some(one_second),
|
|
1865
|
+
failed_timeout: Some(one_second),
|
|
1866
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1867
|
+
check_interval: Duration::from_millis(50),
|
|
1868
|
+
..Default::default()
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
let cfg1 = AgentConfig {
|
|
1872
|
+
urls: vec![],
|
|
1873
|
+
network_types: supported_network_types(),
|
|
1874
|
+
disconnected_timeout: Some(one_second),
|
|
1875
|
+
failed_timeout: Some(one_second),
|
|
1876
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1877
|
+
check_interval: Duration::from_millis(50),
|
|
1878
|
+
..Default::default()
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
1882
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
1883
|
+
|
|
1884
|
+
let (is_complete_tx, mut is_complete_rx) = mpsc::channel::<()>(1);
|
|
1885
|
+
let is_complete_tx = Arc::new(Mutex::new(Some(is_complete_tx)));
|
|
1886
|
+
a_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
1887
|
+
let is_complete_tx_clone = Arc::clone(&is_complete_tx);
|
|
1888
|
+
Box::pin(async move {
|
|
1889
|
+
if c == ConnectionState::Connected {
|
|
1890
|
+
let mut tx = is_complete_tx_clone.lock();
|
|
1891
|
+
tx.take();
|
|
1892
|
+
}
|
|
1893
|
+
})
|
|
1894
|
+
}));
|
|
1895
|
+
|
|
1896
|
+
connect_with_vnet(&a_agent, &b_agent)?;
|
|
1897
|
+
|
|
1898
|
+
let _ = is_complete_rx.recv();
|
|
1899
|
+
let _ = a_agent.get_local_user_credentials();
|
|
1900
|
+
a_agent.restart(Instant::now(), "".to_owned(), "".to_owned())?;
|
|
1901
|
+
|
|
1902
|
+
a_agent.close()?;
|
|
1903
|
+
b_agent.close()?;
|
|
1904
|
+
|
|
1905
|
+
Ok(())
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
#[test]
|
|
1909
|
+
fn test_run_task_in_selected_candidate_pair_change_callback() -> Result<()> {
|
|
1910
|
+
let one_second = Duration::from_secs(1);
|
|
1911
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1912
|
+
|
|
1913
|
+
let cfg0 = AgentConfig {
|
|
1914
|
+
urls: vec![],
|
|
1915
|
+
network_types: supported_network_types(),
|
|
1916
|
+
disconnected_timeout: Some(one_second),
|
|
1917
|
+
failed_timeout: Some(one_second),
|
|
1918
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1919
|
+
check_interval: Duration::from_millis(50),
|
|
1920
|
+
..Default::default()
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
let cfg1 = AgentConfig {
|
|
1924
|
+
urls: vec![],
|
|
1925
|
+
network_types: supported_network_types(),
|
|
1926
|
+
disconnected_timeout: Some(one_second),
|
|
1927
|
+
failed_timeout: Some(one_second),
|
|
1928
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1929
|
+
check_interval: Duration::from_millis(50),
|
|
1930
|
+
..Default::default()
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), cfg0, test_crypto_provider())?);
|
|
1934
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), cfg1, test_crypto_provider())?);
|
|
1935
|
+
|
|
1936
|
+
let (is_tested_tx, mut is_tested_rx) = mpsc::channel::<()>(1);
|
|
1937
|
+
let is_tested_tx = Arc::new(Mutex::new(Some(is_tested_tx)));
|
|
1938
|
+
a_agent.on_selected_candidate_pair_change(Box::new(
|
|
1939
|
+
move |_: &Arc<dyn Candidate + Send + Sync>, _: &Arc<dyn Candidate + Send + Sync>| {
|
|
1940
|
+
let is_tested_tx_clone = Arc::clone(&is_tested_tx);
|
|
1941
|
+
Box::pin(async move {
|
|
1942
|
+
let mut tx = is_tested_tx_clone.lock();
|
|
1943
|
+
tx.take();
|
|
1944
|
+
})
|
|
1945
|
+
},
|
|
1946
|
+
));
|
|
1947
|
+
|
|
1948
|
+
let (is_complete_tx, mut is_complete_rx) = mpsc::channel::<()>(1);
|
|
1949
|
+
let is_complete_tx = Arc::new(Mutex::new(Some(is_complete_tx)));
|
|
1950
|
+
a_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
1951
|
+
let is_complete_tx_clone = Arc::clone(&is_complete_tx);
|
|
1952
|
+
Box::pin(async move {
|
|
1953
|
+
if c == ConnectionState::Connected {
|
|
1954
|
+
let mut tx = is_complete_tx_clone.lock();
|
|
1955
|
+
tx.take();
|
|
1956
|
+
}
|
|
1957
|
+
})
|
|
1958
|
+
}));
|
|
1959
|
+
|
|
1960
|
+
connect_with_vnet(&a_agent, &b_agent)?;
|
|
1961
|
+
|
|
1962
|
+
let _ = is_complete_rx.recv();
|
|
1963
|
+
let _ = is_tested_rx.recv();
|
|
1964
|
+
|
|
1965
|
+
let _ = a_agent.get_local_user_credentials();
|
|
1966
|
+
|
|
1967
|
+
a_agent.close()?;
|
|
1968
|
+
b_agent.close()?;
|
|
1969
|
+
|
|
1970
|
+
Ok(())
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// Assert that a Lite agent goes to disconnected and failed
|
|
1974
|
+
#[test]
|
|
1975
|
+
fn test_lite_lifecycle() -> Result<()> {
|
|
1976
|
+
let (a_notifier, mut a_connected_rx) = on_connected();
|
|
1977
|
+
|
|
1978
|
+
let a_agent = Arc::new(Agent::new(Instant::now(), AgentConfig {
|
|
1979
|
+
network_types: supported_network_types(),
|
|
1980
|
+
..Default::default()
|
|
1981
|
+
})?);
|
|
1982
|
+
|
|
1983
|
+
a_agent.on_connection_state_change(a_notifier);
|
|
1984
|
+
|
|
1985
|
+
let disconnected_duration = Duration::from_secs(1);
|
|
1986
|
+
let failed_duration = Duration::from_secs(1);
|
|
1987
|
+
let keepalive_interval = Duration::from_secs(0);
|
|
1988
|
+
|
|
1989
|
+
let b_agent = Arc::new(Agent::new(Instant::now(), AgentConfig {
|
|
1990
|
+
lite: true,
|
|
1991
|
+
candidate_types: vec![CandidateType::Host],
|
|
1992
|
+
network_types: supported_network_types(),
|
|
1993
|
+
disconnected_timeout: Some(disconnected_duration),
|
|
1994
|
+
failed_timeout: Some(failed_duration),
|
|
1995
|
+
keepalive_interval: Some(keepalive_interval),
|
|
1996
|
+
check_interval: Duration::from_millis(500),
|
|
1997
|
+
..Default::default()
|
|
1998
|
+
})?);
|
|
1999
|
+
|
|
2000
|
+
let (b_connected_tx, mut b_connected_rx) = mpsc::channel::<()>(1);
|
|
2001
|
+
let (b_disconnected_tx, mut b_disconnected_rx) = mpsc::channel::<()>(1);
|
|
2002
|
+
let (b_failed_tx, mut b_failed_rx) = mpsc::channel::<()>(1);
|
|
2003
|
+
let b_connected_tx = Arc::new(Mutex::new(Some(b_connected_tx)));
|
|
2004
|
+
let b_disconnected_tx = Arc::new(Mutex::new(Some(b_disconnected_tx)));
|
|
2005
|
+
let b_failed_tx = Arc::new(Mutex::new(Some(b_failed_tx)));
|
|
2006
|
+
|
|
2007
|
+
b_agent.on_connection_state_change(Box::new(move |c: ConnectionState| {
|
|
2008
|
+
let b_connected_tx_clone = Arc::clone(&b_connected_tx);
|
|
2009
|
+
let b_disconnected_tx_clone = Arc::clone(&b_disconnected_tx);
|
|
2010
|
+
let b_failed_tx_clone = Arc::clone(&b_failed_tx);
|
|
2011
|
+
|
|
2012
|
+
Box::pin(async move {
|
|
2013
|
+
if c == ConnectionState::Connected {
|
|
2014
|
+
let mut tx = b_connected_tx_clone.lock();
|
|
2015
|
+
tx.take();
|
|
2016
|
+
} else if c == ConnectionState::Disconnected {
|
|
2017
|
+
let mut tx = b_disconnected_tx_clone.lock();
|
|
2018
|
+
tx.take();
|
|
2019
|
+
} else if c == ConnectionState::Failed {
|
|
2020
|
+
let mut tx = b_failed_tx_clone.lock();
|
|
2021
|
+
tx.take();
|
|
2022
|
+
}
|
|
2023
|
+
})
|
|
2024
|
+
}));
|
|
2025
|
+
|
|
2026
|
+
connect_with_vnet(&b_agent, &a_agent)?;
|
|
2027
|
+
|
|
2028
|
+
let _ = a_connected_rx.recv();
|
|
2029
|
+
let _ = b_connected_rx.recv();
|
|
2030
|
+
a_agent.close()?;
|
|
2031
|
+
|
|
2032
|
+
let _ = b_disconnected_rx.recv();
|
|
2033
|
+
let _ = b_failed_rx.recv();
|
|
2034
|
+
|
|
2035
|
+
b_agent.close()?;
|
|
2036
|
+
|
|
2037
|
+
Ok(())
|
|
2038
|
+
}
|
|
2039
|
+
*/
|
|
2040
|
+
|
|
2041
|
+
#[test]
|
|
2042
|
+
fn test_zero_failed_timeout_keeps_checking_agent_recoverable() -> Result<()> {
|
|
2043
|
+
let base = Instant::now();
|
|
2044
|
+
let mut agent = Agent::new(
|
|
2045
|
+
base,
|
|
2046
|
+
Arc::new(AgentConfig {
|
|
2047
|
+
disconnected_timeout: Some(Duration::from_secs(5)),
|
|
2048
|
+
failed_timeout: Some(Duration::ZERO),
|
|
2049
|
+
..Default::default()
|
|
2050
|
+
}),
|
|
2051
|
+
test_crypto_provider(),
|
|
2052
|
+
)?;
|
|
2053
|
+
|
|
2054
|
+
agent.connection_state = ConnectionState::Checking;
|
|
2055
|
+
agent.last_connection_state = ConnectionState::Checking;
|
|
2056
|
+
agent.checking_duration = base;
|
|
2057
|
+
agent.contact(base + Duration::from_secs(3600));
|
|
2058
|
+
|
|
2059
|
+
assert_eq!(
|
|
2060
|
+
agent.connection_state,
|
|
2061
|
+
ConnectionState::Checking,
|
|
2062
|
+
"a zero failed timeout is documented to disable the terminal transition"
|
|
2063
|
+
);
|
|
2064
|
+
Ok(())
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/// Test role conflict when both agents are controlling
|
|
2068
|
+
/// RFC 8445 Section 7.3.1.1 - Agent with smaller tiebreaker should switch to controlled
|
|
2069
|
+
#[test]
|
|
2070
|
+
fn test_role_conflict_both_controlling_smaller_tiebreaker_switches() -> Result<()> {
|
|
2071
|
+
// Create agent with controlling role
|
|
2072
|
+
let mut config = AgentConfig::default();
|
|
2073
|
+
config.is_controlling = true;
|
|
2074
|
+
let mut agent = Agent::new(Instant::now(), Arc::new(config), test_crypto_provider())?;
|
|
2075
|
+
|
|
2076
|
+
// Set a specific tiebreaker value
|
|
2077
|
+
agent.tie_breaker = 100;
|
|
2078
|
+
|
|
2079
|
+
// Add local candidate
|
|
2080
|
+
let host_config = CandidateHostConfig {
|
|
2081
|
+
base_config: CandidateConfig {
|
|
2082
|
+
network: "udp".to_owned(),
|
|
2083
|
+
address: "192.168.0.2".to_owned(),
|
|
2084
|
+
port: 7777,
|
|
2085
|
+
component: 1,
|
|
2086
|
+
..Default::default()
|
|
2087
|
+
},
|
|
2088
|
+
..Default::default()
|
|
2089
|
+
};
|
|
2090
|
+
let local_candidate = host_config.new_candidate_host()?;
|
|
2091
|
+
let local_index = 0;
|
|
2092
|
+
agent.add_local_candidate(local_candidate)?;
|
|
2093
|
+
|
|
2094
|
+
// Add remote candidate
|
|
2095
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:9999")?;
|
|
2096
|
+
let remote_host_config = CandidateHostConfig {
|
|
2097
|
+
base_config: CandidateConfig {
|
|
2098
|
+
network: "udp".to_owned(),
|
|
2099
|
+
address: "172.17.0.3".to_owned(),
|
|
2100
|
+
port: 9999,
|
|
2101
|
+
component: 1,
|
|
2102
|
+
..Default::default()
|
|
2103
|
+
},
|
|
2104
|
+
..Default::default()
|
|
2105
|
+
};
|
|
2106
|
+
let remote_candidate = remote_host_config.new_candidate_host()?;
|
|
2107
|
+
agent.add_remote_candidate(remote_candidate)?;
|
|
2108
|
+
|
|
2109
|
+
// Set remote credentials
|
|
2110
|
+
agent.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2111
|
+
ufrag: "remote_ufrag".to_string(),
|
|
2112
|
+
pwd: "remote_pwd".to_string(),
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
let username = agent.ufrag_pwd.local_credentials.ufrag.clone() + ":remote_ufrag";
|
|
2116
|
+
let local_pwd = agent.ufrag_pwd.local_credentials.pwd.clone();
|
|
2117
|
+
|
|
2118
|
+
// Create STUN binding request from remote agent (also controlling, with larger tiebreaker)
|
|
2119
|
+
let remote_tiebreaker = 200; // Larger than ours (100)
|
|
2120
|
+
let mut msg = Message::new();
|
|
2121
|
+
msg.build(&[
|
|
2122
|
+
Box::new(BINDING_REQUEST),
|
|
2123
|
+
Box::new(TransactionId::new()),
|
|
2124
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
2125
|
+
Box::new(AttrControlling(remote_tiebreaker)), // Remote is also controlling
|
|
2126
|
+
Box::new(PriorityAttr(1000)),
|
|
2127
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
2128
|
+
local_pwd,
|
|
2129
|
+
test_crypto_provider().crypto(),
|
|
2130
|
+
)),
|
|
2131
|
+
Box::new(FINGERPRINT),
|
|
2132
|
+
])?;
|
|
2133
|
+
|
|
2134
|
+
// Agent should be controlling before handling the message
|
|
2135
|
+
assert!(
|
|
2136
|
+
agent.is_controlling,
|
|
2137
|
+
"Agent should be controlling before role conflict"
|
|
2138
|
+
);
|
|
2139
|
+
|
|
2140
|
+
// Store initial pair count and priorities
|
|
2141
|
+
let initial_pair_count = agent.candidate_pairs.len();
|
|
2142
|
+
let initial_priorities: Vec<u64> = agent.candidate_pairs.iter().map(|p| p.priority()).collect();
|
|
2143
|
+
|
|
2144
|
+
// Handle the inbound message - should detect role conflict
|
|
2145
|
+
let result = agent.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr);
|
|
2146
|
+
|
|
2147
|
+
// The message should be processed (role conflict handling doesn't stop processing)
|
|
2148
|
+
// It may return an error for other reasons, but not for role conflict itself
|
|
2149
|
+
let _ = result; // We don't care about the exact error, just that role switching happened
|
|
2150
|
+
|
|
2151
|
+
// Agent should have switched to controlled (because our tiebreaker 100 < 200)
|
|
2152
|
+
assert!(
|
|
2153
|
+
!agent.is_controlling,
|
|
2154
|
+
"Agent should have switched to controlled role (smaller tiebreaker)"
|
|
2155
|
+
);
|
|
2156
|
+
|
|
2157
|
+
// Verify that candidate pair priorities were recomputed
|
|
2158
|
+
if !agent.candidate_pairs.is_empty() {
|
|
2159
|
+
// Check that ice_role_controlling was updated in pairs
|
|
2160
|
+
for pair in &agent.candidate_pairs {
|
|
2161
|
+
assert!(
|
|
2162
|
+
!pair.ice_role_controlling,
|
|
2163
|
+
"Candidate pair should have controlling role set to false"
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// Priorities should change when role changes (unless there were no pairs initially)
|
|
2168
|
+
if initial_pair_count > 0 && initial_priorities.len() > 0 {
|
|
2169
|
+
// At least verify the role was updated in pairs
|
|
2170
|
+
assert_eq!(
|
|
2171
|
+
agent.candidate_pairs.len(),
|
|
2172
|
+
initial_pair_count,
|
|
2173
|
+
"Number of pairs should remain the same"
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// Verify nominated pair was cleared
|
|
2179
|
+
assert!(
|
|
2180
|
+
agent.nominated_pair.is_none(),
|
|
2181
|
+
"Nominated pair should be cleared after role switch"
|
|
2182
|
+
);
|
|
2183
|
+
|
|
2184
|
+
agent.close()?;
|
|
2185
|
+
Ok(())
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/// Test role conflict when both agents are controlling
|
|
2189
|
+
/// RFC 8445 Section 7.3.1.1 - Agent with larger tiebreaker should keep controlling role
|
|
2190
|
+
#[test]
|
|
2191
|
+
fn test_role_conflict_both_controlling_larger_tiebreaker_stays() -> Result<()> {
|
|
2192
|
+
// Create agent with controlling role
|
|
2193
|
+
let mut config = AgentConfig::default();
|
|
2194
|
+
config.is_controlling = true;
|
|
2195
|
+
let mut agent = Agent::new(Instant::now(), Arc::new(config), test_crypto_provider())?;
|
|
2196
|
+
|
|
2197
|
+
// Set a larger tiebreaker value
|
|
2198
|
+
agent.tie_breaker = 500;
|
|
2199
|
+
|
|
2200
|
+
// Add local candidate
|
|
2201
|
+
let host_config = CandidateHostConfig {
|
|
2202
|
+
base_config: CandidateConfig {
|
|
2203
|
+
network: "udp".to_owned(),
|
|
2204
|
+
address: "192.168.0.2".to_owned(),
|
|
2205
|
+
port: 7777,
|
|
2206
|
+
component: 1,
|
|
2207
|
+
..Default::default()
|
|
2208
|
+
},
|
|
2209
|
+
..Default::default()
|
|
2210
|
+
};
|
|
2211
|
+
let local_candidate = host_config.new_candidate_host()?;
|
|
2212
|
+
let local_index = 0;
|
|
2213
|
+
agent.add_local_candidate(local_candidate)?;
|
|
2214
|
+
|
|
2215
|
+
// Add remote candidate
|
|
2216
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:9999")?;
|
|
2217
|
+
let remote_host_config = CandidateHostConfig {
|
|
2218
|
+
base_config: CandidateConfig {
|
|
2219
|
+
network: "udp".to_owned(),
|
|
2220
|
+
address: "172.17.0.3".to_owned(),
|
|
2221
|
+
port: 9999,
|
|
2222
|
+
component: 1,
|
|
2223
|
+
..Default::default()
|
|
2224
|
+
},
|
|
2225
|
+
..Default::default()
|
|
2226
|
+
};
|
|
2227
|
+
let remote_candidate = remote_host_config.new_candidate_host()?;
|
|
2228
|
+
agent.add_remote_candidate(remote_candidate)?;
|
|
2229
|
+
|
|
2230
|
+
// Set remote credentials
|
|
2231
|
+
agent.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2232
|
+
ufrag: "remote_ufrag".to_string(),
|
|
2233
|
+
pwd: "remote_pwd".to_string(),
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
let username = agent.ufrag_pwd.local_credentials.ufrag.clone() + ":remote_ufrag";
|
|
2237
|
+
let local_pwd = agent.ufrag_pwd.local_credentials.pwd.clone();
|
|
2238
|
+
|
|
2239
|
+
// Create STUN binding request from remote agent (also controlling, with smaller tiebreaker)
|
|
2240
|
+
let remote_tiebreaker = 100; // Smaller than ours (500)
|
|
2241
|
+
let mut msg = Message::new();
|
|
2242
|
+
msg.build(&[
|
|
2243
|
+
Box::new(BINDING_REQUEST),
|
|
2244
|
+
Box::new(TransactionId::new()),
|
|
2245
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
2246
|
+
Box::new(AttrControlling(remote_tiebreaker)), // Remote is also controlling
|
|
2247
|
+
Box::new(PriorityAttr(1000)),
|
|
2248
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
2249
|
+
local_pwd,
|
|
2250
|
+
test_crypto_provider().crypto(),
|
|
2251
|
+
)),
|
|
2252
|
+
Box::new(FINGERPRINT),
|
|
2253
|
+
])?;
|
|
2254
|
+
|
|
2255
|
+
// Agent should be controlling before handling the message
|
|
2256
|
+
assert!(
|
|
2257
|
+
agent.is_controlling,
|
|
2258
|
+
"Agent should be controlling before role conflict"
|
|
2259
|
+
);
|
|
2260
|
+
|
|
2261
|
+
// Handle the inbound message - should detect role conflict but NOT switch
|
|
2262
|
+
let _ = agent.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr);
|
|
2263
|
+
|
|
2264
|
+
// Agent should remain controlling (because our tiebreaker 500 > 100)
|
|
2265
|
+
assert!(
|
|
2266
|
+
agent.is_controlling,
|
|
2267
|
+
"Agent should remain controlling (larger tiebreaker)"
|
|
2268
|
+
);
|
|
2269
|
+
|
|
2270
|
+
// Verify candidate pairs still have controlling role
|
|
2271
|
+
for pair in &agent.candidate_pairs {
|
|
2272
|
+
assert!(
|
|
2273
|
+
pair.ice_role_controlling,
|
|
2274
|
+
"Candidate pair should still have controlling role set to true"
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
agent.close()?;
|
|
2279
|
+
Ok(())
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
/// Test role conflict when both agents are controlled
|
|
2283
|
+
/// RFC 8445 Section 7.3.1.1 - Agent with larger tiebreaker should switch to controlling
|
|
2284
|
+
#[test]
|
|
2285
|
+
fn test_role_conflict_both_controlled_larger_tiebreaker_switches() -> Result<()> {
|
|
2286
|
+
use crate::attributes::control::AttrControlled;
|
|
2287
|
+
|
|
2288
|
+
// Create agent with controlled role
|
|
2289
|
+
let mut config = AgentConfig::default();
|
|
2290
|
+
config.is_controlling = false; // Controlled
|
|
2291
|
+
let mut agent = Agent::new(Instant::now(), Arc::new(config), test_crypto_provider())?;
|
|
2292
|
+
|
|
2293
|
+
// Set a larger tiebreaker value
|
|
2294
|
+
agent.tie_breaker = 500;
|
|
2295
|
+
|
|
2296
|
+
// Add local candidate
|
|
2297
|
+
let host_config = CandidateHostConfig {
|
|
2298
|
+
base_config: CandidateConfig {
|
|
2299
|
+
network: "udp".to_owned(),
|
|
2300
|
+
address: "192.168.0.2".to_owned(),
|
|
2301
|
+
port: 7777,
|
|
2302
|
+
component: 1,
|
|
2303
|
+
..Default::default()
|
|
2304
|
+
},
|
|
2305
|
+
..Default::default()
|
|
2306
|
+
};
|
|
2307
|
+
let local_candidate = host_config.new_candidate_host()?;
|
|
2308
|
+
let local_index = 0;
|
|
2309
|
+
agent.add_local_candidate(local_candidate)?;
|
|
2310
|
+
|
|
2311
|
+
// Add remote candidate
|
|
2312
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:9999")?;
|
|
2313
|
+
let remote_host_config = CandidateHostConfig {
|
|
2314
|
+
base_config: CandidateConfig {
|
|
2315
|
+
network: "udp".to_owned(),
|
|
2316
|
+
address: "172.17.0.3".to_owned(),
|
|
2317
|
+
port: 9999,
|
|
2318
|
+
component: 1,
|
|
2319
|
+
..Default::default()
|
|
2320
|
+
},
|
|
2321
|
+
..Default::default()
|
|
2322
|
+
};
|
|
2323
|
+
let remote_candidate = remote_host_config.new_candidate_host()?;
|
|
2324
|
+
agent.add_remote_candidate(remote_candidate)?;
|
|
2325
|
+
|
|
2326
|
+
// Set remote credentials
|
|
2327
|
+
agent.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2328
|
+
ufrag: "remote_ufrag".to_string(),
|
|
2329
|
+
pwd: "remote_pwd".to_string(),
|
|
2330
|
+
});
|
|
2331
|
+
|
|
2332
|
+
let username = agent.ufrag_pwd.local_credentials.ufrag.clone() + ":remote_ufrag";
|
|
2333
|
+
let local_pwd = agent.ufrag_pwd.local_credentials.pwd.clone();
|
|
2334
|
+
|
|
2335
|
+
// Create STUN binding request from remote agent (also controlled, with smaller tiebreaker)
|
|
2336
|
+
let remote_tiebreaker = 100; // Smaller than ours (500)
|
|
2337
|
+
let mut msg = Message::new();
|
|
2338
|
+
msg.build(&[
|
|
2339
|
+
Box::new(BINDING_REQUEST),
|
|
2340
|
+
Box::new(TransactionId::new()),
|
|
2341
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
2342
|
+
Box::new(AttrControlled(remote_tiebreaker)), // Remote is also controlled
|
|
2343
|
+
Box::new(PriorityAttr(1000)),
|
|
2344
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
2345
|
+
local_pwd,
|
|
2346
|
+
test_crypto_provider().crypto(),
|
|
2347
|
+
)),
|
|
2348
|
+
Box::new(FINGERPRINT),
|
|
2349
|
+
])?;
|
|
2350
|
+
|
|
2351
|
+
// Agent should be controlled before handling the message
|
|
2352
|
+
assert!(
|
|
2353
|
+
!agent.is_controlling,
|
|
2354
|
+
"Agent should be controlled before role conflict"
|
|
2355
|
+
);
|
|
2356
|
+
|
|
2357
|
+
// Handle the inbound message - should detect role conflict
|
|
2358
|
+
let _ = agent.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr);
|
|
2359
|
+
|
|
2360
|
+
// Agent should have switched to controlling (because our tiebreaker 500 > 100)
|
|
2361
|
+
assert!(
|
|
2362
|
+
agent.is_controlling,
|
|
2363
|
+
"Agent should have switched to controlling role (larger tiebreaker)"
|
|
2364
|
+
);
|
|
2365
|
+
|
|
2366
|
+
// Verify that candidate pair priorities were recomputed
|
|
2367
|
+
for pair in &agent.candidate_pairs {
|
|
2368
|
+
assert!(
|
|
2369
|
+
pair.ice_role_controlling,
|
|
2370
|
+
"Candidate pair should have controlling role set to true"
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// Verify nominated pair was cleared
|
|
2375
|
+
assert!(
|
|
2376
|
+
agent.nominated_pair.is_none(),
|
|
2377
|
+
"Nominated pair should be cleared after role switch"
|
|
2378
|
+
);
|
|
2379
|
+
|
|
2380
|
+
agent.close()?;
|
|
2381
|
+
Ok(())
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
/// Test role conflict when both agents are controlled
|
|
2385
|
+
/// RFC 8445 Section 7.3.1.1 - Agent with smaller tiebreaker should stay controlled
|
|
2386
|
+
#[test]
|
|
2387
|
+
fn test_role_conflict_both_controlled_smaller_tiebreaker_stays() -> Result<()> {
|
|
2388
|
+
use crate::attributes::control::AttrControlled;
|
|
2389
|
+
|
|
2390
|
+
// Create agent with controlled role
|
|
2391
|
+
let mut config = AgentConfig::default();
|
|
2392
|
+
config.is_controlling = false; // Controlled
|
|
2393
|
+
let mut agent = Agent::new(Instant::now(), Arc::new(config), test_crypto_provider())?;
|
|
2394
|
+
|
|
2395
|
+
// Set a smaller tiebreaker value
|
|
2396
|
+
agent.tie_breaker = 100;
|
|
2397
|
+
|
|
2398
|
+
// Add local candidate
|
|
2399
|
+
let host_config = CandidateHostConfig {
|
|
2400
|
+
base_config: CandidateConfig {
|
|
2401
|
+
network: "udp".to_owned(),
|
|
2402
|
+
address: "192.168.0.2".to_owned(),
|
|
2403
|
+
port: 7777,
|
|
2404
|
+
component: 1,
|
|
2405
|
+
..Default::default()
|
|
2406
|
+
},
|
|
2407
|
+
..Default::default()
|
|
2408
|
+
};
|
|
2409
|
+
let local_candidate = host_config.new_candidate_host()?;
|
|
2410
|
+
let local_index = 0;
|
|
2411
|
+
agent.add_local_candidate(local_candidate)?;
|
|
2412
|
+
|
|
2413
|
+
// Add remote candidate
|
|
2414
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:9999")?;
|
|
2415
|
+
let remote_host_config = CandidateHostConfig {
|
|
2416
|
+
base_config: CandidateConfig {
|
|
2417
|
+
network: "udp".to_owned(),
|
|
2418
|
+
address: "172.17.0.3".to_owned(),
|
|
2419
|
+
port: 9999,
|
|
2420
|
+
component: 1,
|
|
2421
|
+
..Default::default()
|
|
2422
|
+
},
|
|
2423
|
+
..Default::default()
|
|
2424
|
+
};
|
|
2425
|
+
let remote_candidate = remote_host_config.new_candidate_host()?;
|
|
2426
|
+
agent.add_remote_candidate(remote_candidate)?;
|
|
2427
|
+
|
|
2428
|
+
// Set remote credentials
|
|
2429
|
+
agent.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2430
|
+
ufrag: "remote_ufrag".to_string(),
|
|
2431
|
+
pwd: "remote_pwd".to_string(),
|
|
2432
|
+
});
|
|
2433
|
+
|
|
2434
|
+
let username = agent.ufrag_pwd.local_credentials.ufrag.clone() + ":remote_ufrag";
|
|
2435
|
+
let local_pwd = agent.ufrag_pwd.local_credentials.pwd.clone();
|
|
2436
|
+
|
|
2437
|
+
// Create STUN binding request from remote agent (also controlled, with larger tiebreaker)
|
|
2438
|
+
let remote_tiebreaker = 500; // Larger than ours (100)
|
|
2439
|
+
let mut msg = Message::new();
|
|
2440
|
+
msg.build(&[
|
|
2441
|
+
Box::new(BINDING_REQUEST),
|
|
2442
|
+
Box::new(TransactionId::new()),
|
|
2443
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
2444
|
+
Box::new(AttrControlled(remote_tiebreaker)), // Remote is also controlled
|
|
2445
|
+
Box::new(PriorityAttr(1000)),
|
|
2446
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
2447
|
+
local_pwd,
|
|
2448
|
+
test_crypto_provider().crypto(),
|
|
2449
|
+
)),
|
|
2450
|
+
Box::new(FINGERPRINT),
|
|
2451
|
+
])?;
|
|
2452
|
+
|
|
2453
|
+
// Agent should be controlled before handling the message
|
|
2454
|
+
assert!(
|
|
2455
|
+
!agent.is_controlling,
|
|
2456
|
+
"Agent should be controlled before role conflict"
|
|
2457
|
+
);
|
|
2458
|
+
|
|
2459
|
+
// Handle the inbound message - should detect role conflict but NOT switch
|
|
2460
|
+
let _ = agent.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr);
|
|
2461
|
+
|
|
2462
|
+
// Agent should remain controlled (because our tiebreaker 100 < 500)
|
|
2463
|
+
assert!(
|
|
2464
|
+
!agent.is_controlling,
|
|
2465
|
+
"Agent should remain controlled (smaller tiebreaker)"
|
|
2466
|
+
);
|
|
2467
|
+
|
|
2468
|
+
// Verify candidate pairs still have controlled role
|
|
2469
|
+
for pair in &agent.candidate_pairs {
|
|
2470
|
+
assert!(
|
|
2471
|
+
!pair.ice_role_controlling,
|
|
2472
|
+
"Candidate pair should still have controlling role set to false"
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
agent.close()?;
|
|
2477
|
+
Ok(())
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
#[test]
|
|
2481
|
+
fn test_candidate_type_filtering() -> Result<()> {
|
|
2482
|
+
// Create an agent with only Relay candidate type allowed (simulates RTCIceTransportPolicy::Relay)
|
|
2483
|
+
let config = Arc::new(AgentConfig {
|
|
2484
|
+
candidate_types: vec![CandidateType::Relay],
|
|
2485
|
+
..Default::default()
|
|
2486
|
+
});
|
|
2487
|
+
let mut agent = Agent::new(Instant::now(), config, test_crypto_provider())?;
|
|
2488
|
+
|
|
2489
|
+
// Host local candidate should be rejected
|
|
2490
|
+
let host_local = CandidateHostConfig {
|
|
2491
|
+
base_config: CandidateConfig {
|
|
2492
|
+
network: "udp".to_owned(),
|
|
2493
|
+
address: "192.168.1.1".to_owned(),
|
|
2494
|
+
port: 5000,
|
|
2495
|
+
component: 1,
|
|
2496
|
+
..Default::default()
|
|
2497
|
+
},
|
|
2498
|
+
..Default::default()
|
|
2499
|
+
}
|
|
2500
|
+
.new_candidate_host()?;
|
|
2501
|
+
assert!(
|
|
2502
|
+
!agent.add_local_candidate(host_local)?,
|
|
2503
|
+
"Host local candidate should be filtered out"
|
|
2504
|
+
);
|
|
2505
|
+
|
|
2506
|
+
// Remote candidates of any type should still be accepted (relay policy only filters local)
|
|
2507
|
+
let host_remote = CandidateHostConfig {
|
|
2508
|
+
base_config: CandidateConfig {
|
|
2509
|
+
network: "udp".to_owned(),
|
|
2510
|
+
address: "192.168.1.3".to_owned(),
|
|
2511
|
+
port: 5003,
|
|
2512
|
+
component: 1,
|
|
2513
|
+
..Default::default()
|
|
2514
|
+
},
|
|
2515
|
+
..Default::default()
|
|
2516
|
+
}
|
|
2517
|
+
.new_candidate_host()?;
|
|
2518
|
+
assert!(
|
|
2519
|
+
agent.add_remote_candidate(host_remote)?,
|
|
2520
|
+
"Remote candidates should be accepted regardless of candidate_types"
|
|
2521
|
+
);
|
|
2522
|
+
|
|
2523
|
+
// Relay local candidate should be accepted
|
|
2524
|
+
let relay_local = CandidateRelayConfig {
|
|
2525
|
+
base_config: CandidateConfig {
|
|
2526
|
+
network: "udp".to_owned(),
|
|
2527
|
+
address: "1.2.3.4".to_owned(),
|
|
2528
|
+
port: 5004,
|
|
2529
|
+
component: 1,
|
|
2530
|
+
..Default::default()
|
|
2531
|
+
},
|
|
2532
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
2533
|
+
rel_port: 5005,
|
|
2534
|
+
..Default::default()
|
|
2535
|
+
}
|
|
2536
|
+
.new_candidate_relay()?;
|
|
2537
|
+
assert!(
|
|
2538
|
+
agent.add_local_candidate(relay_local)?,
|
|
2539
|
+
"Relay local candidate should be accepted"
|
|
2540
|
+
);
|
|
2541
|
+
|
|
2542
|
+
assert_eq!(
|
|
2543
|
+
agent.local_candidates.len(),
|
|
2544
|
+
1,
|
|
2545
|
+
"Only the relay candidate should be stored"
|
|
2546
|
+
);
|
|
2547
|
+
assert_eq!(
|
|
2548
|
+
agent.remote_candidates.len(),
|
|
2549
|
+
1,
|
|
2550
|
+
"Remote host candidate should be stored"
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
agent.close()?;
|
|
2554
|
+
Ok(())
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// Verify that check_keepalive sends a STUN ping even when media traffic has
|
|
2558
|
+
// recently updated the candidate timestamps (RFC 7675).
|
|
2559
|
+
#[test]
|
|
2560
|
+
fn test_keepalive_sent_during_media_flow() -> Result<()> {
|
|
2561
|
+
let mut a = Agent::new(
|
|
2562
|
+
Instant::now(),
|
|
2563
|
+
Arc::new(AgentConfig::default()),
|
|
2564
|
+
test_crypto_provider(),
|
|
2565
|
+
)?;
|
|
2566
|
+
|
|
2567
|
+
// Set up a selected pair
|
|
2568
|
+
let host_local = CandidateHostConfig {
|
|
2569
|
+
base_config: CandidateConfig {
|
|
2570
|
+
network: "udp".to_owned(),
|
|
2571
|
+
address: "192.168.1.1".to_owned(),
|
|
2572
|
+
port: 19216,
|
|
2573
|
+
component: 1,
|
|
2574
|
+
..Default::default()
|
|
2575
|
+
},
|
|
2576
|
+
..Default::default()
|
|
2577
|
+
}
|
|
2578
|
+
.new_candidate_host()?;
|
|
2579
|
+
a.add_local_candidate(host_local)?;
|
|
2580
|
+
|
|
2581
|
+
let relay_remote = CandidateRelayConfig {
|
|
2582
|
+
base_config: CandidateConfig {
|
|
2583
|
+
network: "udp".to_owned(),
|
|
2584
|
+
address: "1.2.3.4".to_owned(),
|
|
2585
|
+
port: 12340,
|
|
2586
|
+
component: 1,
|
|
2587
|
+
..Default::default()
|
|
2588
|
+
},
|
|
2589
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
2590
|
+
rel_port: 43210,
|
|
2591
|
+
..Default::default()
|
|
2592
|
+
}
|
|
2593
|
+
.new_candidate_relay()?;
|
|
2594
|
+
a.add_remote_candidate(relay_remote)?;
|
|
2595
|
+
|
|
2596
|
+
a.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2597
|
+
ufrag: "remoteufrag".to_string(),
|
|
2598
|
+
pwd: "remotepwd".to_string(),
|
|
2599
|
+
});
|
|
2600
|
+
a.is_controlling = true;
|
|
2601
|
+
|
|
2602
|
+
a.add_pair(0, 0);
|
|
2603
|
+
a.set_selected_pair(Some(Instant::now()), Some(0));
|
|
2604
|
+
|
|
2605
|
+
// Simulate recent media activity on both candidates
|
|
2606
|
+
a.local_candidates[0].seen(Instant::now(), true);
|
|
2607
|
+
a.remote_candidates[0].seen(Instant::now(), false);
|
|
2608
|
+
|
|
2609
|
+
// Virtual time: the agent is told the instant, so nothing here sleeps or samples a clock.
|
|
2610
|
+
let base = Instant::now();
|
|
2611
|
+
let keepalive_interval = a.keepalive_interval;
|
|
2612
|
+
|
|
2613
|
+
// Pretend the last consent ping was long ago such that the interval has elapsed
|
|
2614
|
+
a.last_consent_sent = base - Duration::from_secs(10);
|
|
2615
|
+
|
|
2616
|
+
// Drain any events/writes from setup
|
|
2617
|
+
a.write_outs.clear();
|
|
2618
|
+
|
|
2619
|
+
// First call should send a consent ping immediately
|
|
2620
|
+
a.check_keepalive(base);
|
|
2621
|
+
assert!(
|
|
2622
|
+
!a.write_outs.is_empty(),
|
|
2623
|
+
"check_keepalive must send a STUN ping even when media timestamps are fresh"
|
|
2624
|
+
);
|
|
2625
|
+
|
|
2626
|
+
// Drain and call again at the *same* instant; the interval has not elapsed, so no second ping.
|
|
2627
|
+
// Before clock injection this relied on the wall clock not advancing between two calls.
|
|
2628
|
+
a.write_outs.clear();
|
|
2629
|
+
a.check_keepalive(base);
|
|
2630
|
+
assert!(
|
|
2631
|
+
a.write_outs.is_empty(),
|
|
2632
|
+
"check_keepalive must not send again before keepalive_interval elapses"
|
|
2633
|
+
);
|
|
2634
|
+
|
|
2635
|
+
// Advance virtual time past the interval: the next ping goes out, instantly and reproducibly.
|
|
2636
|
+
a.check_keepalive(base + keepalive_interval + Duration::from_millis(1));
|
|
2637
|
+
assert!(
|
|
2638
|
+
!a.write_outs.is_empty(),
|
|
2639
|
+
"check_keepalive must send again once keepalive_interval has elapsed"
|
|
2640
|
+
);
|
|
2641
|
+
|
|
2642
|
+
a.close()?;
|
|
2643
|
+
Ok(())
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
#[test]
|
|
2647
|
+
fn test_pair_network_type_mismatch() -> Result<()> {
|
|
2648
|
+
let mut a = Agent::new(
|
|
2649
|
+
Instant::now(),
|
|
2650
|
+
Arc::new(AgentConfig::default()),
|
|
2651
|
+
test_crypto_provider(),
|
|
2652
|
+
)?;
|
|
2653
|
+
|
|
2654
|
+
// UDP: IPv4 local should not pair with IPv6 remote.
|
|
2655
|
+
let local_v4 = CandidateHostConfig {
|
|
2656
|
+
base_config: CandidateConfig {
|
|
2657
|
+
network: "udp".to_owned(),
|
|
2658
|
+
address: "192.168.1.1".to_owned(),
|
|
2659
|
+
port: 7777,
|
|
2660
|
+
component: 1,
|
|
2661
|
+
..Default::default()
|
|
2662
|
+
},
|
|
2663
|
+
..Default::default()
|
|
2664
|
+
}
|
|
2665
|
+
.new_candidate_host()?;
|
|
2666
|
+
a.add_local_candidate(local_v4)?;
|
|
2667
|
+
|
|
2668
|
+
let remote_v6 = CandidateHostConfig {
|
|
2669
|
+
base_config: CandidateConfig {
|
|
2670
|
+
network: "udp".to_owned(),
|
|
2671
|
+
address: "2001:db8::1".to_owned(),
|
|
2672
|
+
port: 8888,
|
|
2673
|
+
component: 1,
|
|
2674
|
+
..Default::default()
|
|
2675
|
+
},
|
|
2676
|
+
..Default::default()
|
|
2677
|
+
}
|
|
2678
|
+
.new_candidate_host()?;
|
|
2679
|
+
a.add_remote_candidate(remote_v6)?;
|
|
2680
|
+
|
|
2681
|
+
assert!(
|
|
2682
|
+
a.candidate_pairs.is_empty(),
|
|
2683
|
+
"IPv4 local and IPv6 remote candidates should not be paired"
|
|
2684
|
+
);
|
|
2685
|
+
|
|
2686
|
+
// UDP: matching IPv4 should pair.
|
|
2687
|
+
let remote_v4 = CandidateHostConfig {
|
|
2688
|
+
base_config: CandidateConfig {
|
|
2689
|
+
network: "udp".to_owned(),
|
|
2690
|
+
address: "192.168.1.2".to_owned(),
|
|
2691
|
+
port: 8888,
|
|
2692
|
+
component: 1,
|
|
2693
|
+
..Default::default()
|
|
2694
|
+
},
|
|
2695
|
+
..Default::default()
|
|
2696
|
+
}
|
|
2697
|
+
.new_candidate_host()?;
|
|
2698
|
+
a.add_remote_candidate(remote_v4)?;
|
|
2699
|
+
|
|
2700
|
+
assert_eq!(
|
|
2701
|
+
a.candidate_pairs.len(),
|
|
2702
|
+
1,
|
|
2703
|
+
"IPv4 local and IPv4 remote candidates should form exactly one pair"
|
|
2704
|
+
);
|
|
2705
|
+
|
|
2706
|
+
// TCP: IPv4 active local should not pair with IPv6 passive remote.
|
|
2707
|
+
let local_tcp4_active = CandidateHostConfig {
|
|
2708
|
+
base_config: CandidateConfig {
|
|
2709
|
+
network: "tcp".to_owned(),
|
|
2710
|
+
address: "192.168.1.3".to_owned(),
|
|
2711
|
+
port: 7778,
|
|
2712
|
+
component: 1,
|
|
2713
|
+
..Default::default()
|
|
2714
|
+
},
|
|
2715
|
+
tcp_type: TcpType::Active,
|
|
2716
|
+
}
|
|
2717
|
+
.new_candidate_host()?;
|
|
2718
|
+
a.add_local_candidate(local_tcp4_active)?;
|
|
2719
|
+
|
|
2720
|
+
let remote_tcp6_passive = CandidateHostConfig {
|
|
2721
|
+
base_config: CandidateConfig {
|
|
2722
|
+
network: "tcp".to_owned(),
|
|
2723
|
+
address: "2001:db8::2".to_owned(),
|
|
2724
|
+
port: 8889,
|
|
2725
|
+
component: 1,
|
|
2726
|
+
..Default::default()
|
|
2727
|
+
},
|
|
2728
|
+
tcp_type: TcpType::Passive,
|
|
2729
|
+
}
|
|
2730
|
+
.new_candidate_host()?;
|
|
2731
|
+
a.add_remote_candidate(remote_tcp6_passive)?;
|
|
2732
|
+
|
|
2733
|
+
assert_eq!(
|
|
2734
|
+
a.candidate_pairs.len(),
|
|
2735
|
+
1,
|
|
2736
|
+
"TCP4 active local and TCP6 passive remote should not create an additional pair"
|
|
2737
|
+
);
|
|
2738
|
+
|
|
2739
|
+
// TCP: matching IPv4 active/passive should pair.
|
|
2740
|
+
let remote_tcp4_passive = CandidateHostConfig {
|
|
2741
|
+
base_config: CandidateConfig {
|
|
2742
|
+
network: "tcp".to_owned(),
|
|
2743
|
+
address: "192.168.1.4".to_owned(),
|
|
2744
|
+
port: 8890,
|
|
2745
|
+
component: 1,
|
|
2746
|
+
..Default::default()
|
|
2747
|
+
},
|
|
2748
|
+
tcp_type: TcpType::Passive,
|
|
2749
|
+
}
|
|
2750
|
+
.new_candidate_host()?;
|
|
2751
|
+
a.add_remote_candidate(remote_tcp4_passive)?;
|
|
2752
|
+
|
|
2753
|
+
assert_eq!(
|
|
2754
|
+
a.candidate_pairs.len(),
|
|
2755
|
+
2,
|
|
2756
|
+
"TCP4 active local and TCP4 passive remote should form exactly one pair"
|
|
2757
|
+
);
|
|
2758
|
+
|
|
2759
|
+
a.close()?;
|
|
2760
|
+
Ok(())
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// Regression test for https://github.com/webrtc-rs/rtc/issues/88 (Issue 1).
|
|
2764
|
+
//
|
|
2765
|
+
// When the agent transitions to `Failed` it calls `delete_all_candidates`, which
|
|
2766
|
+
// empties `local_candidates`/`remote_candidates`. Previously the candidate pairs -
|
|
2767
|
+
// and the `selected_pair`/`nominated_pair` indices into them - were left behind, so
|
|
2768
|
+
// they referenced candidates that no longer existed. Any later access through a pair,
|
|
2769
|
+
// e.g. `get_best_available_candidate_pair`, then indexed out of bounds and panicked:
|
|
2770
|
+
//
|
|
2771
|
+
// thread '...' panicked at 'index out of bounds: the len is 0 but the index is 0'
|
|
2772
|
+
#[test]
|
|
2773
|
+
fn test_transition_to_failed_clears_stale_candidate_pairs() -> Result<()> {
|
|
2774
|
+
let mut a = Agent::new(
|
|
2775
|
+
Instant::now(),
|
|
2776
|
+
Arc::new(AgentConfig::default()),
|
|
2777
|
+
test_crypto_provider(),
|
|
2778
|
+
)?;
|
|
2779
|
+
|
|
2780
|
+
let local = CandidateHostConfig {
|
|
2781
|
+
base_config: CandidateConfig {
|
|
2782
|
+
network: "udp".to_owned(),
|
|
2783
|
+
address: "192.168.0.2".to_owned(),
|
|
2784
|
+
port: 777,
|
|
2785
|
+
component: 1,
|
|
2786
|
+
..Default::default()
|
|
2787
|
+
},
|
|
2788
|
+
..Default::default()
|
|
2789
|
+
}
|
|
2790
|
+
.new_candidate_host()?;
|
|
2791
|
+
a.add_local_candidate(local)?;
|
|
2792
|
+
|
|
2793
|
+
let remote = CandidateHostConfig {
|
|
2794
|
+
base_config: CandidateConfig {
|
|
2795
|
+
network: "udp".to_owned(),
|
|
2796
|
+
address: "172.17.0.3".to_owned(),
|
|
2797
|
+
port: 999,
|
|
2798
|
+
component: 1,
|
|
2799
|
+
..Default::default()
|
|
2800
|
+
},
|
|
2801
|
+
..Default::default()
|
|
2802
|
+
}
|
|
2803
|
+
.new_candidate_host()?;
|
|
2804
|
+
a.add_remote_candidate(remote)?;
|
|
2805
|
+
|
|
2806
|
+
// A single local/remote pair now exists; mark it selected and nominated so that
|
|
2807
|
+
// both index-into-pairs fields are populated.
|
|
2808
|
+
assert_eq!(a.candidate_pairs.len(), 1);
|
|
2809
|
+
a.set_selected_pair(Some(Instant::now()), Some(0));
|
|
2810
|
+
a.nominated_pair = Some(0);
|
|
2811
|
+
|
|
2812
|
+
// Transition to Failed: this deletes all candidates.
|
|
2813
|
+
a.update_connection_state(Some(Instant::now()), ConnectionState::Failed);
|
|
2814
|
+
|
|
2815
|
+
// The pairs and the indices into them must be gone, otherwise they dangle.
|
|
2816
|
+
assert!(
|
|
2817
|
+
a.candidate_pairs.is_empty(),
|
|
2818
|
+
"candidate pairs must be cleared when candidates are deleted"
|
|
2819
|
+
);
|
|
2820
|
+
assert!(a.selected_pair.is_none(), "selected_pair must be reset");
|
|
2821
|
+
assert!(a.nominated_pair.is_none(), "nominated_pair must be reset");
|
|
2822
|
+
assert!(a.local_candidates.is_empty());
|
|
2823
|
+
assert!(a.remote_candidates.is_empty());
|
|
2824
|
+
|
|
2825
|
+
// Before the fix these accessors dereferenced stale pair indices into the emptied
|
|
2826
|
+
// candidate vectors and panicked; now they must simply return `None`.
|
|
2827
|
+
assert!(a.get_selected_candidate_pair().is_none());
|
|
2828
|
+
assert!(a.get_best_available_candidate_pair().is_none());
|
|
2829
|
+
|
|
2830
|
+
a.close()?;
|
|
2831
|
+
Ok(())
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// Regression test for https://github.com/webrtc-rs/rtc/issues/88 (Issue 2).
|
|
2835
|
+
//
|
|
2836
|
+
// Handling an inbound binding request from an unknown address adds a peer-reflexive
|
|
2837
|
+
// remote candidate. Previously `add_remote_candidate` ran the connectivity check
|
|
2838
|
+
// inline, which could advance the state to `Failed` and clear `remote_candidates`
|
|
2839
|
+
// mid-call. The caller then computed `remote_candidates.len() - 1` on an empty vector
|
|
2840
|
+
// and panicked ('attempt to subtract with overflow' in debug builds). Deferring the
|
|
2841
|
+
// check to `handle_timeout` keeps candidate mutation free of surprising re-entrant
|
|
2842
|
+
// state changes.
|
|
2843
|
+
#[test]
|
|
2844
|
+
fn test_handle_inbound_request_defers_failing_connectivity_check() -> Result<()> {
|
|
2845
|
+
use sansio::Protocol as _;
|
|
2846
|
+
|
|
2847
|
+
let cfg = AgentConfig {
|
|
2848
|
+
disconnected_timeout: Some(Duration::from_secs(1)),
|
|
2849
|
+
failed_timeout: Some(Duration::from_secs(1)),
|
|
2850
|
+
..Default::default()
|
|
2851
|
+
};
|
|
2852
|
+
let mut a = Agent::new(Instant::now(), Arc::new(cfg), test_crypto_provider())?;
|
|
2853
|
+
|
|
2854
|
+
let local_candidate = CandidateHostConfig {
|
|
2855
|
+
base_config: CandidateConfig {
|
|
2856
|
+
network: "udp".to_owned(),
|
|
2857
|
+
address: "192.168.0.2".to_owned(),
|
|
2858
|
+
port: 777,
|
|
2859
|
+
component: 1,
|
|
2860
|
+
..Default::default()
|
|
2861
|
+
},
|
|
2862
|
+
..Default::default()
|
|
2863
|
+
}
|
|
2864
|
+
.new_candidate_host()?;
|
|
2865
|
+
let local_index = 0;
|
|
2866
|
+
let local_priority = local_candidate.priority();
|
|
2867
|
+
a.add_local_candidate(local_candidate)?;
|
|
2868
|
+
|
|
2869
|
+
a.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
2870
|
+
ufrag: "".to_string(),
|
|
2871
|
+
pwd: "".to_string(),
|
|
2872
|
+
});
|
|
2873
|
+
let username = a.ufrag_pwd.local_credentials.ufrag.clone() + ":";
|
|
2874
|
+
let local_pwd = a.ufrag_pwd.local_credentials.pwd.clone();
|
|
2875
|
+
let tie_breaker = a.tie_breaker;
|
|
2876
|
+
|
|
2877
|
+
// Put the agent in a state where running a connectivity check now would time out
|
|
2878
|
+
// and transition to `Failed` (which deletes all candidates).
|
|
2879
|
+
a.connection_state = ConnectionState::Checking;
|
|
2880
|
+
a.last_connection_state = ConnectionState::Checking;
|
|
2881
|
+
a.checking_duration = Instant::now()
|
|
2882
|
+
.checked_sub(Duration::from_secs(3600))
|
|
2883
|
+
.unwrap_or(a.start_time);
|
|
2884
|
+
|
|
2885
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:999")?;
|
|
2886
|
+
let mut msg = Message::new();
|
|
2887
|
+
msg.build(&[
|
|
2888
|
+
Box::new(BINDING_REQUEST),
|
|
2889
|
+
Box::new(TransactionId::new()),
|
|
2890
|
+
Box::new(Username::new(ATTR_USERNAME, username)),
|
|
2891
|
+
Box::new(UseCandidateAttr::new()),
|
|
2892
|
+
Box::new(AttrControlling(tie_breaker)),
|
|
2893
|
+
Box::new(PriorityAttr(local_priority)),
|
|
2894
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
2895
|
+
local_pwd,
|
|
2896
|
+
test_crypto_provider().crypto(),
|
|
2897
|
+
)),
|
|
2898
|
+
Box::new(FINGERPRINT),
|
|
2899
|
+
])?;
|
|
2900
|
+
|
|
2901
|
+
// Before the fix this call panicked while adding the peer-reflexive candidate.
|
|
2902
|
+
a.handle_inbound(Instant::now(), &mut msg, local_index, remote_addr)?;
|
|
2903
|
+
|
|
2904
|
+
// The candidate was added and no re-entrant state change happened during the call.
|
|
2905
|
+
assert_eq!(
|
|
2906
|
+
a.remote_candidates.len(),
|
|
2907
|
+
1,
|
|
2908
|
+
"peer-reflexive remote candidate should have been added"
|
|
2909
|
+
);
|
|
2910
|
+
assert_eq!(
|
|
2911
|
+
a.connection_state,
|
|
2912
|
+
ConnectionState::Checking,
|
|
2913
|
+
"connection state must not change while handling the inbound request"
|
|
2914
|
+
);
|
|
2915
|
+
assert!(
|
|
2916
|
+
a.force_candidate_contact,
|
|
2917
|
+
"a connectivity check should have been deferred to the timeout handler"
|
|
2918
|
+
);
|
|
2919
|
+
|
|
2920
|
+
// The deferred check now runs via the timeout handler, fails, and cleans up fully.
|
|
2921
|
+
a.handle_timeout(Instant::now())?;
|
|
2922
|
+
assert_eq!(a.connection_state, ConnectionState::Failed);
|
|
2923
|
+
assert!(a.candidate_pairs.is_empty());
|
|
2924
|
+
assert!(a.local_candidates.is_empty());
|
|
2925
|
+
assert!(a.remote_candidates.is_empty());
|
|
2926
|
+
assert!(a.selected_pair.is_none());
|
|
2927
|
+
assert!(a.nominated_pair.is_none());
|
|
2928
|
+
|
|
2929
|
+
a.close()?;
|
|
2930
|
+
Ok(())
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// Safari (and Chrome) expose ICE host candidates behind an mDNS "<uuid>.local"
|
|
2934
|
+
// hostname by default. A QueryOnly agent (the default) must RESOLVE such a
|
|
2935
|
+
// candidate -- i.e. issue an mDNS query, which surfaces as an outbound packet to
|
|
2936
|
+
// MDNS_PORT -- whereas a Disabled agent silently drops it. This guards the
|
|
2937
|
+
// behavioral consequence of defaulting the SettingEngine mDNS mode to QueryOnly.
|
|
2938
|
+
//
|
|
2939
|
+
// `add_remote_candidate` takes no instant, so it only *schedules* the query; the
|
|
2940
|
+
// packet is stamped and emitted by the next `handle_timeout`, which does have one.
|
|
2941
|
+
// `poll_timeout` reports an already-due deadline so the driver comes back at once.
|
|
2942
|
+
#[test]
|
|
2943
|
+
fn test_query_only_agent_queries_mdns_remote_candidate() -> Result<()> {
|
|
2944
|
+
// A .local host candidate in Safari's exact format.
|
|
2945
|
+
let cand_line =
|
|
2946
|
+
"1114572465 1 udp 2113939711 61b445d2-6503-41ac-96ce-ee3edac00e9f.local 61163 typ host";
|
|
2947
|
+
|
|
2948
|
+
let base = Instant::now();
|
|
2949
|
+
|
|
2950
|
+
// QueryOnly: adding the candidate issues an mDNS query (not a drop).
|
|
2951
|
+
let mut agent = Agent::new(
|
|
2952
|
+
base,
|
|
2953
|
+
Arc::new(AgentConfig {
|
|
2954
|
+
multicast_dns_mode: crate::mdns::MulticastDnsMode::QueryOnly,
|
|
2955
|
+
..Default::default()
|
|
2956
|
+
}),
|
|
2957
|
+
test_crypto_provider(),
|
|
2958
|
+
)?;
|
|
2959
|
+
let added = agent.add_remote_candidate(unmarshal_candidate(cand_line)?)?;
|
|
2960
|
+
assert!(
|
|
2961
|
+
!added,
|
|
2962
|
+
"an mDNS candidate is not immediately usable; resolution is async"
|
|
2963
|
+
);
|
|
2964
|
+
let deadline = agent
|
|
2965
|
+
.poll_timeout()
|
|
2966
|
+
.expect("a scheduled mDNS query must ask the driver for a timeout");
|
|
2967
|
+
assert!(
|
|
2968
|
+
deadline <= base,
|
|
2969
|
+
"the scheduled query is already due, so the driver runs it immediately"
|
|
2970
|
+
);
|
|
2971
|
+
agent.handle_timeout(deadline)?;
|
|
2972
|
+
let pkt = agent
|
|
2973
|
+
.poll_write()
|
|
2974
|
+
.expect("QueryOnly must emit an mDNS query for a .local remote candidate");
|
|
2975
|
+
assert_eq!(
|
|
2976
|
+
pkt.transport.peer_addr.port(),
|
|
2977
|
+
mdns::MDNS_PORT,
|
|
2978
|
+
"the emitted packet must be an mDNS query"
|
|
2979
|
+
);
|
|
2980
|
+
assert_eq!(
|
|
2981
|
+
pkt.now, deadline,
|
|
2982
|
+
"the query is stamped with the instant the driver supplied, not an ambient one"
|
|
2983
|
+
);
|
|
2984
|
+
|
|
2985
|
+
// Disabled: the same candidate is silently dropped -- no mDNS query.
|
|
2986
|
+
let mut agent = Agent::new(
|
|
2987
|
+
base,
|
|
2988
|
+
Arc::new(AgentConfig {
|
|
2989
|
+
multicast_dns_mode: crate::mdns::MulticastDnsMode::Disabled,
|
|
2990
|
+
..Default::default()
|
|
2991
|
+
}),
|
|
2992
|
+
test_crypto_provider(),
|
|
2993
|
+
)?;
|
|
2994
|
+
agent.add_remote_candidate(unmarshal_candidate(cand_line)?)?;
|
|
2995
|
+
assert!(
|
|
2996
|
+
agent.poll_write().is_none(),
|
|
2997
|
+
"Disabled must not emit an mDNS query for a .local remote candidate"
|
|
2998
|
+
);
|
|
2999
|
+
|
|
3000
|
+
Ok(())
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
/// A terminal ICE agent must not keep advertising its last connectivity-check deadline.
|
|
3004
|
+
///
|
|
3005
|
+
/// `contact` intentionally returns without updating `last_checking_time` after the agent fails.
|
|
3006
|
+
/// If `poll_timeout` continues to derive a deadline from that value, every call after failure
|
|
3007
|
+
/// returns the same instant in the past and a Sans-I/O driver can spin indefinitely.
|
|
3008
|
+
#[test]
|
|
3009
|
+
fn test_failed_agent_stops_and_restart_resumes_connectivity_check_timer() -> Result<()> {
|
|
3010
|
+
let base = Instant::now();
|
|
3011
|
+
let mut agent = Agent::new(
|
|
3012
|
+
base,
|
|
3013
|
+
Arc::new(AgentConfig {
|
|
3014
|
+
multicast_dns_mode: crate::mdns::MulticastDnsMode::Disabled,
|
|
3015
|
+
..Default::default()
|
|
3016
|
+
}),
|
|
3017
|
+
test_crypto_provider(),
|
|
3018
|
+
)?;
|
|
3019
|
+
|
|
3020
|
+
agent.start_connectivity_checks(
|
|
3021
|
+
base,
|
|
3022
|
+
true,
|
|
3023
|
+
"remote-ufrag".to_owned(),
|
|
3024
|
+
"remote-password".to_owned(),
|
|
3025
|
+
)?;
|
|
3026
|
+
agent.update_connection_state(
|
|
3027
|
+
Some(base + Duration::from_secs(190)),
|
|
3028
|
+
ConnectionState::Failed,
|
|
3029
|
+
);
|
|
3030
|
+
|
|
3031
|
+
assert_eq!(
|
|
3032
|
+
agent.poll_timeout(),
|
|
3033
|
+
None,
|
|
3034
|
+
"a failed agent has no more connectivity checks to schedule"
|
|
3035
|
+
);
|
|
3036
|
+
agent.handle_timeout(base + Duration::from_secs(191))?;
|
|
3037
|
+
assert_eq!(
|
|
3038
|
+
agent.poll_timeout(),
|
|
3039
|
+
None,
|
|
3040
|
+
"handling time after failure must not recreate the stale deadline"
|
|
3041
|
+
);
|
|
3042
|
+
|
|
3043
|
+
let restart_time = base + Duration::from_secs(200);
|
|
3044
|
+
agent.apply_restart(restart_time, true)?;
|
|
3045
|
+
agent.start_connectivity_checks(
|
|
3046
|
+
restart_time,
|
|
3047
|
+
true,
|
|
3048
|
+
"new-remote-ufrag".to_owned(),
|
|
3049
|
+
"new-remote-password".to_owned(),
|
|
3050
|
+
)?;
|
|
3051
|
+
|
|
3052
|
+
assert!(
|
|
3053
|
+
agent.poll_timeout().is_some(),
|
|
3054
|
+
"an ICE restart must resume connectivity-check scheduling"
|
|
3055
|
+
);
|
|
3056
|
+
|
|
3057
|
+
agent.close()?;
|
|
3058
|
+
assert_eq!(
|
|
3059
|
+
agent.poll_timeout(),
|
|
3060
|
+
None,
|
|
3061
|
+
"a closed agent has no more connectivity checks to schedule"
|
|
3062
|
+
);
|
|
3063
|
+
|
|
3064
|
+
Ok(())
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
/// Regression test: connectivity checks for a server-reflexive local candidate
|
|
3068
|
+
/// must be tagged with the candidate's base (the bound host socket), not the
|
|
3069
|
+
/// NAT-mapped srflx address (RFC 8445 §6.1.2). Drivers that route outbound
|
|
3070
|
+
/// transmits by `transport.local_addr` otherwise have no socket to send from
|
|
3071
|
+
/// and drop the packet.
|
|
3072
|
+
#[test]
|
|
3073
|
+
fn test_send_stun_from_srflx_uses_base_addr() -> Result<()> {
|
|
3074
|
+
let mut a = Agent::new(
|
|
3075
|
+
Instant::now(),
|
|
3076
|
+
Arc::new(AgentConfig::default()),
|
|
3077
|
+
test_crypto_provider(),
|
|
3078
|
+
)?;
|
|
3079
|
+
|
|
3080
|
+
let srflx_local = CandidateServerReflexiveConfig {
|
|
3081
|
+
base_config: CandidateConfig {
|
|
3082
|
+
network: "udp".to_owned(),
|
|
3083
|
+
address: "10.79.12.1".to_owned(), // NAT mapping; no local socket here
|
|
3084
|
+
port: 60823,
|
|
3085
|
+
component: 1,
|
|
3086
|
+
..Default::default()
|
|
3087
|
+
},
|
|
3088
|
+
rel_addr: "192.168.0.2".to_owned(), // base: bound host socket
|
|
3089
|
+
rel_port: 5000,
|
|
3090
|
+
..Default::default()
|
|
3091
|
+
}
|
|
3092
|
+
.new_candidate_server_reflexive()?;
|
|
3093
|
+
a.add_local_candidate(srflx_local)?;
|
|
3094
|
+
|
|
3095
|
+
let host_remote = CandidateHostConfig {
|
|
3096
|
+
base_config: CandidateConfig {
|
|
3097
|
+
network: "udp".to_owned(),
|
|
3098
|
+
address: "10.79.11.1".to_owned(),
|
|
3099
|
+
port: 37983,
|
|
3100
|
+
component: 1,
|
|
3101
|
+
..Default::default()
|
|
3102
|
+
},
|
|
3103
|
+
..Default::default()
|
|
3104
|
+
}
|
|
3105
|
+
.new_candidate_host()?;
|
|
3106
|
+
a.add_remote_candidate(host_remote)?;
|
|
3107
|
+
|
|
3108
|
+
a.write_outs.clear();
|
|
3109
|
+
|
|
3110
|
+
let msg = Message::new();
|
|
3111
|
+
a.send_stun(Instant::now(), &msg, 0, 0);
|
|
3112
|
+
|
|
3113
|
+
let transmit = a.write_outs.pop_front().expect("send_stun must emit");
|
|
3114
|
+
assert_eq!(
|
|
3115
|
+
transmit.transport.local_addr,
|
|
3116
|
+
"192.168.0.2:5000".parse().unwrap(),
|
|
3117
|
+
"srflx checks must be sent from the candidate's base, not the mapped address"
|
|
3118
|
+
);
|
|
3119
|
+
assert_eq!(
|
|
3120
|
+
transmit.transport.peer_addr,
|
|
3121
|
+
"10.79.11.1:37983".parse().unwrap()
|
|
3122
|
+
);
|
|
3123
|
+
|
|
3124
|
+
a.close()?;
|
|
3125
|
+
Ok(())
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/// Staging an ICE restart must not disturb the live session.
|
|
3129
|
+
///
|
|
3130
|
+
/// JSEP requires `createOffer` to be free of side effects, so the credentials an ICE-restart offer
|
|
3131
|
+
/// advertises are generated but *not* installed until the local description is applied. If they
|
|
3132
|
+
/// were installed at offer time, inbound STUN would immediately start failing its USERNAME and
|
|
3133
|
+
/// MESSAGE-INTEGRITY checks against the new pair — and permanently so if the offer were discarded,
|
|
3134
|
+
/// which JSEP explicitly permits.
|
|
3135
|
+
#[test]
|
|
3136
|
+
fn test_staged_ice_restart_keeps_live_session_authenticating() -> Result<()> {
|
|
3137
|
+
let mut a = Agent::new(
|
|
3138
|
+
Instant::now(),
|
|
3139
|
+
Arc::new(AgentConfig::default()),
|
|
3140
|
+
test_crypto_provider(),
|
|
3141
|
+
)?;
|
|
3142
|
+
|
|
3143
|
+
let local_candidate = CandidateHostConfig {
|
|
3144
|
+
base_config: CandidateConfig {
|
|
3145
|
+
network: "udp".to_owned(),
|
|
3146
|
+
address: "192.168.0.2".to_owned(),
|
|
3147
|
+
port: 777,
|
|
3148
|
+
component: 1,
|
|
3149
|
+
..Default::default()
|
|
3150
|
+
},
|
|
3151
|
+
..Default::default()
|
|
3152
|
+
}
|
|
3153
|
+
.new_candidate_host()?;
|
|
3154
|
+
let local_priority = local_candidate.priority();
|
|
3155
|
+
a.add_local_candidate(local_candidate)?;
|
|
3156
|
+
|
|
3157
|
+
a.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
3158
|
+
ufrag: String::new(),
|
|
3159
|
+
pwd: String::new(),
|
|
3160
|
+
});
|
|
3161
|
+
|
|
3162
|
+
let original = a.ufrag_pwd.local_credentials.clone();
|
|
3163
|
+
let remote_addr = SocketAddr::from_str("172.17.0.3:999")?;
|
|
3164
|
+
|
|
3165
|
+
// Builds a binding request authenticated with `pwd`, as the remote peer would send it.
|
|
3166
|
+
let inbound = |ufrag: &str, pwd: String, tie_breaker: u64| -> Result<Message> {
|
|
3167
|
+
let mut msg = Message::new();
|
|
3168
|
+
msg.build(&[
|
|
3169
|
+
Box::new(BINDING_REQUEST),
|
|
3170
|
+
Box::new(TransactionId::new()),
|
|
3171
|
+
Box::new(Username::new(ATTR_USERNAME, format!("{ufrag}:"))),
|
|
3172
|
+
Box::new(AttrControlling(tie_breaker)),
|
|
3173
|
+
Box::new(PriorityAttr(local_priority)),
|
|
3174
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
3175
|
+
pwd,
|
|
3176
|
+
test_crypto_provider().crypto(),
|
|
3177
|
+
)),
|
|
3178
|
+
Box::new(FINGERPRINT),
|
|
3179
|
+
])?;
|
|
3180
|
+
Ok(msg)
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
// Baseline: the peer's checks are accepted.
|
|
3184
|
+
let mut msg = inbound(&original.ufrag, original.pwd.clone(), a.tie_breaker)?;
|
|
3185
|
+
a.handle_inbound(Instant::now(), &mut msg, 0, remote_addr)?;
|
|
3186
|
+
|
|
3187
|
+
// What `create_offer` does for an ICE restart: generate credentials for the offer to carry.
|
|
3188
|
+
a.generate_restart_credentials(String::new(), String::new())?;
|
|
3189
|
+
|
|
3190
|
+
// The offer advertises the new pair...
|
|
3191
|
+
let advertised = a.get_local_credentials().clone();
|
|
3192
|
+
assert_ne!(
|
|
3193
|
+
advertised.ufrag, original.ufrag,
|
|
3194
|
+
"staging a restart must generate a fresh ufrag for the offer"
|
|
3195
|
+
);
|
|
3196
|
+
assert_ne!(advertised.pwd, original.pwd);
|
|
3197
|
+
|
|
3198
|
+
// ...while the live session still authenticates with the old one.
|
|
3199
|
+
// (`Credentials` deliberately implements neither `Debug` nor `PartialEq`: `pwd` is the
|
|
3200
|
+
// MESSAGE-INTEGRITY key and must not reach a log line, so compare the fields directly.)
|
|
3201
|
+
assert_eq!(
|
|
3202
|
+
a.ufrag_pwd.local_credentials.ufrag, original.ufrag,
|
|
3203
|
+
"staging must not install the new ufrag into the live session"
|
|
3204
|
+
);
|
|
3205
|
+
assert_eq!(
|
|
3206
|
+
a.ufrag_pwd.local_credentials.pwd, original.pwd,
|
|
3207
|
+
"staging must not install the new pwd into the live session"
|
|
3208
|
+
);
|
|
3209
|
+
|
|
3210
|
+
// The guarantee: the peer is still sending checks keyed on the original credentials, and they
|
|
3211
|
+
// must keep being accepted while the offer is in flight.
|
|
3212
|
+
let mut msg = inbound(&original.ufrag, original.pwd.clone(), a.tie_breaker)?;
|
|
3213
|
+
a.handle_inbound(Instant::now(), &mut msg, 0, remote_addr)
|
|
3214
|
+
.expect("inbound STUN keyed on the pre-restart credentials must still validate");
|
|
3215
|
+
|
|
3216
|
+
// The application discards the offer and never sets a local description. The connection must
|
|
3217
|
+
// be exactly as it was.
|
|
3218
|
+
assert!(a.has_pending_restart());
|
|
3219
|
+
let mut msg = inbound(&original.ufrag, original.pwd.clone(), a.tie_breaker)?;
|
|
3220
|
+
a.handle_inbound(Instant::now(), &mut msg, 0, remote_addr)
|
|
3221
|
+
.expect("a discarded ICE-restart offer must leave the session working");
|
|
3222
|
+
|
|
3223
|
+
// Applying it — what `set_local_description` does — installs the staged pair.
|
|
3224
|
+
a.apply_restart(Instant::now(), true)?;
|
|
3225
|
+
assert!(!a.has_pending_restart());
|
|
3226
|
+
assert_eq!(
|
|
3227
|
+
a.ufrag_pwd.local_credentials.ufrag, advertised.ufrag,
|
|
3228
|
+
"apply_restart must install exactly the ufrag the offer advertised"
|
|
3229
|
+
);
|
|
3230
|
+
assert_eq!(
|
|
3231
|
+
a.ufrag_pwd.local_credentials.pwd, advertised.pwd,
|
|
3232
|
+
"apply_restart must install exactly the pwd the offer advertised"
|
|
3233
|
+
);
|
|
3234
|
+
|
|
3235
|
+
a.close()?;
|
|
3236
|
+
Ok(())
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
/// Agent events carry the instant their condition was observed at, so a consumer draining
|
|
3240
|
+
/// `poll_event` is *told* the time rather than having to retain one and guess. `close` has no
|
|
3241
|
+
/// caller-supplied instant, so it stamps with the newest one the agent was given.
|
|
3242
|
+
#[test]
|
|
3243
|
+
fn test_agent_events_carry_the_instant_they_were_observed_at() -> Result<()> {
|
|
3244
|
+
let base = Instant::now();
|
|
3245
|
+
let t = |secs| base + Duration::from_secs(secs);
|
|
3246
|
+
|
|
3247
|
+
let mut a = Agent::new(
|
|
3248
|
+
t(0),
|
|
3249
|
+
Arc::new(AgentConfig::default()),
|
|
3250
|
+
test_crypto_provider(),
|
|
3251
|
+
)?;
|
|
3252
|
+
|
|
3253
|
+
let host_local = CandidateHostConfig {
|
|
3254
|
+
base_config: CandidateConfig {
|
|
3255
|
+
network: "udp".to_owned(),
|
|
3256
|
+
address: "192.168.0.2".to_owned(),
|
|
3257
|
+
port: 1000,
|
|
3258
|
+
component: 1,
|
|
3259
|
+
..Default::default()
|
|
3260
|
+
},
|
|
3261
|
+
..Default::default()
|
|
3262
|
+
}
|
|
3263
|
+
.new_candidate_host()?;
|
|
3264
|
+
a.add_local_candidate(host_local)?;
|
|
3265
|
+
|
|
3266
|
+
let relay_remote = CandidateRelayConfig {
|
|
3267
|
+
base_config: CandidateConfig {
|
|
3268
|
+
network: "udp".to_owned(),
|
|
3269
|
+
address: "1.2.3.4".to_owned(),
|
|
3270
|
+
port: 12340,
|
|
3271
|
+
component: 1,
|
|
3272
|
+
..Default::default()
|
|
3273
|
+
},
|
|
3274
|
+
rel_addr: "4.3.2.1".to_owned(),
|
|
3275
|
+
rel_port: 43210,
|
|
3276
|
+
..Default::default()
|
|
3277
|
+
}
|
|
3278
|
+
.new_candidate_relay()?;
|
|
3279
|
+
a.add_remote_candidate(relay_remote)?;
|
|
3280
|
+
|
|
3281
|
+
a.add_pair(0, 0);
|
|
3282
|
+
a.set_selected_pair(Some(t(5)), Some(0));
|
|
3283
|
+
|
|
3284
|
+
// Selecting a pair emits Connected *and* SelectedCandidatePairChange; both are stamped
|
|
3285
|
+
// with the instant that caused them, not with a reading taken at drain time.
|
|
3286
|
+
let stamped: Vec<_> = std::iter::from_fn(|| a.poll_event()).collect();
|
|
3287
|
+
assert!(!stamped.is_empty(), "selecting a pair emits events");
|
|
3288
|
+
for e in &stamped {
|
|
3289
|
+
assert_eq!(
|
|
3290
|
+
e.now,
|
|
3291
|
+
t(5),
|
|
3292
|
+
"event {:?} must carry the instant its condition was observed at",
|
|
3293
|
+
std::mem::discriminant(&e.event)
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
a.handle_timeout(t(30))?;
|
|
3298
|
+
while a.poll_event().is_some() {}
|
|
3299
|
+
|
|
3300
|
+
// `close` is a drain with no caller-supplied instant, and the agent retains none, so the
|
|
3301
|
+
// Closed transition is applied to `connection_state` but emits no event — there is no
|
|
3302
|
+
// honest instant to stamp one with. A consumer that needs to observe the close should read
|
|
3303
|
+
// the state rather than wait for an event.
|
|
3304
|
+
a.close()?;
|
|
3305
|
+
assert_eq!(a.connection_state, ConnectionState::Closed);
|
|
3306
|
+
assert!(
|
|
3307
|
+
a.poll_event().is_none(),
|
|
3308
|
+
"close emits no event, having no instant to stamp one with"
|
|
3309
|
+
);
|
|
3310
|
+
|
|
3311
|
+
Ok(())
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
/// A `getStats` snapshot reports the instant the caller asked at, throughout — including the
|
|
3315
|
+
/// fields that default rather than being populated from live state. Mixing virtual protocol time
|
|
3316
|
+
/// with wall-clock timestamps in one report is worse than no report: the reader cannot tell which
|
|
3317
|
+
/// numbers are comparable.
|
|
3318
|
+
#[test]
|
|
3319
|
+
fn test_stats_snapshot_reports_the_callers_instant_throughout() -> Result<()> {
|
|
3320
|
+
let base = Instant::now();
|
|
3321
|
+
let t = |secs| base + Duration::from_secs(secs);
|
|
3322
|
+
|
|
3323
|
+
let mut a = Agent::new(
|
|
3324
|
+
t(0),
|
|
3325
|
+
Arc::new(AgentConfig::default()),
|
|
3326
|
+
test_crypto_provider(),
|
|
3327
|
+
)?;
|
|
3328
|
+
|
|
3329
|
+
let host_local = CandidateHostConfig {
|
|
3330
|
+
base_config: CandidateConfig {
|
|
3331
|
+
network: "udp".to_owned(),
|
|
3332
|
+
address: "192.168.0.2".to_owned(),
|
|
3333
|
+
port: 1000,
|
|
3334
|
+
component: 1,
|
|
3335
|
+
..Default::default()
|
|
3336
|
+
},
|
|
3337
|
+
..Default::default()
|
|
3338
|
+
}
|
|
3339
|
+
.new_candidate_host()?;
|
|
3340
|
+
a.add_local_candidate(host_local)?;
|
|
3341
|
+
|
|
3342
|
+
let host_remote = CandidateHostConfig {
|
|
3343
|
+
base_config: CandidateConfig {
|
|
3344
|
+
network: "udp".to_owned(),
|
|
3345
|
+
address: "192.168.0.3".to_owned(),
|
|
3346
|
+
port: 1001,
|
|
3347
|
+
component: 1,
|
|
3348
|
+
..Default::default()
|
|
3349
|
+
},
|
|
3350
|
+
..Default::default()
|
|
3351
|
+
}
|
|
3352
|
+
.new_candidate_host()?;
|
|
3353
|
+
a.add_remote_candidate(host_remote)?;
|
|
3354
|
+
|
|
3355
|
+
// Take the snapshot at an instant far from "now", so a stray wall-clock read is obvious.
|
|
3356
|
+
let pairs = a.get_candidate_pairs_stats(t(600));
|
|
3357
|
+
assert!(!pairs.is_empty(), "adding a remote candidate forms a pair");
|
|
3358
|
+
for p in &pairs {
|
|
3359
|
+
for (field, stamp) in [
|
|
3360
|
+
("timestamp", p.timestamp),
|
|
3361
|
+
("last_packet_sent_timestamp", p.last_packet_sent_timestamp),
|
|
3362
|
+
(
|
|
3363
|
+
"last_packet_received_timestamp",
|
|
3364
|
+
p.last_packet_received_timestamp,
|
|
3365
|
+
),
|
|
3366
|
+
("first_request_timestamp", p.first_request_timestamp),
|
|
3367
|
+
("last_request_timestamp", p.last_request_timestamp),
|
|
3368
|
+
("last_response_timestamp", p.last_response_timestamp),
|
|
3369
|
+
("consent_expired_timestamp", p.consent_expired_timestamp),
|
|
3370
|
+
] {
|
|
3371
|
+
assert_eq!(stamp, t(600), "{field} must report the caller's instant");
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
let locals = a.get_local_candidates_stats(t(600));
|
|
3376
|
+
assert_eq!(locals.len(), 1);
|
|
3377
|
+
assert_eq!(locals[0].timestamp, t(600));
|
|
3378
|
+
|
|
3379
|
+
let remotes = a.get_remote_candidates_stats(t(600));
|
|
3380
|
+
assert_eq!(remotes.len(), 1);
|
|
3381
|
+
assert_eq!(remotes[0].timestamp, t(600));
|
|
3382
|
+
|
|
3383
|
+
// A second snapshot at a different instant reports that one, with no wall-clock time having
|
|
3384
|
+
// passed between the two calls.
|
|
3385
|
+
assert_eq!(a.get_candidate_pairs_stats(t(900))[0].timestamp, t(900));
|
|
3386
|
+
|
|
3387
|
+
Ok(())
|
|
3388
|
+
}
|