@desklink/host 0.5.4 → 0.5.7
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 +8 -2
- package/dist/bridge.js +3 -3
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +1 -0
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +39 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/BOUNDARY.md +4 -2
- package/docs/PROTOCOL.md +95 -9
- package/engine/Cargo.lock +1 -1
- package/engine/Cargo.toml +1 -1
- package/engine/src/audio.rs +398 -0
- package/engine/src/capture.rs +0 -1
- package/engine/src/hyprland.rs +319 -0
- package/engine/src/mac.rs +1 -0
- package/engine/src/main.rs +24 -0
- package/engine/src/peer.rs +73 -0
- package/engine/src/protocol.rs +22 -0
- package/engine/src/session.rs +240 -4
- package/engine/src/win.rs +1 -0
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +53 -1
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +81 -0
- package/engine/vendor/rtc-ice/src/agent/agent_test.rs +206 -0
- package/engine/vendor/rtc-ice/src/agent/mod.rs +91 -0
- package/engine/vendor/rtc-ice/src/dl_trace.rs +49 -0
- package/engine/vendor/rtc-ice/src/lib.rs +3 -0
- package/package.json +3 -3
|
@@ -2930,6 +2930,212 @@ fn test_handle_inbound_request_defers_failing_connectivity_check() -> Result<()>
|
|
|
2930
2930
|
Ok(())
|
|
2931
2931
|
}
|
|
2932
2932
|
|
|
2933
|
+
// Regression test for the desklink patch that nominates off the check pass
|
|
2934
|
+
// (shape 2 in DESKLINK-PATCH.md) instead of the `check_interval` tick.
|
|
2935
|
+
//
|
|
2936
|
+
// Nomination is decided in `contact_candidates`, which the driver reaches on a
|
|
2937
|
+
// `check_interval` tick or when an inbound Binding request arrives while a valid pair
|
|
2938
|
+
// already exists. A peer that runs no checks of its own leaves the second path empty, so
|
|
2939
|
+
// a pair that becomes valid just after a tick waited a whole tick for its
|
|
2940
|
+
// USE-CANDIDATE: measured at 197 ms in the routed roam leg, the entire engine-side cost
|
|
2941
|
+
// between the pair becoming valid and the nomination leaving. The pass that nominates has
|
|
2942
|
+
// to be the one the validating response asked for.
|
|
2943
|
+
//
|
|
2944
|
+
// The agent's clock is driven by hand here and never reaches a tick, so the only thing
|
|
2945
|
+
// that can produce the nomination is the response itself.
|
|
2946
|
+
#[test]
|
|
2947
|
+
fn test_nominates_on_the_response_that_validates_the_pair() -> Result<()> {
|
|
2948
|
+
use sansio::Protocol as _;
|
|
2949
|
+
|
|
2950
|
+
let base = Instant::now();
|
|
2951
|
+
let mut a = Agent::new(
|
|
2952
|
+
base,
|
|
2953
|
+
Arc::new(AgentConfig {
|
|
2954
|
+
is_controlling: true,
|
|
2955
|
+
..Default::default()
|
|
2956
|
+
}),
|
|
2957
|
+
test_crypto_provider(),
|
|
2958
|
+
)?;
|
|
2959
|
+
|
|
2960
|
+
let local = CandidateHostConfig {
|
|
2961
|
+
base_config: CandidateConfig {
|
|
2962
|
+
network: "udp".to_owned(),
|
|
2963
|
+
address: "192.168.0.2".to_owned(),
|
|
2964
|
+
port: 777,
|
|
2965
|
+
component: 1,
|
|
2966
|
+
..Default::default()
|
|
2967
|
+
},
|
|
2968
|
+
..Default::default()
|
|
2969
|
+
}
|
|
2970
|
+
.new_candidate_host()?;
|
|
2971
|
+
a.add_local_candidate(local)?;
|
|
2972
|
+
|
|
2973
|
+
let remote = CandidateHostConfig {
|
|
2974
|
+
base_config: CandidateConfig {
|
|
2975
|
+
network: "udp".to_owned(),
|
|
2976
|
+
address: "172.17.0.3".to_owned(),
|
|
2977
|
+
port: 999,
|
|
2978
|
+
component: 1,
|
|
2979
|
+
..Default::default()
|
|
2980
|
+
},
|
|
2981
|
+
..Default::default()
|
|
2982
|
+
}
|
|
2983
|
+
.new_candidate_host()?;
|
|
2984
|
+
let remote_addr = remote.addr();
|
|
2985
|
+
a.add_remote_candidate(remote)?;
|
|
2986
|
+
assert_eq!(a.candidate_pairs.len(), 1, "one pair to check");
|
|
2987
|
+
|
|
2988
|
+
a.start_connectivity_checks(base, true, "remoteUfrag".to_owned(), "remotePwd".to_owned())?;
|
|
2989
|
+
|
|
2990
|
+
// The first pass pings the pair. Nothing has validated it yet, so nothing is
|
|
2991
|
+
// nominated and no USE-CANDIDATE leaves.
|
|
2992
|
+
a.handle_timeout(base)?;
|
|
2993
|
+
let mut check = Message {
|
|
2994
|
+
raw: a
|
|
2995
|
+
.poll_write()
|
|
2996
|
+
.expect("the agent checks the pair")
|
|
2997
|
+
.message
|
|
2998
|
+
.to_vec(),
|
|
2999
|
+
..Default::default()
|
|
3000
|
+
};
|
|
3001
|
+
check.decode()?;
|
|
3002
|
+
assert_eq!(check.typ, MessageType::new(METHOD_BINDING, CLASS_REQUEST));
|
|
3003
|
+
assert!(
|
|
3004
|
+
!check.contains(ATTR_USE_CANDIDATE),
|
|
3005
|
+
"a check that has not been answered must not nominate"
|
|
3006
|
+
);
|
|
3007
|
+
assert!(a.nominated_pair.is_none());
|
|
3008
|
+
|
|
3009
|
+
// The peer answers that check. Nothing else arrives: no inbound request (the test
|
|
3010
|
+
// delivers no such message) and no tick (the clock moves by a millisecond).
|
|
3011
|
+
let now = base + Duration::from_millis(1);
|
|
3012
|
+
let mut response = Message::new();
|
|
3013
|
+
response.build(&[
|
|
3014
|
+
Box::new(check.clone()),
|
|
3015
|
+
Box::new(BINDING_SUCCESS),
|
|
3016
|
+
Box::new(XorMappedAddress {
|
|
3017
|
+
ip: remote_addr.ip(),
|
|
3018
|
+
port: remote_addr.port(),
|
|
3019
|
+
}),
|
|
3020
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
3021
|
+
"remotePwd".to_owned(),
|
|
3022
|
+
test_crypto_provider().crypto(),
|
|
3023
|
+
)),
|
|
3024
|
+
Box::new(FINGERPRINT),
|
|
3025
|
+
])?;
|
|
3026
|
+
a.handle_inbound(now, &mut response, 0, remote_addr)?;
|
|
3027
|
+
assert_eq!(
|
|
3028
|
+
a.candidate_pairs[0].state,
|
|
3029
|
+
CandidatePairState::Succeeded,
|
|
3030
|
+
"the response validates the pair"
|
|
3031
|
+
);
|
|
3032
|
+
|
|
3033
|
+
// The pair is valid, so the pass that nominates it is due now. Before the patch the
|
|
3034
|
+
// deadline here was the next tick, a whole `check_interval` away.
|
|
3035
|
+
let deadline = a
|
|
3036
|
+
.poll_timeout()
|
|
3037
|
+
.expect("a validated pair asks for a check pass");
|
|
3038
|
+
assert!(
|
|
3039
|
+
deadline <= now,
|
|
3040
|
+
"the validating response must ask for a pass now, not at the next tick: deadline {deadline:?}, now {now:?}"
|
|
3041
|
+
);
|
|
3042
|
+
|
|
3043
|
+
a.handle_timeout(now)?;
|
|
3044
|
+
assert!(
|
|
3045
|
+
a.nominated_pair.is_some(),
|
|
3046
|
+
"the valid pair must be nominated on the pass the response asked for"
|
|
3047
|
+
);
|
|
3048
|
+
let mut nomination = Message {
|
|
3049
|
+
raw: a
|
|
3050
|
+
.poll_write()
|
|
3051
|
+
.expect("the nomination leaves")
|
|
3052
|
+
.message
|
|
3053
|
+
.to_vec(),
|
|
3054
|
+
..Default::default()
|
|
3055
|
+
};
|
|
3056
|
+
nomination.decode()?;
|
|
3057
|
+
assert!(
|
|
3058
|
+
nomination.contains(ATTR_USE_CANDIDATE),
|
|
3059
|
+
"the nomination is a Binding request carrying USE-CANDIDATE"
|
|
3060
|
+
);
|
|
3061
|
+
|
|
3062
|
+
a.close()?;
|
|
3063
|
+
Ok(())
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
// A peer that never answers a check must still retire the pair on the configured budget,
|
|
3067
|
+
// and the shape-2 patch must not change that budget: it only ever asks for a pass when a
|
|
3068
|
+
// response made a pair valid, so a silent peer never earns one. The agent's clock is driven
|
|
3069
|
+
// by hand, so this pins the pass count exactly instead of waiting for it.
|
|
3070
|
+
#[test]
|
|
3071
|
+
fn test_a_silent_peer_retires_the_pair_on_the_check_budget() -> Result<()> {
|
|
3072
|
+
use sansio::Protocol as _;
|
|
3073
|
+
|
|
3074
|
+
let base = Instant::now();
|
|
3075
|
+
let mut a = Agent::new(
|
|
3076
|
+
base,
|
|
3077
|
+
Arc::new(AgentConfig {
|
|
3078
|
+
is_controlling: true,
|
|
3079
|
+
..Default::default()
|
|
3080
|
+
}),
|
|
3081
|
+
test_crypto_provider(),
|
|
3082
|
+
)?;
|
|
3083
|
+
|
|
3084
|
+
let local = CandidateHostConfig {
|
|
3085
|
+
base_config: CandidateConfig {
|
|
3086
|
+
network: "udp".to_owned(),
|
|
3087
|
+
address: "192.168.0.2".to_owned(),
|
|
3088
|
+
port: 777,
|
|
3089
|
+
component: 1,
|
|
3090
|
+
..Default::default()
|
|
3091
|
+
},
|
|
3092
|
+
..Default::default()
|
|
3093
|
+
}
|
|
3094
|
+
.new_candidate_host()?;
|
|
3095
|
+
a.add_local_candidate(local)?;
|
|
3096
|
+
|
|
3097
|
+
let remote = CandidateHostConfig {
|
|
3098
|
+
base_config: CandidateConfig {
|
|
3099
|
+
network: "udp".to_owned(),
|
|
3100
|
+
address: "172.17.0.3".to_owned(),
|
|
3101
|
+
port: 999,
|
|
3102
|
+
component: 1,
|
|
3103
|
+
..Default::default()
|
|
3104
|
+
},
|
|
3105
|
+
..Default::default()
|
|
3106
|
+
}
|
|
3107
|
+
.new_candidate_host()?;
|
|
3108
|
+
a.add_remote_candidate(remote)?;
|
|
3109
|
+
a.start_connectivity_checks(base, true, "remoteUfrag".to_owned(), "remotePwd".to_owned())?;
|
|
3110
|
+
|
|
3111
|
+
// `ping_all_candidates` retires a pair once `binding_request_count` passes
|
|
3112
|
+
// `max_binding_requests`, so the first pass that retires it is one after the last
|
|
3113
|
+
// request it sends: `max_binding_requests + 2` passes from checking.
|
|
3114
|
+
let last_sending_pass = usize::from(DEFAULT_MAX_BINDING_REQUESTS) + 1;
|
|
3115
|
+
let retiring_pass = last_sending_pass + 1;
|
|
3116
|
+
let mut now = base;
|
|
3117
|
+
for pass in 1..=retiring_pass {
|
|
3118
|
+
now += Duration::from_millis(200);
|
|
3119
|
+
a.handle_timeout(now)?;
|
|
3120
|
+
while a.poll_write().is_some() {}
|
|
3121
|
+
let deadline = a.poll_timeout().expect("the agent stays on its timers");
|
|
3122
|
+
assert!(
|
|
3123
|
+
deadline > now,
|
|
3124
|
+
"pass {pass}: an unanswered peer must not ask for an extra pass (deadline {deadline:?}, now {now:?})"
|
|
3125
|
+
);
|
|
3126
|
+
let failed = a.candidate_pairs[0].state == CandidatePairState::Failed;
|
|
3127
|
+
assert_eq!(
|
|
3128
|
+
failed,
|
|
3129
|
+
pass >= retiring_pass,
|
|
3130
|
+
"pass {pass}: pair must retire on the budget, not before or after"
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
assert_eq!(a.connection_state, ConnectionState::Checking);
|
|
3134
|
+
|
|
3135
|
+
a.close()?;
|
|
3136
|
+
Ok(())
|
|
3137
|
+
}
|
|
3138
|
+
|
|
2933
3139
|
// Safari (and Chrome) expose ICE host candidates behind an mDNS "<uuid>.local"
|
|
2934
3140
|
// hostname by default. A QueryOnly agent (the default) must RESOLVE such a
|
|
2935
3141
|
// candidate -- i.e. issue an mDNS query, which surfaces as an outbound packet to
|
|
@@ -38,6 +38,9 @@ use stun::xoraddr::*;
|
|
|
38
38
|
|
|
39
39
|
use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
|
|
40
40
|
use crate::candidate::{candidate_pair::*, *};
|
|
41
|
+
// desklink scout trace (dl-roam-postnom-attr):
|
|
42
|
+
use crate::dl_trace;
|
|
43
|
+
use crate::dl_trace::dl_ice;
|
|
41
44
|
use crate::mdns::{MulticastDnsMode, create_multicast_dns, generate_multicast_dns_name};
|
|
42
45
|
use crate::network_type::NetworkType;
|
|
43
46
|
use crate::rand::*;
|
|
@@ -520,6 +523,17 @@ impl Agent {
|
|
|
520
523
|
}
|
|
521
524
|
}
|
|
522
525
|
|
|
526
|
+
// desklink scout trace: a remote candidate landed and an
|
|
527
|
+
// immediate check pass was requested for it.
|
|
528
|
+
if dl_trace::enabled() {
|
|
529
|
+
dl_ice!(
|
|
530
|
+
"{} remote_candidate_added {} pairs={} force_check",
|
|
531
|
+
self.get_name(),
|
|
532
|
+
self.remote_candidates[remote_index].addr(),
|
|
533
|
+
self.candidate_pairs.len(),
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
523
537
|
self.request_connectivity_check();
|
|
524
538
|
}
|
|
525
539
|
}
|
|
@@ -724,6 +738,18 @@ impl Agent {
|
|
|
724
738
|
/// pair, and restarts the agent's timers. With nothing staged, the current credentials are
|
|
725
739
|
/// kept and only the session is restarted.
|
|
726
740
|
pub fn apply_restart(&mut self, now: Instant, keep_local_candidates: bool) -> Result<()> {
|
|
741
|
+
// desklink scout trace: restart applied — the new generation begins.
|
|
742
|
+
if dl_trace::enabled() {
|
|
743
|
+
dl_ice!(
|
|
744
|
+
"{} APPLY_RESTART keep_local={keep_local_candidates} role={} state={:?} local_cands={} remote_cands={} pairs={}",
|
|
745
|
+
self.get_name(),
|
|
746
|
+
if self.is_controlling { "controlling" } else { "controlled" },
|
|
747
|
+
self.connection_state,
|
|
748
|
+
self.local_candidates.len(),
|
|
749
|
+
self.remote_candidates.len(),
|
|
750
|
+
self.candidate_pairs.len(),
|
|
751
|
+
);
|
|
752
|
+
}
|
|
727
753
|
// desklink patch: remember the pair media is on before the teardown
|
|
728
754
|
// below clears it, so the ICE transport keeps sending and receiving on
|
|
729
755
|
// it until the restarted generation nominates a pair (see
|
|
@@ -784,6 +810,20 @@ impl Agent {
|
|
|
784
810
|
}
|
|
785
811
|
|
|
786
812
|
fn contact(&mut self, now: Instant) {
|
|
813
|
+
// desklink scout trace: one line per agent contact() pass, so the tick
|
|
814
|
+
// cadence (and any forced pass) is measurable.
|
|
815
|
+
if dl_trace::enabled() {
|
|
816
|
+
dl_ice!(
|
|
817
|
+
"{} tick_begin state={:?} sel={:?} nom={:?} pairs={} outstanding={} force={}",
|
|
818
|
+
self.get_name(),
|
|
819
|
+
self.connection_state,
|
|
820
|
+
self.selected_pair,
|
|
821
|
+
self.nominated_pair,
|
|
822
|
+
self.candidate_pairs.len(),
|
|
823
|
+
self.pending_binding_requests.len(),
|
|
824
|
+
self.force_candidate_contact,
|
|
825
|
+
);
|
|
826
|
+
}
|
|
787
827
|
// Consume any pending deferred-check request now that we are running one.
|
|
788
828
|
// Reset before the early returns below so a failed/settled agent does not
|
|
789
829
|
// keep asking `poll_timeout` for an immediate wake-up.
|
|
@@ -826,6 +866,19 @@ impl Agent {
|
|
|
826
866
|
new_state: ConnectionState,
|
|
827
867
|
) {
|
|
828
868
|
if self.connection_state != new_state {
|
|
869
|
+
// desklink scout trace: every connection-state transition, with the
|
|
870
|
+
// selection context that explains it.
|
|
871
|
+
if dl_trace::enabled() {
|
|
872
|
+
dl_ice!(
|
|
873
|
+
"{} state {:?} -> {:?} sel={:?} nom={:?} pairs={}",
|
|
874
|
+
self.get_name(),
|
|
875
|
+
self.connection_state,
|
|
876
|
+
new_state,
|
|
877
|
+
self.selected_pair,
|
|
878
|
+
self.nominated_pair,
|
|
879
|
+
self.candidate_pairs.len(),
|
|
880
|
+
);
|
|
881
|
+
}
|
|
829
882
|
// Connection has gone to failed, release all gathered candidates
|
|
830
883
|
if new_state == ConnectionState::Failed {
|
|
831
884
|
self.set_selected_pair(now, None);
|
|
@@ -861,6 +914,19 @@ impl Agent {
|
|
|
861
914
|
self.candidate_pairs[pair_index]
|
|
862
915
|
);
|
|
863
916
|
|
|
917
|
+
// desklink scout trace: nomination landed on a pair — the moment the
|
|
918
|
+
// agent moves to Connected.
|
|
919
|
+
if dl_trace::enabled() {
|
|
920
|
+
let p = &self.candidate_pairs[pair_index];
|
|
921
|
+
dl_ice!(
|
|
922
|
+
"{} SELECTED_PAIR local={} remote={} pairs={}",
|
|
923
|
+
self.get_name(),
|
|
924
|
+
self.local_candidates[p.local_index].addr(),
|
|
925
|
+
self.remote_candidates[p.remote_index].addr(),
|
|
926
|
+
self.candidate_pairs.len(),
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
864
930
|
self.candidate_pairs[pair_index].nominated = true;
|
|
865
931
|
self.selected_pair = Some(pair_index);
|
|
866
932
|
// desklink patch: the restarted generation now has a nominated pair,
|
|
@@ -1101,6 +1167,20 @@ impl Agent {
|
|
|
1101
1167
|
self.remote_candidates[remote_index],
|
|
1102
1168
|
);
|
|
1103
1169
|
|
|
1170
|
+
// desklink scout trace: every outbound connectivity check / nomination,
|
|
1171
|
+
// with the transaction id, so the relay's wire log can be matched to it.
|
|
1172
|
+
if dl_trace::enabled() {
|
|
1173
|
+
dl_ice!(
|
|
1174
|
+
"{} SEND_REQUEST txn={:?} use_candidate={} local={} remote={} outstanding={}",
|
|
1175
|
+
self.get_name(),
|
|
1176
|
+
m.transaction_id,
|
|
1177
|
+
m.contains(ATTR_USE_CANDIDATE),
|
|
1178
|
+
self.local_candidates[local_index].addr(),
|
|
1179
|
+
self.remote_candidates[remote_index].addr(),
|
|
1180
|
+
self.pending_binding_requests.len(),
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1104
1184
|
self.invalidate_pending_binding_requests(now);
|
|
1105
1185
|
|
|
1106
1186
|
self.pending_binding_requests.push(BindingRequest {
|
|
@@ -1125,6 +1205,17 @@ impl Agent {
|
|
|
1125
1205
|
local_index: usize,
|
|
1126
1206
|
remote_index: usize,
|
|
1127
1207
|
) {
|
|
1208
|
+
// desklink scout trace: the answer to an inbound connectivity check, and
|
|
1209
|
+
// whether the request it answered carried the nomination.
|
|
1210
|
+
if dl_trace::enabled() {
|
|
1211
|
+
dl_ice!(
|
|
1212
|
+
"{} SEND_SUCCESS role={} txn={:?} use_candidate={}",
|
|
1213
|
+
self.get_name(),
|
|
1214
|
+
if self.is_controlling { "controlling" } else { "controlled" },
|
|
1215
|
+
m.transaction_id,
|
|
1216
|
+
m.contains(ATTR_USE_CANDIDATE),
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1128
1219
|
let addr = self.remote_candidates[remote_index].addr();
|
|
1129
1220
|
let (ip, port) = (addr.ip(), addr.port());
|
|
1130
1221
|
let local_pwd = self.ufrag_pwd.local_credentials.pwd.clone();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//! desklink scout instrumentation (dev-only, `DESKLINK_ICE_TRACE=1`).
|
|
2
|
+
//!
|
|
3
|
+
//! Timestamped stderr trace of the ICE agent's restart/check/nomination timeline
|
|
4
|
+
//! for task dl-roam-postnom-attr. Not part of the shipped patch; see
|
|
5
|
+
//! DESKLINK-PATCH.md. Every call site is marked `// desklink scout trace:` so the
|
|
6
|
+
//! whole diff is trivially droppable.
|
|
7
|
+
|
|
8
|
+
use std::fmt::Arguments;
|
|
9
|
+
use std::sync::OnceLock;
|
|
10
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
11
|
+
|
|
12
|
+
/// Whether tracing is on; cached, so the hot path is one atomic load.
|
|
13
|
+
pub(crate) fn enabled() -> bool {
|
|
14
|
+
static ON: OnceLock<bool> = OnceLock::new();
|
|
15
|
+
*ON.get_or_init(|| flag("DESKLINK_ICE_TRACE"))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// A dev-only scout switch, read fresh each time (these are experiments).
|
|
19
|
+
pub(crate) fn flag(name: &str) -> bool {
|
|
20
|
+
match std::env::var(name) {
|
|
21
|
+
Ok(v) => v != "0" && !v.is_empty(),
|
|
22
|
+
Err(_) => false,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Microseconds since the Unix epoch; the vendored crate, the engine and the
|
|
27
|
+
/// browser/relay side all stamp this way (ms on the JS side), so lines from
|
|
28
|
+
/// every observer sort into one timeline.
|
|
29
|
+
pub(crate) fn t_us() -> u128 {
|
|
30
|
+
SystemTime::now()
|
|
31
|
+
.duration_since(UNIX_EPOCH)
|
|
32
|
+
.map(|d| d.as_micros())
|
|
33
|
+
.unwrap_or(0)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Emit one trace line.
|
|
37
|
+
pub(crate) fn write(line: Arguments<'_>) {
|
|
38
|
+
eprintln!("[ice {}] {}", t_us(), line);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
macro_rules! dl_ice {
|
|
42
|
+
($($arg:tt)*) => {{
|
|
43
|
+
if $crate::dl_trace::enabled() {
|
|
44
|
+
$crate::dl_trace::write(format_args!($($arg)*));
|
|
45
|
+
}
|
|
46
|
+
}};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub(crate) use dl_ice;
|
|
@@ -58,6 +58,9 @@
|
|
|
58
58
|
/// without adding — and version-matching — a direct `rtc-crypto` dependency.
|
|
59
59
|
pub use crypto;
|
|
60
60
|
|
|
61
|
+
// desklink scout trace: dev-only ICE timeline instrumentation (dl-roam-postnom-attr).
|
|
62
|
+
pub(crate) mod dl_trace;
|
|
63
|
+
|
|
61
64
|
pub mod agent;
|
|
62
65
|
/// The ICE-specific STUN attributes carried in connectivity checks.
|
|
63
66
|
pub mod attributes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desklink/host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ws": "8.21.3"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"@desklink/host-linux-x64-gnu": "0.5.
|
|
42
|
-
"@desklink/host-darwin-arm64": "0.5.
|
|
41
|
+
"@desklink/host-linux-x64-gnu": "0.5.7",
|
|
42
|
+
"@desklink/host-darwin-arm64": "0.5.7"
|
|
43
43
|
}
|
|
44
44
|
}
|