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.
Files changed (63) hide show
  1. package/Dockerfile +4 -2
  2. package/README.md +90 -15
  3. package/dist/_internal/bridge.d.ts +70 -14
  4. package/dist/_internal/bridge.d.ts.map +1 -1
  5. package/dist/_internal/bridge.js +290 -145
  6. package/dist/_internal/bridge.js.map +1 -1
  7. package/dist/_internal/isolated/child.d.ts +2 -0
  8. package/dist/_internal/isolated/child.d.ts.map +1 -0
  9. package/dist/_internal/isolated/child.js +176 -0
  10. package/dist/_internal/isolated/child.js.map +1 -0
  11. package/dist/_internal/isolated/host.d.ts +147 -0
  12. package/dist/_internal/isolated/host.d.ts.map +1 -0
  13. package/dist/_internal/isolated/host.js +645 -0
  14. package/dist/_internal/isolated/host.js.map +1 -0
  15. package/dist/_internal/isolated/protocol.d.ts +399 -0
  16. package/dist/_internal/isolated/protocol.d.ts.map +1 -0
  17. package/dist/_internal/isolated/protocol.js +250 -0
  18. package/dist/_internal/isolated/protocol.js.map +1 -0
  19. package/dist/_internal/peer.d.ts +68 -13
  20. package/dist/_internal/peer.d.ts.map +1 -1
  21. package/dist/_internal/peer.js +233 -157
  22. package/dist/_internal/peer.js.map +1 -1
  23. package/dist/index.d.ts +19 -10
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +37 -33
  26. package/dist/index.js.map +1 -1
  27. package/dist/isolated.d.ts +25 -0
  28. package/dist/isolated.d.ts.map +1 -0
  29. package/dist/isolated.js +39 -0
  30. package/dist/isolated.js.map +1 -0
  31. package/lib/darwin-arm64/libreactor_effect_native.dylib +0 -0
  32. package/lib/darwin-arm64/native-identity.json +4 -3
  33. package/lib/linux-x64/libreactor_effect_native.so +0 -0
  34. package/lib/linux-x64/native-identity.json +4 -3
  35. package/package.json +7 -3
  36. package/rust/Cargo.toml +108 -2
  37. package/rust/build.rs +253 -114
  38. package/rust/clippy.toml +7 -0
  39. package/rust/include/reactor_effect_native.h +101 -42
  40. package/rust/src/abi.rs +258 -0
  41. package/rust/src/error.rs +149 -0
  42. package/rust/src/ffi/memory.rs +256 -0
  43. package/rust/src/ffi/tests.rs +719 -0
  44. package/rust/src/ffi.rs +474 -0
  45. package/rust/src/lib.rs +36 -2341
  46. package/rust/src/peer/callbacks.rs +145 -0
  47. package/rust/src/peer/media.rs +221 -0
  48. package/rust/src/peer/owner/tests.rs +188 -0
  49. package/rust/src/peer/owner.rs +353 -0
  50. package/rust/src/peer/shared.rs +323 -0
  51. package/rust/src/peer/tests.rs +513 -0
  52. package/rust/src/peer.rs +189 -0
  53. package/rust/src/protocol/event.rs +238 -0
  54. package/rust/src/protocol/request.rs +347 -0
  55. package/rust/src/protocol/stats.rs +356 -0
  56. package/rust/src/protocol.rs +71 -0
  57. package/rust/src/sync/gate.rs +159 -0
  58. package/rust/src/sync/notifier.rs +136 -0
  59. package/rust/src/sync/queue.rs +384 -0
  60. package/rust/src/sync.rs +20 -0
  61. package/rust/src/test_support.rs +99 -0
  62. package/rust-toolchain.toml +7 -0
  63. package/scripts/stage.mjs +41 -1
package/rust/src/lib.rs CHANGED
@@ -1,2343 +1,38 @@
1
- use reactor_webrtc::{
2
- DataChannel, DataChannelState, IceCandidateType, IceGatheringState, IceServer, MediaKind,
3
- PeerConnection, PeerConnectionFactory, PeerConnectionObserver, PeerConnectionState,
4
- RelayProtocol, RemoteTrack, RtcConfiguration, SdpType, SessionDescription, StatsReport,
5
- Transceiver, TransceiverDirection,
6
- };
7
- use serde::{Deserialize, Serialize};
8
- use serde_json::{json, Value};
9
- use std::collections::{HashMap, VecDeque};
10
- use std::panic::{catch_unwind, AssertUnwindSafe};
11
- use std::ptr;
12
- use std::slice;
13
- use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
14
- use std::sync::mpsc::{self, Receiver, Sender, SyncSender};
15
- use std::sync::{Arc, Condvar, Mutex, MutexGuard};
16
- use std::thread::{self, JoinHandle};
17
- use std::time::{Duration, Instant};
18
-
19
- const ABI_VERSION: u32 = 2;
20
- const STATUS_OK: i32 = 0;
21
- const STATUS_AGAIN: i32 = 1;
22
- const STATUS_BUFFER_TOO_SMALL: i32 = 2;
23
- const STATUS_CLOSED: i32 = 3;
24
- const STATUS_INVALID: i32 = -1;
25
- const STATUS_NATIVE: i32 = -2;
26
- const STATUS_OVERFLOW: i32 = -3;
27
-
28
- const CALL_PREPARE: u32 = 1;
29
- const CALL_ANSWER: u32 = 2;
30
- const CALL_DIRECTION: u32 = 3;
31
- const CALL_MAX_BITRATE: u32 = 4;
32
- const CALL_STATS: u32 = 5;
33
- const CALL_MEDIA_SNAPSHOT: u32 = 6;
34
-
35
- const CHANNEL_CONTROL: u32 = 0;
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 tests {
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;