reactor-effect-native 0.2.0 → 0.3.0-rc.0
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/Dockerfile +4 -2
- package/README.md +90 -15
- package/dist/_internal/bridge.d.ts +70 -14
- package/dist/_internal/bridge.d.ts.map +1 -1
- package/dist/_internal/bridge.js +290 -145
- package/dist/_internal/bridge.js.map +1 -1
- package/dist/_internal/isolated/child.d.ts +2 -0
- package/dist/_internal/isolated/child.d.ts.map +1 -0
- package/dist/_internal/isolated/child.js +176 -0
- package/dist/_internal/isolated/child.js.map +1 -0
- package/dist/_internal/isolated/host.d.ts +147 -0
- package/dist/_internal/isolated/host.d.ts.map +1 -0
- package/dist/_internal/isolated/host.js +645 -0
- package/dist/_internal/isolated/host.js.map +1 -0
- package/dist/_internal/isolated/protocol.d.ts +399 -0
- package/dist/_internal/isolated/protocol.d.ts.map +1 -0
- package/dist/_internal/isolated/protocol.js +250 -0
- package/dist/_internal/isolated/protocol.js.map +1 -0
- package/dist/_internal/peer.d.ts +68 -13
- package/dist/_internal/peer.d.ts.map +1 -1
- package/dist/_internal/peer.js +233 -157
- package/dist/_internal/peer.js.map +1 -1
- package/dist/index.d.ts +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -33
- package/dist/index.js.map +1 -1
- package/dist/isolated.d.ts +25 -0
- package/dist/isolated.d.ts.map +1 -0
- package/dist/isolated.js +39 -0
- package/dist/isolated.js.map +1 -0
- package/lib/darwin-arm64/libreactor_effect_native.dylib +0 -0
- package/lib/darwin-arm64/native-identity.json +4 -3
- package/lib/linux-x64/libreactor_effect_native.so +0 -0
- package/lib/linux-x64/native-identity.json +4 -3
- package/package.json +7 -3
- package/rust/Cargo.toml +108 -2
- package/rust/build.rs +253 -114
- package/rust/clippy.toml +7 -0
- package/rust/include/reactor_effect_native.h +101 -42
- package/rust/src/abi.rs +258 -0
- package/rust/src/error.rs +149 -0
- package/rust/src/ffi/memory.rs +256 -0
- package/rust/src/ffi/tests.rs +719 -0
- package/rust/src/ffi.rs +474 -0
- package/rust/src/lib.rs +36 -2341
- package/rust/src/peer/callbacks.rs +145 -0
- package/rust/src/peer/media.rs +221 -0
- package/rust/src/peer/owner/tests.rs +188 -0
- package/rust/src/peer/owner.rs +353 -0
- package/rust/src/peer/shared.rs +323 -0
- package/rust/src/peer/tests.rs +513 -0
- package/rust/src/peer.rs +189 -0
- package/rust/src/protocol/event.rs +238 -0
- package/rust/src/protocol/request.rs +347 -0
- package/rust/src/protocol/stats.rs +356 -0
- package/rust/src/protocol.rs +71 -0
- package/rust/src/sync/gate.rs +159 -0
- package/rust/src/sync/notifier.rs +136 -0
- package/rust/src/sync/queue.rs +384 -0
- package/rust/src/sync.rs +20 -0
- package/rust/src/test_support.rs +99 -0
- package/rust-toolchain.toml +7 -0
- package/scripts/stage.mjs +41 -1
package/rust/src/lib.rs
CHANGED
|
@@ -1,2343 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const CHANNEL_DATA: u32 = 1;
|
|
37
|
-
|
|
38
|
-
const CALL_BUFFER_MIN: usize = 4 * 1024 * 1024;
|
|
39
|
-
const ERROR_BUFFER_MIN: usize = 4096;
|
|
40
|
-
const MAX_REQUEST_BYTES: usize = 1024 * 1024;
|
|
41
|
-
const MAX_MESSAGE_BYTES: usize = 262_144;
|
|
42
|
-
const MAX_BUFFERED_SEND_BYTES: u64 = 1_048_576;
|
|
43
|
-
|
|
44
|
-
#[derive(Debug, Clone)]
|
|
45
|
-
struct BridgeError {
|
|
46
|
-
code: &'static str,
|
|
47
|
-
message: String,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
impl BridgeError {
|
|
51
|
-
fn invalid(message: impl Into<String>) -> Self {
|
|
52
|
-
Self {
|
|
53
|
-
code: "InvalidInput",
|
|
54
|
-
message: message.into(),
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
fn native(message: impl Into<String>) -> Self {
|
|
59
|
-
Self {
|
|
60
|
-
code: "Native",
|
|
61
|
-
message: message.into(),
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
fn closed() -> Self {
|
|
66
|
-
Self {
|
|
67
|
-
code: "Closed",
|
|
68
|
-
message: "native peer is closed".into(),
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
fn overflow(message: impl Into<String>) -> Self {
|
|
73
|
-
Self {
|
|
74
|
-
code: "Overflow",
|
|
75
|
-
message: message.into(),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
fn json(&self) -> Vec<u8> {
|
|
80
|
-
serde_json::to_vec(&json!({ "code": self.code, "message": self.message })).unwrap_or_else(
|
|
81
|
-
|_| br#"{"code":"Native","message":"failed to serialize native error"}"#.to_vec(),
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
impl From<reactor_webrtc::Error> for BridgeError {
|
|
87
|
-
fn from(value: reactor_webrtc::Error) -> Self {
|
|
88
|
-
Self::native(value.to_string())
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
#[derive(Default)]
|
|
93
|
-
struct CallbackGate {
|
|
94
|
-
accepting: AtomicBool,
|
|
95
|
-
active: Mutex<usize>,
|
|
96
|
-
zero: Condvar,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
impl CallbackGate {
|
|
100
|
-
fn new() -> Self {
|
|
101
|
-
Self {
|
|
102
|
-
accepting: AtomicBool::new(true),
|
|
103
|
-
active: Mutex::new(0),
|
|
104
|
-
zero: Condvar::new(),
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
fn enter(self: &Arc<Self>) -> Option<CallbackGuard> {
|
|
109
|
-
if !self.accepting.load(Ordering::Acquire) {
|
|
110
|
-
return None;
|
|
111
|
-
}
|
|
112
|
-
let mut active = lock(&self.active);
|
|
113
|
-
if !self.accepting.load(Ordering::Acquire) {
|
|
114
|
-
return None;
|
|
115
|
-
}
|
|
116
|
-
*active += 1;
|
|
117
|
-
drop(active);
|
|
118
|
-
Some(CallbackGuard {
|
|
119
|
-
gate: Arc::clone(self),
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
fn close(&self) {
|
|
124
|
-
self.accepting.store(false, Ordering::Release);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
fn wait_zero(&self) {
|
|
128
|
-
let mut active = lock(&self.active);
|
|
129
|
-
while *active != 0 {
|
|
130
|
-
active = self.zero.wait(active).unwrap_or_else(|p| p.into_inner());
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
struct CallbackGuard {
|
|
136
|
-
gate: Arc<CallbackGate>,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
impl Drop for CallbackGuard {
|
|
140
|
-
fn drop(&mut self) {
|
|
141
|
-
let mut active = lock(&self.gate.active);
|
|
142
|
-
*active -= 1;
|
|
143
|
-
if *active == 0 {
|
|
144
|
-
self.gate.zero.notify_all();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
|
|
150
|
-
mutex.lock().unwrap_or_else(|p| p.into_inner())
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
#[derive(Debug)]
|
|
154
|
-
enum PushResult {
|
|
155
|
-
Accepted,
|
|
156
|
-
Closed,
|
|
157
|
-
Overflow,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
struct PacketQueue {
|
|
161
|
-
inner: Mutex<PacketQueueInner>,
|
|
162
|
-
wake: Condvar,
|
|
163
|
-
max_items: usize,
|
|
164
|
-
max_bytes: usize,
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
struct PacketQueueInner {
|
|
168
|
-
packets: VecDeque<Vec<u8>>,
|
|
169
|
-
// A size probe transfers the front packet into the reader's retained slot.
|
|
170
|
-
// Producers may evict queued media, but must never replace this packet.
|
|
171
|
-
// Retained packets count toward both bounds until copy or close.
|
|
172
|
-
retained: Option<Vec<u8>>,
|
|
173
|
-
bytes: usize,
|
|
174
|
-
closed: bool,
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
impl PacketQueueInner {
|
|
178
|
-
fn len(&self) -> usize {
|
|
179
|
-
self.packets.len() + usize::from(self.retained.is_some())
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
#[derive(Debug, Clone, Copy)]
|
|
184
|
-
struct QueueSnapshot {
|
|
185
|
-
queued: usize,
|
|
186
|
-
bytes: usize,
|
|
187
|
-
closed: bool,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
impl PacketQueue {
|
|
191
|
-
fn new(max_items: usize, max_bytes: usize) -> Self {
|
|
192
|
-
Self {
|
|
193
|
-
inner: Mutex::new(PacketQueueInner {
|
|
194
|
-
packets: VecDeque::new(),
|
|
195
|
-
retained: None,
|
|
196
|
-
bytes: 0,
|
|
197
|
-
closed: false,
|
|
198
|
-
}),
|
|
199
|
-
wake: Condvar::new(),
|
|
200
|
-
max_items,
|
|
201
|
-
max_bytes,
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
fn push(&self, packet: Vec<u8>) -> PushResult {
|
|
206
|
-
let mut inner = lock(&self.inner);
|
|
207
|
-
if inner.closed {
|
|
208
|
-
return PushResult::Closed;
|
|
209
|
-
}
|
|
210
|
-
if packet.len() > self.max_bytes
|
|
211
|
-
|| inner.len() >= self.max_items
|
|
212
|
-
|| inner.bytes.saturating_add(packet.len()) > self.max_bytes
|
|
213
|
-
{
|
|
214
|
-
return PushResult::Overflow;
|
|
215
|
-
}
|
|
216
|
-
inner.bytes += packet.len();
|
|
217
|
-
inner.packets.push_back(packet);
|
|
218
|
-
self.wake.notify_one();
|
|
219
|
-
PushResult::Accepted
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
fn replace_with(&self, packet: Vec<u8>) {
|
|
223
|
-
let mut inner = lock(&self.inner);
|
|
224
|
-
if inner.closed {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
inner.packets.clear();
|
|
228
|
-
inner.bytes = inner.retained.as_ref().map_or(0, Vec::len);
|
|
229
|
-
// The critical-event queue has room for its small terminal diagnostic
|
|
230
|
-
// even with one maximum-sized transport event retained by its reader.
|
|
231
|
-
if inner.len() < self.max_items
|
|
232
|
-
&& inner.bytes.saturating_add(packet.len()) <= self.max_bytes
|
|
233
|
-
{
|
|
234
|
-
inner.bytes += packet.len();
|
|
235
|
-
inner.packets.push_back(packet);
|
|
236
|
-
}
|
|
237
|
-
self.wake.notify_all();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
fn close(&self) {
|
|
241
|
-
let mut inner = lock(&self.inner);
|
|
242
|
-
inner.closed = true;
|
|
243
|
-
inner.packets.clear();
|
|
244
|
-
inner.retained = None;
|
|
245
|
-
inner.bytes = 0;
|
|
246
|
-
self.wake.notify_all();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
fn snapshot(&self) -> QueueSnapshot {
|
|
250
|
-
let inner = lock(&self.inner);
|
|
251
|
-
QueueSnapshot {
|
|
252
|
-
queued: inner.len(),
|
|
253
|
-
bytes: inner.bytes,
|
|
254
|
-
closed: inner.closed,
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
fn poll(&self, timeout: Duration, capacity: usize) -> PollResult {
|
|
259
|
-
let started = Instant::now();
|
|
260
|
-
let mut inner = lock(&self.inner);
|
|
261
|
-
loop {
|
|
262
|
-
if inner.retained.is_none() {
|
|
263
|
-
inner.retained = inner.packets.pop_front();
|
|
264
|
-
}
|
|
265
|
-
if let Some(packet) = inner.retained.as_ref() {
|
|
266
|
-
if capacity < packet.len() {
|
|
267
|
-
return PollResult::Need(packet.len());
|
|
268
|
-
}
|
|
269
|
-
let packet = inner.retained.take().expect("retained packet exists");
|
|
270
|
-
inner.bytes -= packet.len();
|
|
271
|
-
return PollResult::Packet(packet);
|
|
272
|
-
}
|
|
273
|
-
if inner.closed {
|
|
274
|
-
return PollResult::Closed;
|
|
275
|
-
}
|
|
276
|
-
if timeout.is_zero() {
|
|
277
|
-
return PollResult::Again;
|
|
278
|
-
}
|
|
279
|
-
let remaining = timeout.saturating_sub(started.elapsed());
|
|
280
|
-
if remaining.is_zero() {
|
|
281
|
-
return PollResult::Again;
|
|
282
|
-
}
|
|
283
|
-
let (next, timed) = self
|
|
284
|
-
.wake
|
|
285
|
-
.wait_timeout(inner, remaining)
|
|
286
|
-
.unwrap_or_else(|p| p.into_inner());
|
|
287
|
-
inner = next;
|
|
288
|
-
if timed.timed_out() && inner.packets.is_empty() {
|
|
289
|
-
return PollResult::Again;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
enum PollResult {
|
|
296
|
-
Packet(Vec<u8>),
|
|
297
|
-
Need(usize),
|
|
298
|
-
Again,
|
|
299
|
-
Closed,
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
#[derive(Default)]
|
|
303
|
-
struct MediaCounters {
|
|
304
|
-
observed: AtomicU64,
|
|
305
|
-
dropped: AtomicU64,
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
struct MediaQueue {
|
|
309
|
-
queue: PacketQueue,
|
|
310
|
-
counters: MediaCounters,
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
impl MediaQueue {
|
|
314
|
-
fn new(max_items: usize, max_bytes: usize) -> Self {
|
|
315
|
-
Self {
|
|
316
|
-
queue: PacketQueue::new(max_items, max_bytes),
|
|
317
|
-
counters: MediaCounters::default(),
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
fn push_drop_oldest(&self, packet: Vec<u8>) {
|
|
322
|
-
let mut inner = lock(&self.queue.inner);
|
|
323
|
-
if inner.closed {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
self.counters.observed.fetch_add(1, Ordering::Relaxed);
|
|
327
|
-
if packet.len() > self.queue.max_bytes {
|
|
328
|
-
self.counters.dropped.fetch_add(1, Ordering::Relaxed);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
while !inner.packets.is_empty()
|
|
332
|
-
&& (inner.len() >= self.queue.max_items
|
|
333
|
-
|| inner.bytes.saturating_add(packet.len()) > self.queue.max_bytes)
|
|
334
|
-
{
|
|
335
|
-
if let Some(old) = inner.packets.pop_front() {
|
|
336
|
-
inner.bytes -= old.len();
|
|
337
|
-
self.counters.dropped.fetch_add(1, Ordering::Relaxed);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// A reader can occupy the remaining budget. Drop incoming media rather
|
|
341
|
-
// than evict retained bytes or exceed either queue bound.
|
|
342
|
-
if inner.len() >= self.queue.max_items
|
|
343
|
-
|| inner.bytes.saturating_add(packet.len()) > self.queue.max_bytes
|
|
344
|
-
{
|
|
345
|
-
self.counters.dropped.fetch_add(1, Ordering::Relaxed);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
inner.bytes += packet.len();
|
|
349
|
-
inner.packets.push_back(packet);
|
|
350
|
-
self.queue.wake.notify_one();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
fn snapshot(&self) -> Value {
|
|
354
|
-
let inner = self.queue.snapshot();
|
|
355
|
-
let observed = self.counters.observed.load(Ordering::Relaxed);
|
|
356
|
-
let dropped = self.counters.dropped.load(Ordering::Relaxed);
|
|
357
|
-
let queued = inner.queued as u64;
|
|
358
|
-
let delivered = observed.saturating_sub(dropped).saturating_sub(queued);
|
|
359
|
-
json!({
|
|
360
|
-
"observed": observed.to_string(),
|
|
361
|
-
"dropped": dropped.to_string(),
|
|
362
|
-
"delivered": delivered.to_string(),
|
|
363
|
-
"queued": inner.queued,
|
|
364
|
-
"queuedBytes": inner.bytes,
|
|
365
|
-
"closed": inner.closed,
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
fn close(&self) {
|
|
370
|
-
let mut inner = lock(&self.queue.inner);
|
|
371
|
-
let discarded = inner.len() as u64;
|
|
372
|
-
if discarded != 0 {
|
|
373
|
-
self.counters
|
|
374
|
-
.dropped
|
|
375
|
-
.fetch_add(discarded, Ordering::Relaxed);
|
|
376
|
-
}
|
|
377
|
-
inner.closed = true;
|
|
378
|
-
inner.packets.clear();
|
|
379
|
-
inner.retained = None;
|
|
380
|
-
inner.bytes = 0;
|
|
381
|
-
self.queue.wake.notify_all();
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
#[derive(Clone)]
|
|
386
|
-
struct Binding {
|
|
387
|
-
name: String,
|
|
388
|
-
mid: String,
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
#[derive(Default)]
|
|
392
|
-
struct Bindings {
|
|
393
|
-
video: VecDeque<Binding>,
|
|
394
|
-
audio: VecDeque<Binding>,
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
struct Shared {
|
|
398
|
-
gate: Arc<CallbackGate>,
|
|
399
|
-
events: PacketQueue,
|
|
400
|
-
video: MediaQueue,
|
|
401
|
-
audio: MediaQueue,
|
|
402
|
-
bindings: Mutex<Bindings>,
|
|
403
|
-
remote_tracks: Mutex<Vec<RemoteTrack>>,
|
|
404
|
-
overflowed: AtomicBool,
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
impl Shared {
|
|
408
|
-
fn new(gate: Arc<CallbackGate>) -> Self {
|
|
409
|
-
Self {
|
|
410
|
-
gate,
|
|
411
|
-
events: PacketQueue::new(1024, 16 * 1024 * 1024),
|
|
412
|
-
video: MediaQueue::new(8, 64 * 1024 * 1024),
|
|
413
|
-
audio: MediaQueue::new(256, 4 * 1024 * 1024),
|
|
414
|
-
bindings: Mutex::new(Bindings::default()),
|
|
415
|
-
remote_tracks: Mutex::new(Vec::new()),
|
|
416
|
-
overflowed: AtomicBool::new(false),
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
fn emit(&self, header: Value, payload: &[u8]) {
|
|
421
|
-
let packet = packet(header, payload);
|
|
422
|
-
match self.events.push(packet) {
|
|
423
|
-
PushResult::Accepted | PushResult::Closed => {}
|
|
424
|
-
PushResult::Overflow => self.fail_overflow(),
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
fn fail_overflow(&self) {
|
|
429
|
-
if self.overflowed.swap(true, Ordering::AcqRel) {
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
self.gate.close();
|
|
433
|
-
self.events.replace_with(packet(
|
|
434
|
-
json!({
|
|
435
|
-
"type": "error",
|
|
436
|
-
"code": "Overflow",
|
|
437
|
-
"message": "native transport event queue overflowed; connection retired"
|
|
438
|
-
}),
|
|
439
|
-
&[],
|
|
440
|
-
));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
fn set_bindings(&self, mappings: &[MappingSpec]) {
|
|
444
|
-
let mut bindings = lock(&self.bindings);
|
|
445
|
-
bindings.video.clear();
|
|
446
|
-
bindings.audio.clear();
|
|
447
|
-
for mapping in mappings.iter().filter(|m| m.direction == "recvonly") {
|
|
448
|
-
let binding = Binding {
|
|
449
|
-
name: mapping.name.clone(),
|
|
450
|
-
mid: mapping.mid.clone(),
|
|
451
|
-
};
|
|
452
|
-
if mapping.kind == "video" {
|
|
453
|
-
bindings.video.push_back(binding);
|
|
454
|
-
} else {
|
|
455
|
-
bindings.audio.push_back(binding);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
fn take_binding(&self, kind: MediaKind) -> Option<Binding> {
|
|
461
|
-
let mut bindings = lock(&self.bindings);
|
|
462
|
-
match kind {
|
|
463
|
-
MediaKind::Video => bindings.video.pop_front(),
|
|
464
|
-
MediaKind::Audio => bindings.audio.pop_front(),
|
|
465
|
-
MediaKind::Unknown => None,
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
fn accept_remote(self: &Arc<Self>, track: RemoteTrack) {
|
|
470
|
-
let Some(_guard) = self.gate.enter() else {
|
|
471
|
-
return;
|
|
472
|
-
};
|
|
473
|
-
let kind = track.kind();
|
|
474
|
-
let Some(binding) = self.take_binding(kind) else {
|
|
475
|
-
self.emit(
|
|
476
|
-
json!({
|
|
477
|
-
"type": "error",
|
|
478
|
-
"code": "Protocol",
|
|
479
|
-
"message": "received native track without a declared receive mapping"
|
|
480
|
-
}),
|
|
481
|
-
&[],
|
|
482
|
-
);
|
|
483
|
-
return;
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
match &track {
|
|
487
|
-
RemoteTrack::Video(video) => {
|
|
488
|
-
let shared = Arc::clone(self);
|
|
489
|
-
let frame_binding = binding.clone();
|
|
490
|
-
video.on_frame(move |frame| {
|
|
491
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
492
|
-
return;
|
|
493
|
-
};
|
|
494
|
-
let (frame_id, timestamp_micros, metadata) = frame
|
|
495
|
-
.metadata
|
|
496
|
-
.as_ref()
|
|
497
|
-
.map(|m| (m.frame_id, m.capture_time_us, m.user_data.as_slice()))
|
|
498
|
-
.unwrap_or((0, 0, &[]));
|
|
499
|
-
let header = json!({
|
|
500
|
-
"type": "video",
|
|
501
|
-
"track": frame_binding.name,
|
|
502
|
-
"mid": frame_binding.mid,
|
|
503
|
-
"width": frame.width,
|
|
504
|
-
"height": frame.height,
|
|
505
|
-
"frameId": frame_id.to_string(),
|
|
506
|
-
"timestampMicros": timestamp_micros.to_string(),
|
|
507
|
-
"dataLength": frame.bgra.len(),
|
|
508
|
-
"metadataLength": metadata.len(),
|
|
509
|
-
"format": "BGRA"
|
|
510
|
-
});
|
|
511
|
-
let mut payload = Vec::with_capacity(frame.bgra.len() + metadata.len());
|
|
512
|
-
payload.extend_from_slice(frame.bgra);
|
|
513
|
-
payload.extend_from_slice(metadata);
|
|
514
|
-
shared.video.push_drop_oldest(packet(header, &payload));
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
RemoteTrack::Audio(audio) => {
|
|
518
|
-
let shared = Arc::clone(self);
|
|
519
|
-
let frame_binding = binding.clone();
|
|
520
|
-
audio.on_frame(move |frame| {
|
|
521
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
522
|
-
return;
|
|
523
|
-
};
|
|
524
|
-
let mut payload = Vec::with_capacity(frame.pcm.len() * 2);
|
|
525
|
-
for sample in frame.pcm {
|
|
526
|
-
payload.extend_from_slice(&sample.to_le_bytes());
|
|
527
|
-
}
|
|
528
|
-
shared.audio.push_drop_oldest(packet(
|
|
529
|
-
json!({
|
|
530
|
-
"type": "audio",
|
|
531
|
-
"track": frame_binding.name,
|
|
532
|
-
"mid": frame_binding.mid,
|
|
533
|
-
"sampleRate": frame.sample_rate,
|
|
534
|
-
"channels": frame.channels,
|
|
535
|
-
"frames": frame.frames,
|
|
536
|
-
"samples": frame.pcm.len(),
|
|
537
|
-
"format": "s16le"
|
|
538
|
-
}),
|
|
539
|
-
&payload,
|
|
540
|
-
));
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
self.emit(
|
|
546
|
-
json!({ "type": "track", "name": binding.name, "mid": binding.mid }),
|
|
547
|
-
&[],
|
|
548
|
-
);
|
|
549
|
-
self.emit(
|
|
550
|
-
json!({
|
|
551
|
-
"type": "decoded",
|
|
552
|
-
"kind": match kind { MediaKind::Video => "video", MediaKind::Audio => "audio", MediaKind::Unknown => "unknown" },
|
|
553
|
-
"name": binding.name,
|
|
554
|
-
"mid": binding.mid
|
|
555
|
-
}),
|
|
556
|
-
&[],
|
|
557
|
-
);
|
|
558
|
-
lock(&self.remote_tracks).push(track);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
fn close_queues(&self) {
|
|
562
|
-
self.events.close();
|
|
563
|
-
self.video.close();
|
|
564
|
-
self.audio.close();
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
fn packet(header: Value, payload: &[u8]) -> Vec<u8> {
|
|
569
|
-
let header = serde_json::to_vec(&header).unwrap_or_else(|_| b"{}".to_vec());
|
|
570
|
-
let mut out = Vec::with_capacity(4 + header.len() + payload.len());
|
|
571
|
-
out.extend_from_slice(&(header.len() as u32).to_le_bytes());
|
|
572
|
-
out.extend_from_slice(&header);
|
|
573
|
-
out.extend_from_slice(payload);
|
|
574
|
-
out
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
#[derive(Debug, Deserialize)]
|
|
578
|
-
#[serde(rename_all = "camelCase")]
|
|
579
|
-
struct PrepareRequest {
|
|
580
|
-
servers: Vec<IceServerSpec>,
|
|
581
|
-
tracks: Vec<TrackSpec>,
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
#[derive(Debug, Deserialize)]
|
|
585
|
-
struct IceServerSpec {
|
|
586
|
-
urls: Vec<String>,
|
|
587
|
-
#[serde(default)]
|
|
588
|
-
username: String,
|
|
589
|
-
#[serde(default)]
|
|
590
|
-
credential: String,
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
#[derive(Debug, Clone, Deserialize)]
|
|
594
|
-
struct TrackSpec {
|
|
595
|
-
name: String,
|
|
596
|
-
kind: String,
|
|
597
|
-
direction: String,
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
601
|
-
struct MappingSpec {
|
|
602
|
-
name: String,
|
|
603
|
-
kind: String,
|
|
604
|
-
direction: String,
|
|
605
|
-
mid: String,
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
#[derive(Debug, Deserialize)]
|
|
609
|
-
struct DirectionRequest {
|
|
610
|
-
name: String,
|
|
611
|
-
active: bool,
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
#[derive(Debug, Deserialize)]
|
|
615
|
-
#[serde(rename_all = "camelCase")]
|
|
616
|
-
struct BitrateRequest {
|
|
617
|
-
name: String,
|
|
618
|
-
bits_per_second: u32,
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
struct NativeTrack {
|
|
622
|
-
spec: TrackSpec,
|
|
623
|
-
transceiver: Transceiver,
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
struct Channels {
|
|
627
|
-
control: DataChannel,
|
|
628
|
-
data: DataChannel,
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
struct WorkerState {
|
|
632
|
-
shared: Arc<Shared>,
|
|
633
|
-
factory: Option<PeerConnectionFactory>,
|
|
634
|
-
peer: Option<PeerConnection>,
|
|
635
|
-
channels: Option<Channels>,
|
|
636
|
-
tracks: HashMap<String, NativeTrack>,
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
impl WorkerState {
|
|
640
|
-
fn new(shared: Arc<Shared>) -> Self {
|
|
641
|
-
Self {
|
|
642
|
-
shared,
|
|
643
|
-
factory: None,
|
|
644
|
-
peer: None,
|
|
645
|
-
channels: None,
|
|
646
|
-
tracks: HashMap::new(),
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
fn prepare(&mut self, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
651
|
-
if self.peer.is_some() {
|
|
652
|
-
return Err(BridgeError::invalid("native peer is already prepared"));
|
|
653
|
-
}
|
|
654
|
-
let request: PrepareRequest = decode_json(request)?;
|
|
655
|
-
validate_prepare(&request)?;
|
|
656
|
-
|
|
657
|
-
let config = RtcConfiguration {
|
|
658
|
-
ice_servers: request
|
|
659
|
-
.servers
|
|
660
|
-
.iter()
|
|
661
|
-
.map(|server| IceServer {
|
|
662
|
-
urls: server.urls.clone(),
|
|
663
|
-
username: server.username.clone(),
|
|
664
|
-
password: server.credential.clone(),
|
|
665
|
-
})
|
|
666
|
-
.collect(),
|
|
667
|
-
..RtcConfiguration::default()
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
let factory = PeerConnectionFactory::builder()
|
|
671
|
-
.with_synthetic_adm()
|
|
672
|
-
.build()
|
|
673
|
-
.map_err(BridgeError::from)?;
|
|
674
|
-
|
|
675
|
-
let observer = observer(Arc::clone(&self.shared));
|
|
676
|
-
let peer = factory
|
|
677
|
-
.create_peer_connection(&config, observer)
|
|
678
|
-
.map_err(BridgeError::from)?;
|
|
679
|
-
|
|
680
|
-
let mut control = peer
|
|
681
|
-
.create_data_channel("control")
|
|
682
|
-
.map_err(BridgeError::from)?;
|
|
683
|
-
let mut data = peer
|
|
684
|
-
.create_data_channel("data")
|
|
685
|
-
.map_err(BridgeError::from)?;
|
|
686
|
-
wire_channel(&mut control, "control", Arc::clone(&self.shared));
|
|
687
|
-
wire_channel(&mut data, "data", Arc::clone(&self.shared));
|
|
688
|
-
|
|
689
|
-
let mut tracks = HashMap::with_capacity(request.tracks.len());
|
|
690
|
-
let mut order = Vec::with_capacity(request.tracks.len());
|
|
691
|
-
for spec in request.tracks {
|
|
692
|
-
let kind = media_kind(&spec.kind)?;
|
|
693
|
-
let direction = direction(&spec.direction)?;
|
|
694
|
-
let transceiver = peer
|
|
695
|
-
.add_transceiver(kind, direction)
|
|
696
|
-
.map_err(BridgeError::from)?;
|
|
697
|
-
order.push(spec.name.clone());
|
|
698
|
-
tracks.insert(spec.name.clone(), NativeTrack { spec, transceiver });
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
let offer = peer.create_offer().map_err(BridgeError::from)?;
|
|
702
|
-
peer.set_local_description(&offer)
|
|
703
|
-
.map_err(BridgeError::from)?;
|
|
704
|
-
|
|
705
|
-
let mut mappings = Vec::with_capacity(order.len());
|
|
706
|
-
for name in &order {
|
|
707
|
-
let entry = tracks
|
|
708
|
-
.get(name)
|
|
709
|
-
.ok_or_else(|| BridgeError::native("track map changed while preparing"))?;
|
|
710
|
-
let mid = entry.transceiver.mid().ok_or_else(|| {
|
|
711
|
-
BridgeError::native(format!("missing MID after local description: {name}"))
|
|
712
|
-
})?;
|
|
713
|
-
mappings.push(MappingSpec {
|
|
714
|
-
name: entry.spec.name.clone(),
|
|
715
|
-
kind: entry.spec.kind.clone(),
|
|
716
|
-
direction: entry.spec.direction.clone(),
|
|
717
|
-
mid,
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
self.shared.set_bindings(&mappings);
|
|
721
|
-
|
|
722
|
-
self.factory = Some(factory);
|
|
723
|
-
self.peer = Some(peer);
|
|
724
|
-
self.channels = Some(Channels { control, data });
|
|
725
|
-
self.tracks = tracks;
|
|
726
|
-
|
|
727
|
-
serde_json::to_vec(&json!({ "sdp": offer.sdp, "mapping": mappings }))
|
|
728
|
-
.map_err(|e| BridgeError::native(format!("serialize prepare response: {e}")))
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
fn answer(&mut self, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
732
|
-
let sdp = std::str::from_utf8(request)
|
|
733
|
-
.map_err(|_| BridgeError::invalid("answer SDP is not UTF-8"))?;
|
|
734
|
-
if sdp.is_empty() {
|
|
735
|
-
return Err(BridgeError::invalid("answer SDP is empty"));
|
|
736
|
-
}
|
|
737
|
-
self.require_peer()?
|
|
738
|
-
.set_remote_description(&SessionDescription {
|
|
739
|
-
kind: SdpType::Answer,
|
|
740
|
-
sdp: sdp.to_owned(),
|
|
741
|
-
})
|
|
742
|
-
.map_err(BridgeError::from)?;
|
|
743
|
-
Ok(b"{}".to_vec())
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
fn set_direction(&mut self, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
747
|
-
let request: DirectionRequest = decode_json(request)?;
|
|
748
|
-
let entry = self
|
|
749
|
-
.tracks
|
|
750
|
-
.get(&request.name)
|
|
751
|
-
.ok_or_else(|| BridgeError::invalid(format!("unknown track: {}", request.name)))?;
|
|
752
|
-
let value = if request.active {
|
|
753
|
-
direction(&entry.spec.direction)?
|
|
754
|
-
} else {
|
|
755
|
-
TransceiverDirection::Inactive
|
|
756
|
-
};
|
|
757
|
-
entry
|
|
758
|
-
.transceiver
|
|
759
|
-
.set_direction(value)
|
|
760
|
-
.map_err(BridgeError::from)?;
|
|
761
|
-
Ok(b"{}".to_vec())
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
fn max_bitrate(&mut self, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
765
|
-
let request: BitrateRequest = decode_json(request)?;
|
|
766
|
-
if request.bits_per_second == 0 || request.bits_per_second > i32::MAX as u32 {
|
|
767
|
-
return Err(BridgeError::invalid(
|
|
768
|
-
"bitsPerSecond must be an integer in 1..=2147483647",
|
|
769
|
-
));
|
|
770
|
-
}
|
|
771
|
-
let entry = self
|
|
772
|
-
.tracks
|
|
773
|
-
.get(&request.name)
|
|
774
|
-
.ok_or_else(|| BridgeError::invalid(format!("unknown track: {}", request.name)))?;
|
|
775
|
-
if entry.spec.direction != "sendonly" {
|
|
776
|
-
return Err(BridgeError::invalid(format!(
|
|
777
|
-
"{} is not an outgoing track",
|
|
778
|
-
request.name
|
|
779
|
-
)));
|
|
780
|
-
}
|
|
781
|
-
entry
|
|
782
|
-
.transceiver
|
|
783
|
-
.set_send_bitrate(None, Some(request.bits_per_second as i32))
|
|
784
|
-
.map_err(BridgeError::from)?;
|
|
785
|
-
Ok(b"{}".to_vec())
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
fn stats(&mut self) -> Result<Vec<u8>, BridgeError> {
|
|
789
|
-
let report = self
|
|
790
|
-
.require_peer()?
|
|
791
|
-
.get_stats()
|
|
792
|
-
.map_err(BridgeError::from)?;
|
|
793
|
-
serde_json::to_vec(&stats_json(report))
|
|
794
|
-
.map_err(|e| BridgeError::native(format!("serialize stats response: {e}")))
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
fn media_snapshot(&self) -> Result<Vec<u8>, BridgeError> {
|
|
798
|
-
let control = self.shared.events.snapshot();
|
|
799
|
-
let video = self.shared.video.snapshot();
|
|
800
|
-
let audio = self.shared.audio.snapshot();
|
|
801
|
-
let video_bytes = video
|
|
802
|
-
.get("queuedBytes")
|
|
803
|
-
.and_then(Value::as_u64)
|
|
804
|
-
.unwrap_or(0);
|
|
805
|
-
let audio_bytes = audio
|
|
806
|
-
.get("queuedBytes")
|
|
807
|
-
.and_then(Value::as_u64)
|
|
808
|
-
.unwrap_or(0);
|
|
809
|
-
serde_json::to_vec(&json!({
|
|
810
|
-
"closed": !self.shared.gate.accepting.load(Ordering::Acquire),
|
|
811
|
-
"queuedControl": control.queued,
|
|
812
|
-
"queuedVideo": video.get("queued").and_then(Value::as_u64).unwrap_or(0),
|
|
813
|
-
"queuedAudio": audio.get("queued").and_then(Value::as_u64).unwrap_or(0),
|
|
814
|
-
"queuedBytes": (control.bytes as u64).saturating_add(video_bytes).saturating_add(audio_bytes),
|
|
815
|
-
"droppedVideo": video.get("dropped").and_then(Value::as_str).unwrap_or("0"),
|
|
816
|
-
"droppedAudio": audio.get("dropped").and_then(Value::as_str).unwrap_or("0"),
|
|
817
|
-
"deliveredVideo": video.get("delivered").and_then(Value::as_str).unwrap_or("0"),
|
|
818
|
-
"deliveredAudio": audio.get("delivered").and_then(Value::as_str).unwrap_or("0"),
|
|
819
|
-
"pendingRequests": 0,
|
|
820
|
-
}))
|
|
821
|
-
.map_err(|e| BridgeError::native(format!("serialize media snapshot: {e}")))
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
fn send(&mut self, channel: u32, bytes: &[u8]) -> Result<(), BridgeError> {
|
|
825
|
-
if bytes.len() > MAX_MESSAGE_BYTES {
|
|
826
|
-
return Err(BridgeError::overflow(format!(
|
|
827
|
-
"data channel message exceeds {MAX_MESSAGE_BYTES} bytes"
|
|
828
|
-
)));
|
|
829
|
-
}
|
|
830
|
-
let channels = self.channels.as_ref().ok_or_else(BridgeError::closed)?;
|
|
831
|
-
let channel = match channel {
|
|
832
|
-
CHANNEL_CONTROL => &channels.control,
|
|
833
|
-
CHANNEL_DATA => &channels.data,
|
|
834
|
-
_ => return Err(BridgeError::invalid("unknown data channel")),
|
|
835
|
-
};
|
|
836
|
-
if channel.state() != DataChannelState::Open {
|
|
837
|
-
return Err(BridgeError::closed());
|
|
838
|
-
}
|
|
839
|
-
if channel.buffered_amount().saturating_add(bytes.len() as u64) > MAX_BUFFERED_SEND_BYTES {
|
|
840
|
-
return Err(BridgeError::overflow(
|
|
841
|
-
"native data channel buffered amount bound exceeded",
|
|
842
|
-
));
|
|
843
|
-
}
|
|
844
|
-
channel.send(bytes, true).map_err(BridgeError::from)
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
fn call(&mut self, operation: u32, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
848
|
-
match operation {
|
|
849
|
-
CALL_PREPARE => self.prepare(request),
|
|
850
|
-
CALL_ANSWER => self.answer(request),
|
|
851
|
-
CALL_DIRECTION => self.set_direction(request),
|
|
852
|
-
CALL_MAX_BITRATE => self.max_bitrate(request),
|
|
853
|
-
CALL_STATS => self.stats(),
|
|
854
|
-
CALL_MEDIA_SNAPSHOT => self.media_snapshot(),
|
|
855
|
-
_ => Err(BridgeError::invalid("unknown native call operation")),
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
fn require_peer(&self) -> Result<&PeerConnection, BridgeError> {
|
|
860
|
-
self.peer.as_ref().ok_or_else(BridgeError::closed)
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
fn shutdown(mut self) {
|
|
864
|
-
self.channels.take();
|
|
865
|
-
lock(&self.shared.remote_tracks).clear();
|
|
866
|
-
self.tracks.clear();
|
|
867
|
-
self.peer.take();
|
|
868
|
-
self.factory.take();
|
|
869
|
-
self.shared.gate.wait_zero();
|
|
870
|
-
self.shared.close_queues();
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
fn validate_prepare(request: &PrepareRequest) -> Result<(), BridgeError> {
|
|
875
|
-
if request.servers.len() > 64 {
|
|
876
|
-
return Err(BridgeError::invalid("at most 64 ICE servers are supported"));
|
|
877
|
-
}
|
|
878
|
-
if request.tracks.len() > 64 {
|
|
879
|
-
return Err(BridgeError::invalid("at most 64 tracks are supported"));
|
|
880
|
-
}
|
|
881
|
-
let mut names = std::collections::HashSet::new();
|
|
882
|
-
for server in &request.servers {
|
|
883
|
-
if server.urls.is_empty() || server.urls.len() > 16 {
|
|
884
|
-
return Err(BridgeError::invalid(
|
|
885
|
-
"each ICE server must contain 1..=16 URLs",
|
|
886
|
-
));
|
|
887
|
-
}
|
|
888
|
-
for url in &server.urls {
|
|
889
|
-
if url.is_empty() || url.len() > 2048 {
|
|
890
|
-
return Err(BridgeError::invalid("invalid ICE URL length"));
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
for track in &request.tracks {
|
|
895
|
-
if track.name.is_empty() || track.name.len() > 256 || track.name.contains('\0') {
|
|
896
|
-
return Err(BridgeError::invalid("invalid track name"));
|
|
897
|
-
}
|
|
898
|
-
media_kind(&track.kind)?;
|
|
899
|
-
direction(&track.direction)?;
|
|
900
|
-
if !names.insert(track.name.as_str()) {
|
|
901
|
-
return Err(BridgeError::invalid("duplicate track name"));
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
Ok(())
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
fn media_kind(kind: &str) -> Result<MediaKind, BridgeError> {
|
|
908
|
-
match kind {
|
|
909
|
-
"video" => Ok(MediaKind::Video),
|
|
910
|
-
"audio" => Ok(MediaKind::Audio),
|
|
911
|
-
_ => Err(BridgeError::invalid(format!("unknown track kind: {kind}"))),
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
fn direction(direction: &str) -> Result<TransceiverDirection, BridgeError> {
|
|
916
|
-
match direction {
|
|
917
|
-
"recvonly" => Ok(TransceiverDirection::RecvOnly),
|
|
918
|
-
"sendonly" => Ok(TransceiverDirection::SendOnly),
|
|
919
|
-
_ => Err(BridgeError::invalid(format!(
|
|
920
|
-
"unknown track direction: {direction}"
|
|
921
|
-
))),
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
fn decode_json<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T, BridgeError> {
|
|
926
|
-
serde_json::from_slice(bytes).map_err(|e| BridgeError::invalid(format!("invalid JSON: {e}")))
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
fn observer(shared: Arc<Shared>) -> PeerConnectionObserver {
|
|
930
|
-
PeerConnectionObserver::new()
|
|
931
|
-
.on_connection_state_change({
|
|
932
|
-
let shared = Arc::clone(&shared);
|
|
933
|
-
move |state| {
|
|
934
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
935
|
-
return;
|
|
936
|
-
};
|
|
937
|
-
shared.emit(
|
|
938
|
-
json!({ "type": "state", "state": connection_state(state) }),
|
|
939
|
-
&[],
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
})
|
|
943
|
-
.on_ice_gathering_change({
|
|
944
|
-
let shared = Arc::clone(&shared);
|
|
945
|
-
move |state| {
|
|
946
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
947
|
-
return;
|
|
948
|
-
};
|
|
949
|
-
if state == IceGatheringState::Complete {
|
|
950
|
-
shared.emit(json!({ "type": "ice" }), &[]);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
})
|
|
954
|
-
.on_ice_candidate({
|
|
955
|
-
let shared = Arc::clone(&shared);
|
|
956
|
-
move |candidate| {
|
|
957
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
958
|
-
return;
|
|
959
|
-
};
|
|
960
|
-
shared.emit(
|
|
961
|
-
json!({
|
|
962
|
-
"type": "ice",
|
|
963
|
-
"candidate": {
|
|
964
|
-
"candidate": candidate.candidate,
|
|
965
|
-
"sdp_mid": candidate.sdp_mid,
|
|
966
|
-
"sdp_mline_index": candidate.sdp_mline_index,
|
|
967
|
-
}
|
|
968
|
-
}),
|
|
969
|
-
&[],
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
})
|
|
973
|
-
.on_track({
|
|
974
|
-
let shared = Arc::clone(&shared);
|
|
975
|
-
move |track| shared.accept_remote(track)
|
|
976
|
-
})
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
fn wire_channel(channel: &mut DataChannel, name: &'static str, shared: Arc<Shared>) {
|
|
980
|
-
channel.on_message({
|
|
981
|
-
let shared = Arc::clone(&shared);
|
|
982
|
-
move |bytes, binary| {
|
|
983
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
984
|
-
return;
|
|
985
|
-
};
|
|
986
|
-
if !binary {
|
|
987
|
-
shared.emit(
|
|
988
|
-
json!({
|
|
989
|
-
"type": "error",
|
|
990
|
-
"code": "Protocol",
|
|
991
|
-
"message": format!("{name} data channel delivered a nonbinary message")
|
|
992
|
-
}),
|
|
993
|
-
&[],
|
|
994
|
-
);
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
if bytes.len() > MAX_MESSAGE_BYTES {
|
|
998
|
-
shared.emit(
|
|
999
|
-
json!({
|
|
1000
|
-
"type": "error",
|
|
1001
|
-
"code": "Overflow",
|
|
1002
|
-
"message": format!("{name} data channel message exceeds local bound")
|
|
1003
|
-
}),
|
|
1004
|
-
&[],
|
|
1005
|
-
);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
shared.emit(json!({ "type": "message", "channel": name }), bytes);
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
channel.on_state_change(move |state| {
|
|
1012
|
-
let Some(_guard) = shared.gate.enter() else {
|
|
1013
|
-
return;
|
|
1014
|
-
};
|
|
1015
|
-
match state {
|
|
1016
|
-
DataChannelState::Open => shared.emit(
|
|
1017
|
-
json!({ "type": "channel", "channel": name, "open": true }),
|
|
1018
|
-
&[],
|
|
1019
|
-
),
|
|
1020
|
-
DataChannelState::Closed => shared.emit(
|
|
1021
|
-
json!({ "type": "channel", "channel": name, "open": false }),
|
|
1022
|
-
&[],
|
|
1023
|
-
),
|
|
1024
|
-
DataChannelState::Connecting | DataChannelState::Closing => {}
|
|
1025
|
-
}
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
fn connection_state(state: PeerConnectionState) -> &'static str {
|
|
1030
|
-
match state {
|
|
1031
|
-
PeerConnectionState::New => "new",
|
|
1032
|
-
PeerConnectionState::Connecting => "connecting",
|
|
1033
|
-
PeerConnectionState::Connected => "connected",
|
|
1034
|
-
PeerConnectionState::Disconnected => "disconnected",
|
|
1035
|
-
PeerConnectionState::Failed => "failed",
|
|
1036
|
-
PeerConnectionState::Closed => "closed",
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
fn stats_json(report: StatsReport) -> Value {
|
|
1041
|
-
let mut entries = Vec::new();
|
|
1042
|
-
for entry in report.inbound_rtp {
|
|
1043
|
-
entries.push(json!({
|
|
1044
|
-
"id": format!("inbound-rtp-{}", entry.ssrc),
|
|
1045
|
-
"type": "inbound-rtp",
|
|
1046
|
-
"ssrc": entry.ssrc,
|
|
1047
|
-
"kind": stream_kind(entry.kind),
|
|
1048
|
-
"packetsReceived": entry.packets_received,
|
|
1049
|
-
"bytesReceived": entry.bytes_received.to_string(),
|
|
1050
|
-
"jitter": entry.jitter_s,
|
|
1051
|
-
"packetsLost": entry.packets_lost,
|
|
1052
|
-
"nackCount": entry.nack_count,
|
|
1053
|
-
"pliCount": entry.pli_count,
|
|
1054
|
-
"firCount": entry.fir_count,
|
|
1055
|
-
"totalDecodeTime": entry.total_decode_time_s,
|
|
1056
|
-
"framesPerSecond": entry.frames_per_second,
|
|
1057
|
-
"framesDecoded": entry.frames_decoded,
|
|
1058
|
-
"framesDropped": entry.frames_dropped,
|
|
1059
|
-
"frameWidth": entry.frame_width,
|
|
1060
|
-
"frameHeight": entry.frame_height,
|
|
1061
|
-
}));
|
|
1062
|
-
}
|
|
1063
|
-
for entry in report.outbound_rtp {
|
|
1064
|
-
entries.push(json!({
|
|
1065
|
-
"id": format!("outbound-rtp-{}", entry.ssrc),
|
|
1066
|
-
"type": "outbound-rtp",
|
|
1067
|
-
"ssrc": entry.ssrc,
|
|
1068
|
-
"kind": stream_kind(entry.kind),
|
|
1069
|
-
"packetsSent": entry.packets_sent.to_string(),
|
|
1070
|
-
"bytesSent": entry.bytes_sent.to_string(),
|
|
1071
|
-
"targetBitrate": entry.target_bitrate_bps,
|
|
1072
|
-
"roundTripTime": entry.round_trip_time_s,
|
|
1073
|
-
"totalRoundTripTime": entry.total_round_trip_time_s,
|
|
1074
|
-
"fractionLost": entry.fraction_lost,
|
|
1075
|
-
"packetsLost": entry.packets_lost,
|
|
1076
|
-
"retransmittedPacketsSent": entry.retransmitted_packets_sent.to_string(),
|
|
1077
|
-
"nackCount": entry.nack_count,
|
|
1078
|
-
"pliCount": entry.pli_count,
|
|
1079
|
-
"firCount": entry.fir_count,
|
|
1080
|
-
"framesPerSecond": entry.frames_per_second,
|
|
1081
|
-
"framesSent": entry.frames_sent,
|
|
1082
|
-
"frameWidth": entry.frame_width,
|
|
1083
|
-
"frameHeight": entry.frame_height,
|
|
1084
|
-
}));
|
|
1085
|
-
}
|
|
1086
|
-
for (index, entry) in report.candidate_pairs.into_iter().enumerate() {
|
|
1087
|
-
let local_id = format!("local-candidate-{index}");
|
|
1088
|
-
entries.push(json!({
|
|
1089
|
-
"id": local_id,
|
|
1090
|
-
"type": "local-candidate",
|
|
1091
|
-
"candidateType": candidate_type(entry.local_candidate_type),
|
|
1092
|
-
"relayProtocol": relay_protocol(entry.local_relay_protocol),
|
|
1093
|
-
}));
|
|
1094
|
-
entries.push(json!({
|
|
1095
|
-
"id": format!("candidate-pair-{index}"),
|
|
1096
|
-
"type": "candidate-pair",
|
|
1097
|
-
"state": pair_state(entry.state),
|
|
1098
|
-
"nominated": entry.nominated,
|
|
1099
|
-
"writable": entry.writable,
|
|
1100
|
-
"priority": entry.priority.to_string(),
|
|
1101
|
-
"bytesSent": entry.bytes_sent.to_string(),
|
|
1102
|
-
"bytesReceived": entry.bytes_received.to_string(),
|
|
1103
|
-
"packetsSent": entry.packets_sent.to_string(),
|
|
1104
|
-
"packetsReceived": entry.packets_received.to_string(),
|
|
1105
|
-
"currentRoundTripTime": entry.current_round_trip_time_s,
|
|
1106
|
-
"totalRoundTripTime": entry.total_round_trip_time_s,
|
|
1107
|
-
"availableOutgoingBitrate": entry.available_outgoing_bitrate_bps,
|
|
1108
|
-
"availableIncomingBitrate": entry.available_incoming_bitrate_bps,
|
|
1109
|
-
"localCandidateId": format!("local-candidate-{index}"),
|
|
1110
|
-
}));
|
|
1111
|
-
}
|
|
1112
|
-
Value::Array(entries)
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
fn stream_kind(kind: reactor_webrtc::StreamKind) -> &'static str {
|
|
1116
|
-
match kind {
|
|
1117
|
-
reactor_webrtc::StreamKind::Audio => "audio",
|
|
1118
|
-
reactor_webrtc::StreamKind::Video => "video",
|
|
1119
|
-
reactor_webrtc::StreamKind::Unknown => "unknown",
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
fn pair_state(state: reactor_webrtc::IceCandidatePairState) -> &'static str {
|
|
1124
|
-
match state {
|
|
1125
|
-
reactor_webrtc::IceCandidatePairState::Waiting => "waiting",
|
|
1126
|
-
reactor_webrtc::IceCandidatePairState::InProgress => "in-progress",
|
|
1127
|
-
reactor_webrtc::IceCandidatePairState::Failed => "failed",
|
|
1128
|
-
reactor_webrtc::IceCandidatePairState::Succeeded => "succeeded",
|
|
1129
|
-
reactor_webrtc::IceCandidatePairState::Cancelled => "cancelled",
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
fn candidate_type(value: IceCandidateType) -> &'static str {
|
|
1134
|
-
match value {
|
|
1135
|
-
IceCandidateType::Host => "host",
|
|
1136
|
-
IceCandidateType::Srflx => "srflx",
|
|
1137
|
-
IceCandidateType::Prflx => "prflx",
|
|
1138
|
-
IceCandidateType::Relay => "relay",
|
|
1139
|
-
IceCandidateType::Unknown => "unknown",
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
fn relay_protocol(value: RelayProtocol) -> &'static str {
|
|
1144
|
-
match value {
|
|
1145
|
-
RelayProtocol::Udp => "udp",
|
|
1146
|
-
RelayProtocol::Tcp => "tcp",
|
|
1147
|
-
RelayProtocol::Tls => "tls",
|
|
1148
|
-
RelayProtocol::NotRelayed => "",
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
type Reply = SyncSender<Result<Vec<u8>, BridgeError>>;
|
|
1153
|
-
|
|
1154
|
-
enum Command {
|
|
1155
|
-
Call {
|
|
1156
|
-
operation: u32,
|
|
1157
|
-
request: Vec<u8>,
|
|
1158
|
-
reply: Reply,
|
|
1159
|
-
},
|
|
1160
|
-
Send {
|
|
1161
|
-
channel: u32,
|
|
1162
|
-
bytes: Vec<u8>,
|
|
1163
|
-
reply: SyncSender<Result<(), BridgeError>>,
|
|
1164
|
-
},
|
|
1165
|
-
Shutdown,
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
fn worker_loop(shared: Arc<Shared>, commands: Receiver<Command>) {
|
|
1169
|
-
let mut state = WorkerState::new(Arc::clone(&shared));
|
|
1170
|
-
while let Ok(command) = commands.recv() {
|
|
1171
|
-
match command {
|
|
1172
|
-
Command::Call {
|
|
1173
|
-
operation,
|
|
1174
|
-
request,
|
|
1175
|
-
reply,
|
|
1176
|
-
} => {
|
|
1177
|
-
let result = if shared.gate.accepting.load(Ordering::Acquire) {
|
|
1178
|
-
state.call(operation, &request)
|
|
1179
|
-
} else {
|
|
1180
|
-
Err(BridgeError::closed())
|
|
1181
|
-
};
|
|
1182
|
-
let _ = reply.send(result);
|
|
1183
|
-
}
|
|
1184
|
-
Command::Send {
|
|
1185
|
-
channel,
|
|
1186
|
-
bytes,
|
|
1187
|
-
reply,
|
|
1188
|
-
} => {
|
|
1189
|
-
let result = if shared.gate.accepting.load(Ordering::Acquire) {
|
|
1190
|
-
state.send(channel, &bytes)
|
|
1191
|
-
} else {
|
|
1192
|
-
Err(BridgeError::closed())
|
|
1193
|
-
};
|
|
1194
|
-
let _ = reply.send(result);
|
|
1195
|
-
}
|
|
1196
|
-
Command::Shutdown => break,
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
state.shutdown();
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
#[repr(C)]
|
|
1203
|
-
pub struct ReactorEffectPeer {
|
|
1204
|
-
gate: Arc<CallbackGate>,
|
|
1205
|
-
shared: Arc<Shared>,
|
|
1206
|
-
commands: Sender<Command>,
|
|
1207
|
-
worker: Mutex<Option<JoinHandle<()>>>,
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
impl ReactorEffectPeer {
|
|
1211
|
-
fn request(&self, operation: u32, request: &[u8]) -> Result<Vec<u8>, BridgeError> {
|
|
1212
|
-
if !self.gate.accepting.load(Ordering::Acquire) {
|
|
1213
|
-
return Err(BridgeError::closed());
|
|
1214
|
-
}
|
|
1215
|
-
let (tx, rx) = mpsc::sync_channel(1);
|
|
1216
|
-
self.commands
|
|
1217
|
-
.send(Command::Call {
|
|
1218
|
-
operation,
|
|
1219
|
-
request: request.to_vec(),
|
|
1220
|
-
reply: tx,
|
|
1221
|
-
})
|
|
1222
|
-
.map_err(|_| BridgeError::closed())?;
|
|
1223
|
-
rx.recv().map_err(|_| BridgeError::closed())?
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
fn send(&self, channel: u32, bytes: &[u8]) -> Result<(), BridgeError> {
|
|
1227
|
-
if !self.gate.accepting.load(Ordering::Acquire) {
|
|
1228
|
-
return Err(BridgeError::closed());
|
|
1229
|
-
}
|
|
1230
|
-
let (tx, rx) = mpsc::sync_channel(1);
|
|
1231
|
-
self.commands
|
|
1232
|
-
.send(Command::Send {
|
|
1233
|
-
channel,
|
|
1234
|
-
bytes: bytes.to_vec(),
|
|
1235
|
-
reply: tx,
|
|
1236
|
-
})
|
|
1237
|
-
.map_err(|_| BridgeError::closed())?;
|
|
1238
|
-
rx.recv().map_err(|_| BridgeError::closed())?
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
fn close(&self) {
|
|
1242
|
-
self.gate.close();
|
|
1243
|
-
self.shared.close_queues();
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
fn shutdown(&self) -> Result<(), BridgeError> {
|
|
1247
|
-
self.close();
|
|
1248
|
-
let mut worker = lock(&self.worker);
|
|
1249
|
-
let Some(handle) = worker.take() else {
|
|
1250
|
-
return Ok(());
|
|
1251
|
-
};
|
|
1252
|
-
let _ = self.commands.send(Command::Shutdown);
|
|
1253
|
-
handle
|
|
1254
|
-
.join()
|
|
1255
|
-
.map_err(|_| BridgeError::native("native owner thread panicked"))
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
#[no_mangle]
|
|
1260
|
-
pub extern "C" fn reactor_effect_abi_version() -> u32 {
|
|
1261
|
-
ABI_VERSION
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// A unique marker makes the same identity inspectable without executing a
|
|
1265
|
-
// foreign-platform library during package staging. The exported function lets
|
|
1266
|
-
// runtime preflight compare the loaded image with that inspected artifact.
|
|
1267
|
-
static BUILD_IDENTITY: &str = concat!(
|
|
1268
|
-
"reactor-effect-native:build-identity:",
|
|
1269
|
-
env!("REACTOR_EFFECT_BUILD_IDENTITY"),
|
|
1270
|
-
":end\0"
|
|
1271
|
-
);
|
|
1272
|
-
|
|
1273
|
-
#[no_mangle]
|
|
1274
|
-
pub extern "C" fn reactor_effect_build_identity() -> *const std::ffi::c_char {
|
|
1275
|
-
BUILD_IDENTITY.as_ptr().cast()
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
#[no_mangle]
|
|
1279
|
-
pub extern "C" fn reactor_effect_peer_create() -> *mut ReactorEffectPeer {
|
|
1280
|
-
catch_unwind(AssertUnwindSafe(|| {
|
|
1281
|
-
let gate = Arc::new(CallbackGate::new());
|
|
1282
|
-
let shared = Arc::new(Shared::new(Arc::clone(&gate)));
|
|
1283
|
-
let (commands, receiver) = mpsc::channel();
|
|
1284
|
-
let worker_shared = Arc::clone(&shared);
|
|
1285
|
-
let handle = match thread::Builder::new()
|
|
1286
|
-
.name("reactor-effect-native".into())
|
|
1287
|
-
.spawn(move || worker_loop(worker_shared, receiver))
|
|
1288
|
-
{
|
|
1289
|
-
Ok(handle) => handle,
|
|
1290
|
-
Err(_) => return ptr::null_mut(),
|
|
1291
|
-
};
|
|
1292
|
-
Box::into_raw(Box::new(ReactorEffectPeer {
|
|
1293
|
-
gate,
|
|
1294
|
-
shared,
|
|
1295
|
-
commands,
|
|
1296
|
-
worker: Mutex::new(Some(handle)),
|
|
1297
|
-
}))
|
|
1298
|
-
}))
|
|
1299
|
-
.unwrap_or(ptr::null_mut())
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
#[no_mangle]
|
|
1303
|
-
/// Invoke one serialized peer operation through the C ABI.
|
|
1304
|
-
///
|
|
1305
|
-
/// # Safety
|
|
1306
|
-
/// `peer` must be a live handle returned by [`reactor_effect_peer_create`].
|
|
1307
|
-
/// Non-null input/output pointers must reference at least their declared byte
|
|
1308
|
-
/// lengths, and `response_len` must be writable for one `usize`.
|
|
1309
|
-
pub unsafe extern "C" fn reactor_effect_peer_call(
|
|
1310
|
-
peer: *mut ReactorEffectPeer,
|
|
1311
|
-
operation: u32,
|
|
1312
|
-
request: *const u8,
|
|
1313
|
-
request_len: usize,
|
|
1314
|
-
response: *mut u8,
|
|
1315
|
-
response_cap: usize,
|
|
1316
|
-
response_len: *mut usize,
|
|
1317
|
-
) -> i32 {
|
|
1318
|
-
ffi_status(|| {
|
|
1319
|
-
let peer = peer_ref(peer)?;
|
|
1320
|
-
require_call_buffer(response, response_cap, response_len)?;
|
|
1321
|
-
let request = input(request, request_len)?;
|
|
1322
|
-
if request.len() > MAX_REQUEST_BYTES {
|
|
1323
|
-
let native = BridgeError::overflow("native request exceeds 1 MiB");
|
|
1324
|
-
let bytes = native.json();
|
|
1325
|
-
copy_out(&bytes, response, response_cap, response_len);
|
|
1326
|
-
return Ok(status_for(&native));
|
|
1327
|
-
}
|
|
1328
|
-
let (status, bytes) = match peer.request(operation, request) {
|
|
1329
|
-
Ok(bytes) => (STATUS_OK, bytes),
|
|
1330
|
-
Err(error) => (status_for(&error), error.json()),
|
|
1331
|
-
};
|
|
1332
|
-
if bytes.len() > response_cap {
|
|
1333
|
-
let native = BridgeError::overflow("native call response exceeds 4 MiB");
|
|
1334
|
-
let bytes = native.json();
|
|
1335
|
-
copy_out(&bytes, response, response_cap, response_len);
|
|
1336
|
-
return Ok(status_for(&native));
|
|
1337
|
-
}
|
|
1338
|
-
copy_out(&bytes, response, response_cap, response_len);
|
|
1339
|
-
Ok(status)
|
|
1340
|
-
})
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
#[no_mangle]
|
|
1344
|
-
/// Send one binary SCTP message on a bridge-owned channel.
|
|
1345
|
-
///
|
|
1346
|
-
/// # Safety
|
|
1347
|
-
/// `peer` must be live. `data` must reference `data_len` readable bytes when
|
|
1348
|
-
/// nonempty; `error` must reference `error_cap` writable bytes and `error_len`
|
|
1349
|
-
/// must be writable for one `usize`.
|
|
1350
|
-
pub unsafe extern "C" fn reactor_effect_peer_send(
|
|
1351
|
-
peer: *mut ReactorEffectPeer,
|
|
1352
|
-
channel: u32,
|
|
1353
|
-
data: *const u8,
|
|
1354
|
-
data_len: usize,
|
|
1355
|
-
error: *mut u8,
|
|
1356
|
-
error_cap: usize,
|
|
1357
|
-
error_len: *mut usize,
|
|
1358
|
-
) -> i32 {
|
|
1359
|
-
ffi_status(|| {
|
|
1360
|
-
require_error_buffer(error, error_cap, error_len)?;
|
|
1361
|
-
let peer = peer_ref(peer)?;
|
|
1362
|
-
let data = input(data, data_len)?;
|
|
1363
|
-
match peer.send(channel, data) {
|
|
1364
|
-
Ok(()) => {
|
|
1365
|
-
copy_out(&[], error, error_cap, error_len);
|
|
1366
|
-
Ok(STATUS_OK)
|
|
1367
|
-
}
|
|
1368
|
-
Err(native) => {
|
|
1369
|
-
let bytes = native.json();
|
|
1370
|
-
copy_out(&bytes, error, error_cap, error_len);
|
|
1371
|
-
Ok(status_for(&native))
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
})
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
#[no_mangle]
|
|
1378
|
-
/// Poll the critical transport event queue.
|
|
1379
|
-
///
|
|
1380
|
-
/// # Safety
|
|
1381
|
-
/// `peer` must be live. When `out_cap` is nonzero, `out` must reference that
|
|
1382
|
-
/// many writable bytes; `out_len` must be writable for one `usize`.
|
|
1383
|
-
pub unsafe extern "C" fn reactor_effect_peer_poll_event(
|
|
1384
|
-
peer: *mut ReactorEffectPeer,
|
|
1385
|
-
timeout_ms: u32,
|
|
1386
|
-
out: *mut u8,
|
|
1387
|
-
out_cap: usize,
|
|
1388
|
-
out_len: *mut usize,
|
|
1389
|
-
) -> i32 {
|
|
1390
|
-
ffi_poll(
|
|
1391
|
-
peer,
|
|
1392
|
-
&|p| &p.shared.events,
|
|
1393
|
-
timeout_ms,
|
|
1394
|
-
out,
|
|
1395
|
-
out_cap,
|
|
1396
|
-
out_len,
|
|
1397
|
-
)
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
#[no_mangle]
|
|
1401
|
-
/// Poll the decoded-video queue.
|
|
1402
|
-
///
|
|
1403
|
-
/// # Safety
|
|
1404
|
-
/// `peer` must be live. When `out_cap` is nonzero, `out` must reference that
|
|
1405
|
-
/// many writable bytes; `out_len` must be writable for one `usize`.
|
|
1406
|
-
pub unsafe extern "C" fn reactor_effect_peer_poll_video(
|
|
1407
|
-
peer: *mut ReactorEffectPeer,
|
|
1408
|
-
timeout_ms: u32,
|
|
1409
|
-
out: *mut u8,
|
|
1410
|
-
out_cap: usize,
|
|
1411
|
-
out_len: *mut usize,
|
|
1412
|
-
) -> i32 {
|
|
1413
|
-
ffi_poll(
|
|
1414
|
-
peer,
|
|
1415
|
-
&|p| &p.shared.video.queue,
|
|
1416
|
-
timeout_ms,
|
|
1417
|
-
out,
|
|
1418
|
-
out_cap,
|
|
1419
|
-
out_len,
|
|
1420
|
-
)
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
#[no_mangle]
|
|
1424
|
-
/// Poll the decoded-audio queue.
|
|
1425
|
-
///
|
|
1426
|
-
/// # Safety
|
|
1427
|
-
/// `peer` must be live. When `out_cap` is nonzero, `out` must reference that
|
|
1428
|
-
/// many writable bytes; `out_len` must be writable for one `usize`.
|
|
1429
|
-
pub unsafe extern "C" fn reactor_effect_peer_poll_audio(
|
|
1430
|
-
peer: *mut ReactorEffectPeer,
|
|
1431
|
-
timeout_ms: u32,
|
|
1432
|
-
out: *mut u8,
|
|
1433
|
-
out_cap: usize,
|
|
1434
|
-
out_len: *mut usize,
|
|
1435
|
-
) -> i32 {
|
|
1436
|
-
ffi_poll(
|
|
1437
|
-
peer,
|
|
1438
|
-
&|p| &p.shared.audio.queue,
|
|
1439
|
-
timeout_ms,
|
|
1440
|
-
out,
|
|
1441
|
-
out_cap,
|
|
1442
|
-
out_len,
|
|
1443
|
-
)
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
#[no_mangle]
|
|
1447
|
-
/// Fence callback/event admission immediately.
|
|
1448
|
-
///
|
|
1449
|
-
/// # Safety
|
|
1450
|
-
/// `peer` must be null or a live handle returned by
|
|
1451
|
-
/// [`reactor_effect_peer_create`]. It must not have been destroyed.
|
|
1452
|
-
pub unsafe extern "C" fn reactor_effect_peer_close(peer: *mut ReactorEffectPeer) {
|
|
1453
|
-
let _ = catch_unwind(AssertUnwindSafe(|| {
|
|
1454
|
-
if let Some(peer) = peer.as_ref() {
|
|
1455
|
-
peer.close();
|
|
1456
|
-
}
|
|
1457
|
-
}));
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
#[no_mangle]
|
|
1461
|
-
/// Join native ownership after callback admission has been fenced.
|
|
1462
|
-
///
|
|
1463
|
-
/// # Safety
|
|
1464
|
-
/// `peer` must be live. `error` must reference `error_cap` writable bytes and
|
|
1465
|
-
/// `error_len` must be writable for one `usize`.
|
|
1466
|
-
pub unsafe extern "C" fn reactor_effect_peer_shutdown(
|
|
1467
|
-
peer: *mut ReactorEffectPeer,
|
|
1468
|
-
error: *mut u8,
|
|
1469
|
-
error_cap: usize,
|
|
1470
|
-
error_len: *mut usize,
|
|
1471
|
-
) -> i32 {
|
|
1472
|
-
ffi_status(|| {
|
|
1473
|
-
require_error_buffer(error, error_cap, error_len)?;
|
|
1474
|
-
let peer = peer_ref(peer)?;
|
|
1475
|
-
match peer.shutdown() {
|
|
1476
|
-
Ok(()) => {
|
|
1477
|
-
copy_out(&[], error, error_cap, error_len);
|
|
1478
|
-
Ok(STATUS_OK)
|
|
1479
|
-
}
|
|
1480
|
-
Err(native) => {
|
|
1481
|
-
let bytes = native.json();
|
|
1482
|
-
copy_out(&bytes, error, error_cap, error_len);
|
|
1483
|
-
Ok(status_for(&native))
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
})
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
#[no_mangle]
|
|
1490
|
-
/// Shut down and free an opaque peer handle.
|
|
1491
|
-
///
|
|
1492
|
-
/// # Safety
|
|
1493
|
-
/// `peer` must be null or a handle returned by [`reactor_effect_peer_create`]
|
|
1494
|
-
/// that has not already been passed to this function. All foreign calls using
|
|
1495
|
-
/// the handle, including calls queued in a host FFI executor, must have returned.
|
|
1496
|
-
/// Joining the native owner alone does not establish that host-side condition.
|
|
1497
|
-
pub unsafe extern "C" fn reactor_effect_peer_destroy(peer: *mut ReactorEffectPeer) {
|
|
1498
|
-
let _ = catch_unwind(AssertUnwindSafe(|| {
|
|
1499
|
-
if peer.is_null() {
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
let peer = Box::from_raw(peer);
|
|
1503
|
-
let _ = peer.shutdown();
|
|
1504
|
-
}));
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
unsafe fn ffi_poll(
|
|
1508
|
-
peer: *mut ReactorEffectPeer,
|
|
1509
|
-
queue: &dyn Fn(&ReactorEffectPeer) -> &PacketQueue,
|
|
1510
|
-
timeout_ms: u32,
|
|
1511
|
-
out: *mut u8,
|
|
1512
|
-
out_cap: usize,
|
|
1513
|
-
out_len: *mut usize,
|
|
1514
|
-
) -> i32 {
|
|
1515
|
-
ffi_status(|| {
|
|
1516
|
-
let peer = peer_ref(peer)?;
|
|
1517
|
-
if out_len.is_null() || (out_cap != 0 && out.is_null()) {
|
|
1518
|
-
return Err(FfiError::Invalid);
|
|
1519
|
-
}
|
|
1520
|
-
match queue(peer).poll(Duration::from_millis(timeout_ms as u64), out_cap) {
|
|
1521
|
-
PollResult::Packet(packet) => {
|
|
1522
|
-
if out.is_null() && !packet.is_empty() {
|
|
1523
|
-
return Err(FfiError::Invalid);
|
|
1524
|
-
}
|
|
1525
|
-
copy_out(&packet, out, out_cap, out_len);
|
|
1526
|
-
Ok(STATUS_OK)
|
|
1527
|
-
}
|
|
1528
|
-
PollResult::Need(required) => {
|
|
1529
|
-
*out_len = required;
|
|
1530
|
-
Ok(STATUS_BUFFER_TOO_SMALL)
|
|
1531
|
-
}
|
|
1532
|
-
PollResult::Again => {
|
|
1533
|
-
*out_len = 0;
|
|
1534
|
-
Ok(STATUS_AGAIN)
|
|
1535
|
-
}
|
|
1536
|
-
PollResult::Closed => {
|
|
1537
|
-
*out_len = 0;
|
|
1538
|
-
Ok(STATUS_CLOSED)
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
})
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
enum FfiError {
|
|
1545
|
-
Invalid,
|
|
1546
|
-
Status(i32),
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
fn ffi_status(body: impl FnOnce() -> Result<i32, FfiError>) -> i32 {
|
|
1550
|
-
match catch_unwind(AssertUnwindSafe(body)) {
|
|
1551
|
-
Ok(Ok(status)) => status,
|
|
1552
|
-
Ok(Err(FfiError::Invalid)) => STATUS_INVALID,
|
|
1553
|
-
Ok(Err(FfiError::Status(status))) => status,
|
|
1554
|
-
Err(_) => STATUS_NATIVE,
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
unsafe fn peer_ref<'a>(peer: *mut ReactorEffectPeer) -> Result<&'a ReactorEffectPeer, FfiError> {
|
|
1559
|
-
peer.as_ref().ok_or(FfiError::Invalid)
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
unsafe fn input<'a>(data: *const u8, len: usize) -> Result<&'a [u8], FfiError> {
|
|
1563
|
-
if len == 0 {
|
|
1564
|
-
return Ok(&[]);
|
|
1565
|
-
}
|
|
1566
|
-
if data.is_null() {
|
|
1567
|
-
return Err(FfiError::Invalid);
|
|
1568
|
-
}
|
|
1569
|
-
Ok(slice::from_raw_parts(data, len))
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
unsafe fn require_call_buffer(out: *mut u8, cap: usize, len: *mut usize) -> Result<(), FfiError> {
|
|
1573
|
-
if len.is_null() || out.is_null() {
|
|
1574
|
-
return Err(FfiError::Invalid);
|
|
1575
|
-
}
|
|
1576
|
-
if cap < CALL_BUFFER_MIN {
|
|
1577
|
-
*len = CALL_BUFFER_MIN;
|
|
1578
|
-
return Err(FfiError::Status(STATUS_BUFFER_TOO_SMALL));
|
|
1579
|
-
}
|
|
1580
|
-
Ok(())
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
unsafe fn require_error_buffer(out: *mut u8, cap: usize, len: *mut usize) -> Result<(), FfiError> {
|
|
1584
|
-
if len.is_null() || out.is_null() {
|
|
1585
|
-
return Err(FfiError::Invalid);
|
|
1586
|
-
}
|
|
1587
|
-
if cap < ERROR_BUFFER_MIN {
|
|
1588
|
-
*len = ERROR_BUFFER_MIN;
|
|
1589
|
-
return Err(FfiError::Status(STATUS_BUFFER_TOO_SMALL));
|
|
1590
|
-
}
|
|
1591
|
-
Ok(())
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
unsafe fn copy_out(bytes: &[u8], out: *mut u8, cap: usize, len: *mut usize) {
|
|
1595
|
-
*len = bytes.len();
|
|
1596
|
-
if !bytes.is_empty() {
|
|
1597
|
-
debug_assert!(!out.is_null());
|
|
1598
|
-
debug_assert!(cap >= bytes.len());
|
|
1599
|
-
ptr::copy_nonoverlapping(bytes.as_ptr(), out, bytes.len());
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
fn status_for(error: &BridgeError) -> i32 {
|
|
1604
|
-
match error.code {
|
|
1605
|
-
"InvalidInput" | "Protocol" => STATUS_INVALID,
|
|
1606
|
-
"Closed" => STATUS_CLOSED,
|
|
1607
|
-
"Overflow" => STATUS_OVERFLOW,
|
|
1608
|
-
_ => STATUS_NATIVE,
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1
|
+
//! The native WebRTC transport of `reactor-effect-native`, exported as a C ABI.
|
|
2
|
+
//!
|
|
3
|
+
//! `include/reactor_effect_native.h` is the contract and [`ffi`] implements
|
|
4
|
+
//! it. Each peer runs on three kinds of threads:
|
|
5
|
+
//!
|
|
6
|
+
//! - its **owner thread** holds the libwebrtc peer connection and runs the
|
|
7
|
+
//! host's calls and sends one at a time;
|
|
8
|
+
//! - **libwebrtc threads** run callbacks, which only copy into bounded queues
|
|
9
|
+
//! and set readiness bits: they never call or wait on the host;
|
|
10
|
+
//! - its **notifier thread** is the one thread that calls the host, passing
|
|
11
|
+
//! it those readiness bits.
|
|
12
|
+
//!
|
|
13
|
+
//! The host drains the queues with nonblocking takes. Closing a peer fences
|
|
14
|
+
//! callback admission at once; shutting it down joins the owner thread, every
|
|
15
|
+
//! admitted callback and the notifier thread.
|
|
16
|
+
//!
|
|
17
|
+
//! The source tree follows those roles:
|
|
18
|
+
//!
|
|
19
|
+
//! - `ffi`: the exported functions and C structs;
|
|
20
|
+
//! - `abi`: the header's constants, checked against it by a test;
|
|
21
|
+
//! - `peer`: the peer handle, its owner thread and its libwebrtc callbacks;
|
|
22
|
+
//! - `protocol`: the JSON of requests, responses and event headers;
|
|
23
|
+
//! - `sync`: the queues, callback gate and notifier the threads share;
|
|
24
|
+
//! - `error`: failures and the ABI failure class of each.
|
|
25
|
+
|
|
26
|
+
mod abi;
|
|
27
|
+
mod error;
|
|
28
|
+
#[expect(
|
|
29
|
+
unsafe_code,
|
|
30
|
+
reason = "the C ABI exports symbols and turns caller pointers into checked views"
|
|
31
|
+
)]
|
|
32
|
+
pub mod ffi;
|
|
33
|
+
mod peer;
|
|
34
|
+
mod protocol;
|
|
35
|
+
mod sync;
|
|
1611
36
|
|
|
1612
37
|
#[cfg(test)]
|
|
1613
|
-
mod
|
|
1614
|
-
use super::*;
|
|
1615
|
-
use reactor_webrtc::{
|
|
1616
|
-
AudioFrame, AudioTrackOptions, AudioTrackSource, IceCandidate, VideoFrame,
|
|
1617
|
-
};
|
|
1618
|
-
use std::sync::atomic::AtomicBool;
|
|
1619
|
-
|
|
1620
|
-
#[derive(Default)]
|
|
1621
|
-
struct AnswererSignals {
|
|
1622
|
-
ice: Mutex<VecDeque<IceCandidate>>,
|
|
1623
|
-
connected: AtomicBool,
|
|
1624
|
-
channels: Mutex<HashMap<String, DataChannel>>,
|
|
1625
|
-
inbox: Mutex<Vec<(String, Vec<u8>)>>,
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
fn answerer_observer(signals: Arc<AnswererSignals>) -> PeerConnectionObserver {
|
|
1629
|
-
PeerConnectionObserver::new()
|
|
1630
|
-
.on_ice_candidate({
|
|
1631
|
-
let signals = Arc::clone(&signals);
|
|
1632
|
-
move |candidate| lock(&signals.ice).push_back(candidate)
|
|
1633
|
-
})
|
|
1634
|
-
.on_connection_state_change({
|
|
1635
|
-
let signals = Arc::clone(&signals);
|
|
1636
|
-
move |state| {
|
|
1637
|
-
if state == PeerConnectionState::Connected {
|
|
1638
|
-
signals.connected.store(true, Ordering::Release);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
})
|
|
1642
|
-
.on_data_channel(move |mut channel| {
|
|
1643
|
-
let label = channel.label();
|
|
1644
|
-
let inbox = Arc::clone(&signals);
|
|
1645
|
-
let message_label = label.clone();
|
|
1646
|
-
channel.on_message(move |bytes, binary| {
|
|
1647
|
-
assert!(binary, "bridge channels must remain binary");
|
|
1648
|
-
lock(&inbox.inbox).push((message_label.clone(), bytes.to_vec()));
|
|
1649
|
-
});
|
|
1650
|
-
lock(&signals.channels).insert(label, channel);
|
|
1651
|
-
})
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
fn packet_parts(packet: &[u8]) -> (Value, &[u8]) {
|
|
1655
|
-
assert!(packet.len() >= 4, "packet missing header length");
|
|
1656
|
-
let header_len = u32::from_le_bytes(packet[0..4].try_into().unwrap()) as usize;
|
|
1657
|
-
assert!(4 + header_len <= packet.len(), "header exceeds packet");
|
|
1658
|
-
let header =
|
|
1659
|
-
serde_json::from_slice(&packet[4..4 + header_len]).expect("packet header JSON");
|
|
1660
|
-
(header, &packet[4 + header_len..])
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
fn drain_bridge_events(
|
|
1664
|
-
shared: &Shared,
|
|
1665
|
-
answerer: &PeerConnection,
|
|
1666
|
-
bridge_connected: &mut bool,
|
|
1667
|
-
bridge_messages: &mut Vec<(String, Vec<u8>)>,
|
|
1668
|
-
) {
|
|
1669
|
-
loop {
|
|
1670
|
-
let packet = match shared.events.poll(Duration::ZERO, 2 * 1024 * 1024) {
|
|
1671
|
-
PollResult::Packet(packet) => packet,
|
|
1672
|
-
PollResult::Again | PollResult::Closed => break,
|
|
1673
|
-
PollResult::Need(size) => panic!("unexpected oversized bridge event: {size}"),
|
|
1674
|
-
};
|
|
1675
|
-
let (header, payload) = packet_parts(&packet);
|
|
1676
|
-
match header.get("type").and_then(Value::as_str) {
|
|
1677
|
-
Some("state")
|
|
1678
|
-
if header.get("state").and_then(Value::as_str) == Some("connected") =>
|
|
1679
|
-
{
|
|
1680
|
-
*bridge_connected = true;
|
|
1681
|
-
}
|
|
1682
|
-
Some("ice") => {
|
|
1683
|
-
if let Some(candidate) = header.get("candidate").and_then(Value::as_object) {
|
|
1684
|
-
let candidate = IceCandidate {
|
|
1685
|
-
candidate: candidate
|
|
1686
|
-
.get("candidate")
|
|
1687
|
-
.and_then(Value::as_str)
|
|
1688
|
-
.expect("candidate text")
|
|
1689
|
-
.to_owned(),
|
|
1690
|
-
sdp_mid: candidate
|
|
1691
|
-
.get("sdp_mid")
|
|
1692
|
-
.and_then(Value::as_str)
|
|
1693
|
-
.map(str::to_owned),
|
|
1694
|
-
sdp_mline_index: candidate
|
|
1695
|
-
.get("sdp_mline_index")
|
|
1696
|
-
.and_then(Value::as_u64)
|
|
1697
|
-
.map(|value| value as u16),
|
|
1698
|
-
};
|
|
1699
|
-
answerer
|
|
1700
|
-
.add_ice_candidate(&candidate)
|
|
1701
|
-
.expect("answerer accepts bridge ICE");
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
Some("message") => bridge_messages.push((
|
|
1705
|
-
header
|
|
1706
|
-
.get("channel")
|
|
1707
|
-
.and_then(Value::as_str)
|
|
1708
|
-
.expect("message channel")
|
|
1709
|
-
.to_owned(),
|
|
1710
|
-
payload.to_vec(),
|
|
1711
|
-
)),
|
|
1712
|
-
Some("error") => panic!("bridge emitted error event: {header}"),
|
|
1713
|
-
_ => {}
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
fn forward_answerer_ice(signals: &AnswererSignals, bridge: &PeerConnection) {
|
|
1719
|
-
while let Some(candidate) = lock(&signals.ice).pop_front() {
|
|
1720
|
-
bridge
|
|
1721
|
-
.add_ice_candidate(&candidate)
|
|
1722
|
-
.expect("bridge accepts answerer ICE");
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
#[test]
|
|
1727
|
-
fn packet_framing_is_length_prefixed_and_lossless() {
|
|
1728
|
-
let payload = [0, 1, 2, 255];
|
|
1729
|
-
let packet = packet(json!({ "type": "message", "channel": "data" }), &payload);
|
|
1730
|
-
let header_len = u32::from_le_bytes(packet[0..4].try_into().unwrap()) as usize;
|
|
1731
|
-
let header: Value = serde_json::from_slice(&packet[4..4 + header_len]).unwrap();
|
|
1732
|
-
assert_eq!(header["type"], "message");
|
|
1733
|
-
assert_eq!(&packet[4 + header_len..], payload);
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
#[test]
|
|
1737
|
-
fn callback_gate_fences_new_callbacks_and_waits_for_inflight() {
|
|
1738
|
-
let gate = Arc::new(CallbackGate::new());
|
|
1739
|
-
let guard = gate.enter().expect("first callback admitted");
|
|
1740
|
-
gate.close();
|
|
1741
|
-
assert!(gate.enter().is_none(), "close must fence new callbacks");
|
|
1742
|
-
let waiter = {
|
|
1743
|
-
let gate = Arc::clone(&gate);
|
|
1744
|
-
thread::spawn(move || gate.wait_zero())
|
|
1745
|
-
};
|
|
1746
|
-
thread::sleep(Duration::from_millis(10));
|
|
1747
|
-
assert!(
|
|
1748
|
-
!waiter.is_finished(),
|
|
1749
|
-
"wait_zero returned while callback was active"
|
|
1750
|
-
);
|
|
1751
|
-
drop(guard);
|
|
1752
|
-
waiter.join().unwrap();
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
#[test]
|
|
1756
|
-
fn media_queue_drops_oldest_under_pressure() {
|
|
1757
|
-
let queue = MediaQueue::new(2, 32);
|
|
1758
|
-
queue.push_drop_oldest(vec![1; 8]);
|
|
1759
|
-
queue.push_drop_oldest(vec![2; 8]);
|
|
1760
|
-
queue.push_drop_oldest(vec![3; 8]);
|
|
1761
|
-
assert_eq!(queue.counters.observed.load(Ordering::Relaxed), 3);
|
|
1762
|
-
assert_eq!(queue.counters.dropped.load(Ordering::Relaxed), 1);
|
|
1763
|
-
match queue.queue.poll(Duration::ZERO, 32) {
|
|
1764
|
-
PollResult::Packet(packet) => assert_eq!(packet, vec![2; 8]),
|
|
1765
|
-
_ => panic!("expected retained packet"),
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
#[test]
|
|
1770
|
-
fn media_probe_owns_packet_across_differently_sized_eviction() {
|
|
1771
|
-
// Both directions matter: a larger successor formerly failed the copy,
|
|
1772
|
-
// while a smaller successor changed the size after JS allocated output.
|
|
1773
|
-
for (first_size, replacement_size) in [(8, 13), (13, 8)] {
|
|
1774
|
-
let queue = MediaQueue::new(2, 32);
|
|
1775
|
-
queue.push_drop_oldest(vec![1; first_size]);
|
|
1776
|
-
queue.push_drop_oldest(vec![2; 7]);
|
|
1777
|
-
assert!(matches!(
|
|
1778
|
-
queue.queue.poll(Duration::ZERO, 0),
|
|
1779
|
-
PollResult::Need(size) if size == first_size
|
|
1780
|
-
));
|
|
1781
|
-
|
|
1782
|
-
// The producer runs between the size probe and the copying call.
|
|
1783
|
-
queue.push_drop_oldest(vec![3; replacement_size]);
|
|
1784
|
-
assert_eq!(queue.counters.dropped.load(Ordering::Relaxed), 1);
|
|
1785
|
-
let snapshot = queue.queue.snapshot();
|
|
1786
|
-
assert_eq!(snapshot.queued, 2);
|
|
1787
|
-
assert_eq!(snapshot.bytes, first_size + replacement_size);
|
|
1788
|
-
match queue.queue.poll(Duration::ZERO, first_size) {
|
|
1789
|
-
PollResult::Packet(bytes) => assert_eq!(bytes, vec![1; first_size]),
|
|
1790
|
-
_ => panic!("producer evicted a packet already retained by a reader"),
|
|
1791
|
-
}
|
|
1792
|
-
match queue.queue.poll(Duration::ZERO, replacement_size) {
|
|
1793
|
-
PollResult::Packet(bytes) => assert_eq!(bytes, vec![3; replacement_size]),
|
|
1794
|
-
_ => panic!("replacement packet was lost or reordered"),
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
#[test]
|
|
1800
|
-
fn retained_media_stays_within_capacity_and_close_discards_it() {
|
|
1801
|
-
let queue = MediaQueue::new(1, 8);
|
|
1802
|
-
queue.push_drop_oldest(vec![1; 8]);
|
|
1803
|
-
assert!(matches!(
|
|
1804
|
-
queue.queue.poll(Duration::ZERO, 0),
|
|
1805
|
-
PollResult::Need(8)
|
|
1806
|
-
));
|
|
1807
|
-
queue.push_drop_oldest(vec![2; 7]);
|
|
1808
|
-
assert_eq!(queue.queue.snapshot().bytes, 8);
|
|
1809
|
-
assert_eq!(queue.queue.snapshot().queued, 1);
|
|
1810
|
-
assert_eq!(queue.counters.dropped.load(Ordering::Relaxed), 1);
|
|
1811
|
-
queue.close();
|
|
1812
|
-
assert_eq!(queue.queue.snapshot().bytes, 0);
|
|
1813
|
-
assert_eq!(queue.counters.dropped.load(Ordering::Relaxed), 2);
|
|
1814
|
-
assert!(matches!(
|
|
1815
|
-
queue.queue.poll(Duration::ZERO, 8),
|
|
1816
|
-
PollResult::Closed
|
|
1817
|
-
));
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
#[test]
|
|
1821
|
-
fn critical_overflow_preserves_a_probed_packet_before_the_diagnostic() {
|
|
1822
|
-
let queue = PacketQueue::new(2, 128);
|
|
1823
|
-
assert!(matches!(queue.push(vec![1; 7]), PushResult::Accepted));
|
|
1824
|
-
assert!(matches!(queue.poll(Duration::ZERO, 0), PollResult::Need(7)));
|
|
1825
|
-
queue.replace_with(vec![2; 13]);
|
|
1826
|
-
match queue.poll(Duration::ZERO, 7) {
|
|
1827
|
-
PollResult::Packet(bytes) => assert_eq!(bytes, vec![1; 7]),
|
|
1828
|
-
_ => panic!("critical overflow invalidated a reader's retained packet"),
|
|
1829
|
-
}
|
|
1830
|
-
match queue.poll(Duration::ZERO, 13) {
|
|
1831
|
-
PollResult::Packet(bytes) => assert_eq!(bytes, vec![2; 13]),
|
|
1832
|
-
_ => panic!("critical overflow lost its diagnostic"),
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
#[test]
|
|
1837
|
-
fn packet_queue_size_probe_retains_packet_and_fifo_order() {
|
|
1838
|
-
let queue = PacketQueue::new(4, 128);
|
|
1839
|
-
assert!(matches!(queue.push(vec![1, 2, 3]), PushResult::Accepted));
|
|
1840
|
-
assert!(matches!(queue.push(vec![4, 5]), PushResult::Accepted));
|
|
1841
|
-
|
|
1842
|
-
assert!(matches!(queue.poll(Duration::ZERO, 0), PollResult::Need(3)));
|
|
1843
|
-
match queue.poll(Duration::ZERO, 3) {
|
|
1844
|
-
PollResult::Packet(packet) => assert_eq!(packet, vec![1, 2, 3]),
|
|
1845
|
-
_ => panic!("size probe consumed or reordered the first packet"),
|
|
1846
|
-
}
|
|
1847
|
-
match queue.poll(Duration::ZERO, 2) {
|
|
1848
|
-
PollResult::Packet(packet) => assert_eq!(packet, vec![4, 5]),
|
|
1849
|
-
_ => panic!("second packet did not remain FIFO"),
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
#[test]
|
|
1854
|
-
fn critical_event_overflow_fences_connection_and_retains_one_error() {
|
|
1855
|
-
let gate = Arc::new(CallbackGate::new());
|
|
1856
|
-
let shared = Shared::new(Arc::clone(&gate));
|
|
1857
|
-
for index in 0..1024 {
|
|
1858
|
-
shared.emit(json!({ "type": "probe", "index": index }), &[]);
|
|
1859
|
-
}
|
|
1860
|
-
assert!(gate.accepting.load(Ordering::Acquire));
|
|
1861
|
-
|
|
1862
|
-
shared.emit(json!({ "type": "overflow-trigger" }), &[]);
|
|
1863
|
-
assert!(!gate.accepting.load(Ordering::Acquire));
|
|
1864
|
-
let snapshot = shared.events.snapshot();
|
|
1865
|
-
assert_eq!(
|
|
1866
|
-
snapshot.queued, 1,
|
|
1867
|
-
"overflow must collapse critical backlog"
|
|
1868
|
-
);
|
|
1869
|
-
match shared.events.poll(Duration::ZERO, 4096) {
|
|
1870
|
-
PollResult::Packet(packet) => {
|
|
1871
|
-
let (header, payload) = packet_parts(&packet);
|
|
1872
|
-
assert!(payload.is_empty());
|
|
1873
|
-
assert_eq!(header["type"], "error");
|
|
1874
|
-
assert_eq!(header["code"], "Overflow");
|
|
1875
|
-
}
|
|
1876
|
-
_ => panic!("overflow diagnostic was not retained"),
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
#[test]
|
|
1881
|
-
fn c_abi_size_probe_retains_packet_until_copy() {
|
|
1882
|
-
unsafe {
|
|
1883
|
-
let peer = reactor_effect_peer_create();
|
|
1884
|
-
assert!(!peer.is_null());
|
|
1885
|
-
let packet = packet(json!({ "type": "message", "channel": "control" }), b"abc");
|
|
1886
|
-
let peer_ref = &*peer;
|
|
1887
|
-
assert!(matches!(
|
|
1888
|
-
peer_ref.shared.events.push(packet.clone()),
|
|
1889
|
-
PushResult::Accepted
|
|
1890
|
-
));
|
|
1891
|
-
|
|
1892
|
-
let mut required = 0usize;
|
|
1893
|
-
let status = reactor_effect_peer_poll_event(peer, 0, ptr::null_mut(), 0, &mut required);
|
|
1894
|
-
assert_eq!(status, STATUS_BUFFER_TOO_SMALL);
|
|
1895
|
-
assert_eq!(required, packet.len());
|
|
1896
|
-
|
|
1897
|
-
let mut output = vec![0u8; required];
|
|
1898
|
-
let mut copied = 0usize;
|
|
1899
|
-
let status = reactor_effect_peer_poll_event(
|
|
1900
|
-
peer,
|
|
1901
|
-
0,
|
|
1902
|
-
output.as_mut_ptr(),
|
|
1903
|
-
output.len(),
|
|
1904
|
-
&mut copied,
|
|
1905
|
-
);
|
|
1906
|
-
assert_eq!(status, STATUS_OK);
|
|
1907
|
-
assert_eq!(copied, packet.len());
|
|
1908
|
-
assert_eq!(output, packet);
|
|
1909
|
-
reactor_effect_peer_destroy(peer);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
#[test]
|
|
1914
|
-
fn c_abi_video_probe_survives_producer_eviction_between_calls() {
|
|
1915
|
-
for (first_size, replacement_size) in [(8, 13), (13, 8)] {
|
|
1916
|
-
unsafe {
|
|
1917
|
-
let peer = reactor_effect_peer_create();
|
|
1918
|
-
assert!(!peer.is_null());
|
|
1919
|
-
let inner = &*peer;
|
|
1920
|
-
let media = &inner.shared.video;
|
|
1921
|
-
let first = packet(json!({ "type": "fixture" }), &vec![1; first_size]);
|
|
1922
|
-
let replacement = packet(json!({ "type": "fixture" }), &vec![3; replacement_size]);
|
|
1923
|
-
media.push_drop_oldest(first.clone());
|
|
1924
|
-
// Fill the production queue to capacity before retaining its
|
|
1925
|
-
// first packet through the exported C ABI.
|
|
1926
|
-
for _ in 1..8 {
|
|
1927
|
-
media.push_drop_oldest(packet(json!({ "type": "fixture" }), &[2; 7]));
|
|
1928
|
-
}
|
|
1929
|
-
let mut required = 0;
|
|
1930
|
-
assert_eq!(
|
|
1931
|
-
reactor_effect_peer_poll_video(peer, 0, ptr::null_mut(), 0, &mut required),
|
|
1932
|
-
STATUS_BUFFER_TOO_SMALL
|
|
1933
|
-
);
|
|
1934
|
-
assert_eq!(required, first.len());
|
|
1935
|
-
media.push_drop_oldest(replacement);
|
|
1936
|
-
assert_eq!(media.counters.dropped.load(Ordering::Relaxed), 1);
|
|
1937
|
-
let mut copied = 0;
|
|
1938
|
-
let mut output = vec![0; required];
|
|
1939
|
-
assert_eq!(
|
|
1940
|
-
reactor_effect_peer_poll_video(
|
|
1941
|
-
peer,
|
|
1942
|
-
0,
|
|
1943
|
-
output.as_mut_ptr(),
|
|
1944
|
-
output.len(),
|
|
1945
|
-
&mut copied
|
|
1946
|
-
),
|
|
1947
|
-
STATUS_OK
|
|
1948
|
-
);
|
|
1949
|
-
assert_eq!(copied, first.len());
|
|
1950
|
-
assert_eq!(output, first);
|
|
1951
|
-
reactor_effect_peer_destroy(peer);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
#[test]
|
|
1957
|
-
fn close_wakes_inflight_poll_and_post_close_operations_stay_closed() {
|
|
1958
|
-
unsafe {
|
|
1959
|
-
let peer = reactor_effect_peer_create();
|
|
1960
|
-
assert!(!peer.is_null());
|
|
1961
|
-
let address = peer as usize;
|
|
1962
|
-
let waiter = thread::spawn(move || {
|
|
1963
|
-
let peer = address as *mut ReactorEffectPeer;
|
|
1964
|
-
let mut length = 0usize;
|
|
1965
|
-
reactor_effect_peer_poll_event(peer, 10_000, ptr::null_mut(), 0, &mut length)
|
|
1966
|
-
});
|
|
1967
|
-
|
|
1968
|
-
thread::sleep(Duration::from_millis(20));
|
|
1969
|
-
reactor_effect_peer_close(peer);
|
|
1970
|
-
assert_eq!(waiter.join().expect("poll thread"), STATUS_CLOSED);
|
|
1971
|
-
|
|
1972
|
-
let mut response = vec![0u8; CALL_BUFFER_MIN];
|
|
1973
|
-
let mut response_len = 0usize;
|
|
1974
|
-
let status = reactor_effect_peer_call(
|
|
1975
|
-
peer,
|
|
1976
|
-
CALL_MEDIA_SNAPSHOT,
|
|
1977
|
-
ptr::null(),
|
|
1978
|
-
0,
|
|
1979
|
-
response.as_mut_ptr(),
|
|
1980
|
-
response.len(),
|
|
1981
|
-
&mut response_len,
|
|
1982
|
-
);
|
|
1983
|
-
assert_eq!(status, STATUS_CLOSED);
|
|
1984
|
-
let error: Value =
|
|
1985
|
-
serde_json::from_slice(&response[..response_len]).expect("closed JSON");
|
|
1986
|
-
assert_eq!(error["code"], "Closed");
|
|
1987
|
-
|
|
1988
|
-
let mut send_error = vec![0u8; ERROR_BUFFER_MIN];
|
|
1989
|
-
let mut send_error_len = 0usize;
|
|
1990
|
-
let status = reactor_effect_peer_send(
|
|
1991
|
-
peer,
|
|
1992
|
-
CHANNEL_DATA,
|
|
1993
|
-
b"late".as_ptr(),
|
|
1994
|
-
4,
|
|
1995
|
-
send_error.as_mut_ptr(),
|
|
1996
|
-
send_error.len(),
|
|
1997
|
-
&mut send_error_len,
|
|
1998
|
-
);
|
|
1999
|
-
assert_eq!(status, STATUS_CLOSED);
|
|
2000
|
-
let error: Value =
|
|
2001
|
-
serde_json::from_slice(&send_error[..send_error_len]).expect("closed send JSON");
|
|
2002
|
-
assert_eq!(error["code"], "Closed");
|
|
2003
|
-
|
|
2004
|
-
let mut poll_len = usize::MAX;
|
|
2005
|
-
assert_eq!(
|
|
2006
|
-
reactor_effect_peer_poll_event(peer, 0, ptr::null_mut(), 0, &mut poll_len),
|
|
2007
|
-
STATUS_CLOSED
|
|
2008
|
-
);
|
|
2009
|
-
assert_eq!(poll_len, 0);
|
|
2010
|
-
reactor_effect_peer_destroy(peer);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
#[test]
|
|
2015
|
-
fn transport_loopback_exchanges_ordered_binary_and_real_decoded_media() {
|
|
2016
|
-
let gate = Arc::new(CallbackGate::new());
|
|
2017
|
-
let shared = Arc::new(Shared::new(Arc::clone(&gate)));
|
|
2018
|
-
let mut bridge = WorkerState::new(Arc::clone(&shared));
|
|
2019
|
-
|
|
2020
|
-
let prepare = serde_json::to_vec(&json!({
|
|
2021
|
-
"servers": [],
|
|
2022
|
-
"tracks": [
|
|
2023
|
-
{ "name": "video-a", "kind": "video", "direction": "recvonly" },
|
|
2024
|
-
{ "name": "video-b", "kind": "video", "direction": "recvonly" },
|
|
2025
|
-
{ "name": "audio-a", "kind": "audio", "direction": "recvonly" },
|
|
2026
|
-
{ "name": "outgoing-video", "kind": "video", "direction": "sendonly" }
|
|
2027
|
-
]
|
|
2028
|
-
}))
|
|
2029
|
-
.unwrap();
|
|
2030
|
-
let response = bridge.prepare(&prepare).expect("bridge prepare");
|
|
2031
|
-
let prepared: Value = serde_json::from_slice(&response).expect("prepare JSON");
|
|
2032
|
-
let offer = SessionDescription {
|
|
2033
|
-
kind: SdpType::Offer,
|
|
2034
|
-
sdp: prepared["sdp"].as_str().expect("offer SDP").to_owned(),
|
|
2035
|
-
};
|
|
2036
|
-
let mappings: Vec<MappingSpec> =
|
|
2037
|
-
serde_json::from_value(prepared["mapping"].clone()).expect("prepare mappings");
|
|
2038
|
-
assert_eq!(mappings.len(), 4);
|
|
2039
|
-
assert!(
|
|
2040
|
-
offer.declares_frame_metadata(),
|
|
2041
|
-
"bridge offer must negotiate metadata"
|
|
2042
|
-
);
|
|
2043
|
-
|
|
2044
|
-
// Direction and sender bitrate are local transceiver capabilities and do
|
|
2045
|
-
// not depend on a Reactor account or a browser MediaStreamTrack.
|
|
2046
|
-
bridge
|
|
2047
|
-
.set_direction(br#"{"name":"outgoing-video","active":false}"#)
|
|
2048
|
-
.expect("pause outgoing transceiver");
|
|
2049
|
-
bridge
|
|
2050
|
-
.set_direction(br#"{"name":"outgoing-video","active":true}"#)
|
|
2051
|
-
.expect("resume outgoing transceiver");
|
|
2052
|
-
bridge
|
|
2053
|
-
.max_bitrate(br#"{"name":"outgoing-video","bitsPerSecond":2000000}"#)
|
|
2054
|
-
.expect("set outgoing bitrate");
|
|
2055
|
-
|
|
2056
|
-
let factory = PeerConnectionFactory::builder()
|
|
2057
|
-
.with_synthetic_adm()
|
|
2058
|
-
.build()
|
|
2059
|
-
.expect("answerer factory");
|
|
2060
|
-
let signals = Arc::new(AnswererSignals::default());
|
|
2061
|
-
let answerer = factory
|
|
2062
|
-
.create_peer_connection(
|
|
2063
|
-
&RtcConfiguration::default(),
|
|
2064
|
-
answerer_observer(Arc::clone(&signals)),
|
|
2065
|
-
)
|
|
2066
|
-
.expect("answerer PC");
|
|
2067
|
-
answerer
|
|
2068
|
-
.set_remote_description(&offer)
|
|
2069
|
-
.expect("answerer remote offer");
|
|
2070
|
-
|
|
2071
|
-
let video_a = factory
|
|
2072
|
-
.create_video_track("fixture-video-a")
|
|
2073
|
-
.expect("video a");
|
|
2074
|
-
let video_b = factory
|
|
2075
|
-
.create_video_track("fixture-video-b")
|
|
2076
|
-
.expect("video b");
|
|
2077
|
-
let audio = factory
|
|
2078
|
-
.create_audio_track_with_options("fixture-audio", {
|
|
2079
|
-
let mut options = AudioTrackOptions::default();
|
|
2080
|
-
options.source = AudioTrackSource::LocalPush;
|
|
2081
|
-
options
|
|
2082
|
-
})
|
|
2083
|
-
.expect("fixture audio");
|
|
2084
|
-
|
|
2085
|
-
for transceiver in answerer.transceivers() {
|
|
2086
|
-
let mid = transceiver.mid().expect("answerer transceiver MID");
|
|
2087
|
-
let Some(mapping) = mappings.iter().find(|mapping| mapping.mid == mid) else {
|
|
2088
|
-
continue;
|
|
2089
|
-
};
|
|
2090
|
-
if mapping.direction != "recvonly" {
|
|
2091
|
-
continue;
|
|
2092
|
-
}
|
|
2093
|
-
match mapping.name.as_str() {
|
|
2094
|
-
"video-a" => transceiver.set_track(&video_a).expect("publish video a"),
|
|
2095
|
-
"video-b" => transceiver.set_track(&video_b).expect("publish video b"),
|
|
2096
|
-
"audio-a" => transceiver.set_track(&audio).expect("publish audio"),
|
|
2097
|
-
other => panic!("unexpected receive mapping {other}"),
|
|
2098
|
-
}
|
|
2099
|
-
transceiver
|
|
2100
|
-
.set_direction(TransceiverDirection::SendOnly)
|
|
2101
|
-
.expect("answerer send direction");
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
let answer = answerer.create_answer().expect("answer");
|
|
2105
|
-
assert!(
|
|
2106
|
-
answer.declares_frame_metadata(),
|
|
2107
|
-
"answer must echo metadata support"
|
|
2108
|
-
);
|
|
2109
|
-
answerer
|
|
2110
|
-
.set_local_description(&answer)
|
|
2111
|
-
.expect("answerer local answer");
|
|
2112
|
-
bridge
|
|
2113
|
-
.answer(answer.sdp.as_bytes())
|
|
2114
|
-
.expect("bridge remote answer");
|
|
2115
|
-
|
|
2116
|
-
let deadline = Instant::now() + Duration::from_secs(20);
|
|
2117
|
-
let mut bridge_connected = false;
|
|
2118
|
-
let mut bridge_messages = Vec::new();
|
|
2119
|
-
loop {
|
|
2120
|
-
drain_bridge_events(
|
|
2121
|
-
&shared,
|
|
2122
|
-
&answerer,
|
|
2123
|
-
&mut bridge_connected,
|
|
2124
|
-
&mut bridge_messages,
|
|
2125
|
-
);
|
|
2126
|
-
forward_answerer_ice(&signals, bridge.require_peer().unwrap());
|
|
2127
|
-
let channels_open = bridge.channels.as_ref().is_some_and(|channels| {
|
|
2128
|
-
channels.control.state() == DataChannelState::Open
|
|
2129
|
-
&& channels.data.state() == DataChannelState::Open
|
|
2130
|
-
});
|
|
2131
|
-
let remote_channels = {
|
|
2132
|
-
let channels = lock(&signals.channels);
|
|
2133
|
-
channels
|
|
2134
|
-
.get("control")
|
|
2135
|
-
.is_some_and(|channel| channel.state() == DataChannelState::Open)
|
|
2136
|
-
&& channels
|
|
2137
|
-
.get("data")
|
|
2138
|
-
.is_some_and(|channel| channel.state() == DataChannelState::Open)
|
|
2139
|
-
};
|
|
2140
|
-
if bridge_connected
|
|
2141
|
-
&& signals.connected.load(Ordering::Acquire)
|
|
2142
|
-
&& channels_open
|
|
2143
|
-
&& remote_channels
|
|
2144
|
-
{
|
|
2145
|
-
break;
|
|
2146
|
-
}
|
|
2147
|
-
assert!(
|
|
2148
|
-
Instant::now() < deadline,
|
|
2149
|
-
"loopback did not connect/open both channels"
|
|
2150
|
-
);
|
|
2151
|
-
thread::sleep(Duration::from_millis(20));
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
for bytes in [b"one".as_slice(), b"two".as_slice(), b"three".as_slice()] {
|
|
2155
|
-
bridge.send(CHANNEL_DATA, bytes).expect("bridge data send");
|
|
2156
|
-
}
|
|
2157
|
-
let data_deadline = Instant::now() + Duration::from_secs(5);
|
|
2158
|
-
while lock(&signals.inbox)
|
|
2159
|
-
.iter()
|
|
2160
|
-
.filter(|(channel, _)| channel == "data")
|
|
2161
|
-
.count()
|
|
2162
|
-
< 3
|
|
2163
|
-
{
|
|
2164
|
-
assert!(
|
|
2165
|
-
Instant::now() < data_deadline,
|
|
2166
|
-
"answerer did not receive ordered data"
|
|
2167
|
-
);
|
|
2168
|
-
thread::sleep(Duration::from_millis(10));
|
|
2169
|
-
}
|
|
2170
|
-
let received: Vec<Vec<u8>> = lock(&signals.inbox)
|
|
2171
|
-
.iter()
|
|
2172
|
-
.filter(|(channel, _)| channel == "data")
|
|
2173
|
-
.map(|(_, bytes)| bytes.clone())
|
|
2174
|
-
.collect();
|
|
2175
|
-
assert_eq!(
|
|
2176
|
-
received,
|
|
2177
|
-
[b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
|
|
2178
|
-
);
|
|
2179
|
-
|
|
2180
|
-
{
|
|
2181
|
-
let channels = lock(&signals.channels);
|
|
2182
|
-
let control = channels.get("control").expect("remote control channel");
|
|
2183
|
-
control.send(b"alpha", true).expect("answerer alpha");
|
|
2184
|
-
control.send(b"beta", true).expect("answerer beta");
|
|
2185
|
-
}
|
|
2186
|
-
let reverse_deadline = Instant::now() + Duration::from_secs(5);
|
|
2187
|
-
while bridge_messages
|
|
2188
|
-
.iter()
|
|
2189
|
-
.filter(|(channel, _)| channel == "control")
|
|
2190
|
-
.count()
|
|
2191
|
-
< 2
|
|
2192
|
-
{
|
|
2193
|
-
drain_bridge_events(
|
|
2194
|
-
&shared,
|
|
2195
|
-
&answerer,
|
|
2196
|
-
&mut bridge_connected,
|
|
2197
|
-
&mut bridge_messages,
|
|
2198
|
-
);
|
|
2199
|
-
assert!(
|
|
2200
|
-
Instant::now() < reverse_deadline,
|
|
2201
|
-
"bridge did not receive control messages"
|
|
2202
|
-
);
|
|
2203
|
-
thread::sleep(Duration::from_millis(10));
|
|
2204
|
-
}
|
|
2205
|
-
let reverse: Vec<Vec<u8>> = bridge_messages
|
|
2206
|
-
.iter()
|
|
2207
|
-
.filter(|(channel, _)| channel == "control")
|
|
2208
|
-
.map(|(_, bytes)| bytes.clone())
|
|
2209
|
-
.collect();
|
|
2210
|
-
assert_eq!(reverse, [b"alpha".to_vec(), b"beta".to_vec()]);
|
|
2211
|
-
|
|
2212
|
-
let (width, height) = (64u32, 48u32);
|
|
2213
|
-
let bgra_a = vec![0x21; (width * height * 4) as usize];
|
|
2214
|
-
let bgra_b = vec![0x83; (width * height * 4) as usize];
|
|
2215
|
-
let pcm: Vec<i16> = (0..480)
|
|
2216
|
-
.map(|sample| (((sample % 128) as i16) - 64) * 128)
|
|
2217
|
-
.collect();
|
|
2218
|
-
let media_deadline = Instant::now() + Duration::from_secs(20);
|
|
2219
|
-
let mut got_video: HashMap<String, (Vec<u8>, Vec<u8>)> = HashMap::new();
|
|
2220
|
-
let mut got_audio = false;
|
|
2221
|
-
while got_video.len() < 2 || !got_audio {
|
|
2222
|
-
video_a
|
|
2223
|
-
.push_frame_with_metadata(VideoFrame::new(&bgra_a, width, height), b"meta-a")
|
|
2224
|
-
.expect("push video a");
|
|
2225
|
-
video_b
|
|
2226
|
-
.push_frame_with_metadata(VideoFrame::new(&bgra_b, width, height), b"meta-b")
|
|
2227
|
-
.expect("push video b");
|
|
2228
|
-
audio
|
|
2229
|
-
.push_frame(AudioFrame {
|
|
2230
|
-
pcm: &pcm,
|
|
2231
|
-
sample_rate: 48_000,
|
|
2232
|
-
channels: 1,
|
|
2233
|
-
frames: pcm.len() as u32,
|
|
2234
|
-
})
|
|
2235
|
-
.expect("push audio");
|
|
2236
|
-
|
|
2237
|
-
while let PollResult::Packet(packet) =
|
|
2238
|
-
shared.video.queue.poll(Duration::ZERO, 1024 * 1024)
|
|
2239
|
-
{
|
|
2240
|
-
let (header, payload) = packet_parts(&packet);
|
|
2241
|
-
let name = header["track"].as_str().expect("video track").to_owned();
|
|
2242
|
-
let data_len = header["dataLength"].as_u64().expect("data length") as usize;
|
|
2243
|
-
assert_eq!(header["format"], "BGRA");
|
|
2244
|
-
assert_eq!(header["width"], width);
|
|
2245
|
-
assert_eq!(header["height"], height);
|
|
2246
|
-
assert_eq!(data_len, bgra_a.len());
|
|
2247
|
-
got_video.insert(
|
|
2248
|
-
name,
|
|
2249
|
-
(payload[..data_len].to_vec(), payload[data_len..].to_vec()),
|
|
2250
|
-
);
|
|
2251
|
-
}
|
|
2252
|
-
while let PollResult::Packet(packet) =
|
|
2253
|
-
shared.audio.queue.poll(Duration::ZERO, 1024 * 1024)
|
|
2254
|
-
{
|
|
2255
|
-
let (header, payload) = packet_parts(&packet);
|
|
2256
|
-
assert_eq!(header["track"], "audio-a");
|
|
2257
|
-
assert_eq!(header["format"], "s16le");
|
|
2258
|
-
assert_eq!(header["sampleRate"], 48_000);
|
|
2259
|
-
assert_eq!(header["channels"], 1);
|
|
2260
|
-
assert!(!payload.is_empty());
|
|
2261
|
-
got_audio = true;
|
|
2262
|
-
}
|
|
2263
|
-
assert!(
|
|
2264
|
-
Instant::now() < media_deadline,
|
|
2265
|
-
"real codecs did not deliver all decoded media"
|
|
2266
|
-
);
|
|
2267
|
-
thread::sleep(Duration::from_millis(30));
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
let (decoded_a, metadata_a) = got_video.get("video-a").expect("decoded video-a");
|
|
2271
|
-
let (decoded_b, metadata_b) = got_video.get("video-b").expect("decoded video-b");
|
|
2272
|
-
// VP8/H264 are lossy, so exact pixels are not asserted. Distinct luma
|
|
2273
|
-
// inputs should remain observably distinct after the real codec path.
|
|
2274
|
-
assert_ne!(
|
|
2275
|
-
decoded_a[0], decoded_b[0],
|
|
2276
|
-
"two decoded video lanes collapsed into one"
|
|
2277
|
-
);
|
|
2278
|
-
assert_eq!(
|
|
2279
|
-
metadata_a, b"meta-a",
|
|
2280
|
-
"same-kind lane A metadata was misattributed"
|
|
2281
|
-
);
|
|
2282
|
-
assert_eq!(
|
|
2283
|
-
metadata_b, b"meta-b",
|
|
2284
|
-
"same-kind lane B metadata was misattributed"
|
|
2285
|
-
);
|
|
2286
|
-
assert_ne!(
|
|
2287
|
-
bigint_from_header(&shared.video),
|
|
2288
|
-
0,
|
|
2289
|
-
"metadata path never observed frames"
|
|
2290
|
-
);
|
|
2291
|
-
|
|
2292
|
-
let stats_deadline = Instant::now() + Duration::from_secs(5);
|
|
2293
|
-
loop {
|
|
2294
|
-
let stats: Value =
|
|
2295
|
-
serde_json::from_slice(&bridge.stats().expect("bridge stats")).expect("stats JSON");
|
|
2296
|
-
let entries = stats.as_array().expect("stats array");
|
|
2297
|
-
let video = entries.iter().any(|entry| {
|
|
2298
|
-
entry["type"] == "inbound-rtp"
|
|
2299
|
-
&& entry["kind"] == "video"
|
|
2300
|
-
&& entry["framesDecoded"].as_u64().unwrap_or(0) > 0
|
|
2301
|
-
});
|
|
2302
|
-
let audio = entries
|
|
2303
|
-
.iter()
|
|
2304
|
-
.any(|entry| entry["type"] == "inbound-rtp" && entry["kind"] == "audio");
|
|
2305
|
-
let pair = entries
|
|
2306
|
-
.iter()
|
|
2307
|
-
.any(|entry| entry["type"] == "candidate-pair");
|
|
2308
|
-
if video && audio && pair {
|
|
2309
|
-
break;
|
|
2310
|
-
}
|
|
2311
|
-
assert!(
|
|
2312
|
-
Instant::now() < stats_deadline,
|
|
2313
|
-
"bridge stats omitted live media/candidate data"
|
|
2314
|
-
);
|
|
2315
|
-
thread::sleep(Duration::from_millis(100));
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
|
-
// Fence admission before the remote tries one more frame. The queue is
|
|
2319
|
-
// then closed by shutdown, establishing that no late callback can become
|
|
2320
|
-
// observable after the finalizer returns.
|
|
2321
|
-
gate.close();
|
|
2322
|
-
video_a
|
|
2323
|
-
.push_frame_with_metadata(VideoFrame::new(&bgra_a, width, height), b"late")
|
|
2324
|
-
.expect("remote source can still push after local fence");
|
|
2325
|
-
bridge.shutdown();
|
|
2326
|
-
assert!(matches!(
|
|
2327
|
-
shared.video.queue.poll(Duration::ZERO, 1024),
|
|
2328
|
-
PollResult::Closed
|
|
2329
|
-
));
|
|
2330
|
-
assert!(matches!(
|
|
2331
|
-
shared.audio.queue.poll(Duration::ZERO, 1024),
|
|
2332
|
-
PollResult::Closed
|
|
2333
|
-
));
|
|
2334
|
-
assert!(matches!(
|
|
2335
|
-
shared.events.poll(Duration::ZERO, 1024),
|
|
2336
|
-
PollResult::Closed
|
|
2337
|
-
));
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
fn bigint_from_header(media: &MediaQueue) -> u64 {
|
|
2341
|
-
media.counters.observed.load(Ordering::Relaxed)
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
38
|
+
mod test_support;
|