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/build.rs CHANGED
@@ -1,85 +1,207 @@
1
+ //! Embeds the library's source and build identity and, on macOS, links the
2
+ //! compiler-rt archive that the pinned libwebrtc needs.
3
+ //!
4
+ //! The identity is a JSON object that `scripts/stage.mjs` finds in the built
5
+ //! library and checks against the checked-out sources before staging it. Its
6
+ //! `sourceSha256` covers the same files, hashed the same way, as
7
+ //! `stage.mjs --source-hash` and the pack check in `scripts/pack.ts`. Its
8
+ //! `webrtcPrebuilt` names the Reactor libwebrtc prebuilt that
9
+ //! `reactor-webrtc-sys` links, which staging checks against the shipped SBOM.
10
+
1
11
  use std::env;
12
+ use std::error::Error;
13
+ use std::fmt::{self, Write as _};
2
14
  use std::fs;
3
- use std::io::Write;
15
+ use std::io::{self, Write as _};
4
16
  use std::path::{Path, PathBuf};
5
17
  use std::process::{Command, Stdio};
6
18
 
7
- fn source_files(root: &Path, directory: &Path, files: &mut Vec<String>) {
8
- for entry in fs::read_dir(directory).expect("native source directory must exist") {
9
- let path = entry.expect("read native source entry").path();
10
- if path.is_dir() {
11
- source_files(root, &path, files);
12
- } else if path.is_file() {
13
- files.push(
14
- path.strip_prefix(root)
15
- .unwrap()
16
- .to_str()
17
- .unwrap()
18
- .replace('\\', "/"),
19
- );
20
- }
19
+ /// A failed build step, which Cargo reports with the script's output.
20
+ type BuildResult<T> = Result<T, Box<dyn Error>>;
21
+
22
+ /// The C ABI version. A library test checks it against `abi::ABI_VERSION`.
23
+ const ABI_VERSION: u32 = 4;
24
+
25
+ /// The source identity's inputs outside `src/`, which it covers entirely.
26
+ const SOURCE_FILES: [&str; 5] = [
27
+ "Cargo.toml",
28
+ "Cargo.lock",
29
+ "build.rs",
30
+ ".cargo/config.toml",
31
+ "include/reactor_effect_native.h",
32
+ ];
33
+
34
+ /// Overrides that make `reactor-webrtc-sys` link something other than its
35
+ /// tagged prebuilt; with either set, the identity names no prebuilt.
36
+ const WEBRTC_OVERRIDES: [&str; 2] = ["REACTOR_WEBRTC_LIB_DIR", "REACTOR_WEBRTC_PREBUILT_URL"];
37
+
38
+ /// Environment that changes the compiled library, recorded when set.
39
+ const BUILD_ENVIRONMENT: [&str; 6] = [
40
+ "CC",
41
+ "CXX",
42
+ "CARGO_ENCODED_RUSTFLAGS",
43
+ "CFLAGS",
44
+ "CXXFLAGS",
45
+ "MACOSX_DEPLOYMENT_TARGET",
46
+ ];
47
+
48
+ fn main() -> BuildResult<()> {
49
+ let root = env::var_os("CARGO_MANIFEST_DIR")
50
+ .map(PathBuf::from)
51
+ .ok_or("Cargo sets CARGO_MANIFEST_DIR")?;
52
+ let identity = build_identity(&root)?;
53
+ println!("cargo::rustc-env=REACTOR_EFFECT_BUILD_IDENTITY={identity}");
54
+ if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
55
+ link_compiler_rt()?;
21
56
  }
57
+ Ok(())
22
58
  }
23
59
 
24
- fn quote(value: &str) -> String {
25
- let mut out = String::from("\"");
26
- for ch in value.chars() {
27
- match ch {
28
- '"' => out.push_str("\\\""),
29
- '\\' => out.push_str("\\\\"),
30
- '\n' => out.push_str("\\n"),
31
- '\r' => out.push_str("\\r"),
32
- '\t' => out.push_str("\\t"),
33
- ch if ch.is_control() => out.push_str(&format!("\\u{:04x}", ch as u32)),
34
- ch => out.push(ch),
60
+ /// The identity JSON: the source hash, target and profile, the toolchain's
61
+ /// versions and the build environment.
62
+ fn build_identity(root: &Path) -> BuildResult<String> {
63
+ let cc = env::var("CC").unwrap_or_else(|_| "clang".to_owned());
64
+ let cxx = env::var("CXX").unwrap_or_else(|_| "clang++".to_owned());
65
+ let rustc = cargo_env("RUSTC")?;
66
+ // Each value is already JSON.
67
+ let mut fields = vec![
68
+ ("schemaVersion", "1".to_owned()),
69
+ ("abiVersion", ABI_VERSION.to_string()),
70
+ ("sourceSha256", json_string(&source_sha256(root)?)),
71
+ ("target", json_string(&cargo_env("TARGET")?)),
72
+ ("profile", json_string(&cargo_env("PROFILE")?)),
73
+ ("rustc", json_string(&tool_version(&rustc)?)),
74
+ ("cc", json_string(&tool_version(&cc)?)),
75
+ ("cxx", json_string(&tool_version(&cxx)?)),
76
+ (
77
+ "webrtcPrebuilt",
78
+ webrtc_prebuilt(root)?.map_or_else(|| "null".to_owned(), |tag| json_string(&tag)),
79
+ ),
80
+ ];
81
+ for key in BUILD_ENVIRONMENT {
82
+ println!("cargo::rerun-if-env-changed={key}");
83
+ if let Ok(value) = env::var(key) {
84
+ fields.push((key, json_string(&value)));
35
85
  }
36
86
  }
37
- out.push('"');
38
- out
87
+ let fields: Vec<String> = fields
88
+ .iter()
89
+ .map(|(key, value)| format!("{}:{value}", json_string(key)))
90
+ .collect();
91
+ Ok(format!("{{{}}}", fields.join(",")))
39
92
  }
40
93
 
41
- fn version(program: &str) -> String {
42
- let output = Command::new(program)
43
- .arg("--version")
94
+ /// The prebuilt tag `reactor-webrtc-sys` downloads, derived as it derives it:
95
+ /// from `WEBRTC_VERSION` at the root of the pinned `reactor-webrtc` checkout.
96
+ /// `None` when an override links a local or custom archive instead.
97
+ fn webrtc_prebuilt(root: &Path) -> BuildResult<Option<String>> {
98
+ for key in WEBRTC_OVERRIDES {
99
+ println!("cargo::rerun-if-env-changed={key}");
100
+ if env::var_os(key).is_some() {
101
+ return Ok(None);
102
+ }
103
+ }
104
+ let output = Command::new(cargo_env("CARGO")?)
105
+ .args(["metadata", "--format-version", "1", "--offline", "--locked"])
106
+ .arg("--manifest-path")
107
+ .arg(root.join("Cargo.toml"))
44
108
  .output()
45
- .unwrap_or_else(|_| panic!("could not inspect native build tool {program}"));
46
- assert!(
47
- output.status.success(),
48
- "native build tool {program} failed"
49
- );
50
- String::from_utf8(output.stdout)
51
- .expect("build tool version must be UTF-8")
52
- .lines()
53
- .next()
54
- .unwrap_or("")
55
- .to_owned()
109
+ .map_err(|error| format!("cargo metadata could not run: {error}"))?;
110
+ if !output.status.success() {
111
+ return Err(format!("cargo metadata failed: {}", output.status).into());
112
+ }
113
+ let metadata = String::from_utf8(output.stdout)?;
114
+ let manifest = metadata
115
+ .split("\"manifest_path\":\"")
116
+ .skip(1)
117
+ .filter_map(|rest| rest.split('"').next())
118
+ .find(|path| path.ends_with("/reactor-webrtc-sys/Cargo.toml"))
119
+ .ok_or("cargo metadata names no reactor-webrtc-sys manifest")?;
120
+ // `<checkout>/crates/reactor-webrtc-sys/Cargo.toml`, as its build script reads it.
121
+ let version = Path::new(manifest)
122
+ .ancestors()
123
+ .nth(3)
124
+ .ok_or("reactor-webrtc-sys is not inside a reactor-webrtc checkout")?
125
+ .join("WEBRTC_VERSION");
126
+ println!("cargo::rerun-if-changed={}", version.display());
127
+ let source = fs::read_to_string(&version)
128
+ .map_err(|error| format!("read {}: {error}", version.display()))?;
129
+ prebuilt_tag(&source).map(Some)
56
130
  }
57
131
 
58
- fn build_identity() {
59
- let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
60
- let mut files = [
61
- "Cargo.toml",
62
- "Cargo.lock",
63
- "build.rs",
64
- ".cargo/config.toml",
65
- "include/reactor_effect_native.h",
66
- ]
67
- .map(str::to_owned)
68
- .to_vec();
69
- source_files(&root, &root.join("src"), &mut files);
132
+ /// `webrtc-<milestone>-<commit8>-p<patch>` from `WEBRTC_VERSION`'s variables.
133
+ fn prebuilt_tag(source: &str) -> BuildResult<String> {
134
+ let (mut branch, mut commit, mut patch) = (None, None, "0");
135
+ for line in source.lines().map(str::trim) {
136
+ if line.starts_with('#') {
137
+ continue;
138
+ }
139
+ if let Some(value) = line.strip_prefix("WEBRTC_BRANCH=") {
140
+ branch = Some(value);
141
+ } else if let Some(value) = line.strip_prefix("WEBRTC_COMMIT=") {
142
+ commit = Some(value);
143
+ } else if let Some(value) = line.strip_prefix("REACTOR_PATCH_LEVEL=") {
144
+ patch = value;
145
+ }
146
+ }
147
+ let branch = branch.ok_or("WEBRTC_VERSION names no WEBRTC_BRANCH")?;
148
+ let commit = commit
149
+ .and_then(|commit| commit.get(..8))
150
+ .ok_or("WEBRTC_VERSION pins no WEBRTC_COMMIT")?;
151
+ let milestone = branch.strip_prefix("branch-heads/").unwrap_or(branch);
152
+ Ok(format!("webrtc-{milestone}-{commit}-p{patch}"))
153
+ }
154
+
155
+ /// A variable Cargo sets for every build script.
156
+ fn cargo_env(key: &str) -> BuildResult<String> {
157
+ env::var(key).map_err(|error| format!("Cargo sets {key}: {error}").into())
158
+ }
159
+
160
+ /// SHA-256 over each source input, as `path NUL contents NUL` in path order.
161
+ fn source_sha256(root: &Path) -> BuildResult<String> {
162
+ let mut files = SOURCE_FILES.map(str::to_owned).to_vec();
163
+ files.extend(files_under(root, &root.join("src"))?);
70
164
  files.sort();
165
+ // The directory as well, so a new source file reruns this script.
166
+ println!("cargo::rerun-if-changed=src");
71
167
  let mut input = Vec::new();
72
168
  for file in files {
73
- println!("cargo:rerun-if-changed={file}");
169
+ println!("cargo::rerun-if-changed={file}");
170
+ let contents = fs::read(root.join(&file))
171
+ .map_err(|error| format!("read native build input {file}: {error}"))?;
74
172
  input.extend_from_slice(file.as_bytes());
75
173
  input.push(0);
76
- input.extend_from_slice(&fs::read(root.join(file)).expect("read native build input"));
174
+ input.extend_from_slice(&contents);
77
175
  input.push(0);
78
176
  }
177
+ sha256_hex(&input)
178
+ }
179
+
180
+ /// Every file under `directory`, as a `/`-separated path relative to `root`.
181
+ fn files_under(root: &Path, directory: &Path) -> BuildResult<Vec<String>> {
182
+ let unreadable = |error: io::Error| format!("read {}: {error}", directory.display());
183
+ let mut files = Vec::new();
184
+ for entry in fs::read_dir(directory).map_err(unreadable)? {
185
+ let path = entry.map_err(unreadable)?.path();
186
+ if path.is_dir() {
187
+ files.extend(files_under(root, &path)?);
188
+ } else if path.is_file() {
189
+ let relative = path
190
+ .strip_prefix(root)?
191
+ .to_str()
192
+ .ok_or_else(|| format!("source path is not UTF-8: {}", path.display()))?;
193
+ files.push(relative.replace('\\', "/"));
194
+ }
195
+ }
196
+ Ok(files)
197
+ }
198
+
199
+ /// SHA-256 as hex, from the platform's tool rather than a build dependency.
200
+ fn sha256_hex(input: &[u8]) -> BuildResult<String> {
79
201
  let mut command = if cfg!(target_os = "macos") {
80
- let mut command = Command::new("shasum");
81
- command.args(["-a", "256"]);
82
- command
202
+ let mut shasum = Command::new("shasum");
203
+ shasum.args(["-a", "256"]);
204
+ shasum
83
205
  } else {
84
206
  Command::new("sha256sum")
85
207
  };
@@ -87,73 +209,90 @@ fn build_identity() {
87
209
  .stdin(Stdio::piped())
88
210
  .stdout(Stdio::piped())
89
211
  .spawn()
90
- .expect("a SHA-256 tool is required to identify native build inputs");
212
+ .map_err(|error| {
213
+ format!("a SHA-256 tool is required to identify native build inputs: {error}")
214
+ })?;
215
+ // The piped stdin closes at the end of this statement, ending the input.
91
216
  child
92
217
  .stdin
93
218
  .take()
94
- .unwrap()
95
- .write_all(&input)
96
- .expect("hash native build inputs");
97
- let output = child.wait_with_output().expect("join native source hash");
98
- assert!(output.status.success(), "native source hash failed");
99
- let text = String::from_utf8(output.stdout).unwrap();
100
- let digest = text.split_whitespace().next().unwrap();
101
- assert!(digest.len() == 64 && digest.chars().all(|ch| ch.is_ascii_hexdigit()));
102
-
103
- let cc = env::var("CC").unwrap_or_else(|_| "clang".into());
104
- let cxx = env::var("CXX").unwrap_or_else(|_| "clang++".into());
105
- let mut fields = vec![
106
- "\"schemaVersion\":1".to_owned(),
107
- "\"abiVersion\":2".to_owned(),
108
- format!("\"sourceSha256\":{}", quote(digest)),
109
- format!("\"target\":{}", quote(&env::var("TARGET").unwrap())),
110
- format!("\"profile\":{}", quote(&env::var("PROFILE").unwrap())),
111
- format!("\"rustc\":{}", quote(&version(&env::var("RUSTC").unwrap()))),
112
- format!("\"cc\":{}", quote(&version(&cc))),
113
- format!("\"cxx\":{}", quote(&version(&cxx))),
114
- ];
115
- for key in [
116
- "CC",
117
- "CXX",
118
- "CARGO_ENCODED_RUSTFLAGS",
119
- "CFLAGS",
120
- "CXXFLAGS",
121
- "MACOSX_DEPLOYMENT_TARGET",
122
- ] {
123
- println!("cargo:rerun-if-env-changed={key}");
124
- if let Ok(value) = env::var(key) {
125
- fields.push(format!("{}:{}", quote(key), quote(&value)));
126
- }
219
+ .ok_or("the hash tool has no stdin")?
220
+ .write_all(input)?;
221
+ let output = child.wait_with_output()?;
222
+ if !output.status.success() {
223
+ return Err(format!("native source hash failed: {}", output.status).into());
224
+ }
225
+ let text = String::from_utf8(output.stdout)?;
226
+ let digest = text.split_whitespace().next().unwrap_or_default();
227
+ if digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) {
228
+ return Err(format!("unexpected SHA-256 digest {digest:?}").into());
127
229
  }
128
- println!(
129
- "cargo:rustc-env=REACTOR_EFFECT_BUILD_IDENTITY={{{}}}",
130
- fields.join(",")
131
- );
230
+ Ok(digest.to_owned())
132
231
  }
133
232
 
134
- fn main() {
135
- build_identity();
136
- if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
137
- return;
233
+ /// The first line of `program --version`.
234
+ fn tool_version(program: &str) -> BuildResult<String> {
235
+ let output = Command::new(program)
236
+ .arg("--version")
237
+ .output()
238
+ .map_err(|error| format!("could not inspect native build tool {program}: {error}"))?;
239
+ if !output.status.success() {
240
+ return Err(format!("native build tool {program} failed: {}", output.status).into());
241
+ }
242
+ let text = String::from_utf8(output.stdout)?;
243
+ Ok(text.lines().next().unwrap_or_default().to_owned())
244
+ }
245
+
246
+ /// `value` as a JSON string literal.
247
+ fn json_string(value: &str) -> String {
248
+ JsonString(value).to_string()
249
+ }
250
+
251
+ /// Formats a string as a JSON string literal.
252
+ struct JsonString<'a>(&'a str);
253
+
254
+ impl fmt::Display for JsonString<'_> {
255
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256
+ f.write_char('"')?;
257
+ for ch in self.0.chars() {
258
+ match ch {
259
+ '"' => f.write_str("\\\"")?,
260
+ '\\' => f.write_str("\\\\")?,
261
+ '\n' => f.write_str("\\n")?,
262
+ '\r' => f.write_str("\\r")?,
263
+ '\t' => f.write_str("\\t")?,
264
+ ch if ch.is_control() => write!(f, "\\u{:04x}", u32::from(ch))?,
265
+ ch => f.write_char(ch)?,
266
+ }
267
+ }
268
+ f.write_char('"')
138
269
  }
270
+ }
139
271
 
140
- // The pinned Reactor libwebrtc archive contains ScreenCaptureKit objects
141
- // produced by Clang. They reference compiler-rt's deployment-version helper,
142
- // which rustc's macOS link line does not add on its own.
272
+ /// The pinned Reactor libwebrtc archive contains `ScreenCaptureKit` objects
273
+ /// produced by Clang. They reference compiler-rt's deployment-version helper,
274
+ /// which rustc's macOS link line does not add on its own.
275
+ fn link_compiler_rt() -> BuildResult<()> {
143
276
  let cc = env::var("CC").unwrap_or_else(|_| "clang".to_owned());
144
277
  let output = Command::new(cc)
145
278
  .arg("-print-resource-dir")
146
279
  .output()
147
- .expect("clang is required to link the Reactor libwebrtc archive on macOS");
148
- assert!(output.status.success(), "clang -print-resource-dir failed");
149
- let resource = String::from_utf8(output.stdout).expect("clang resource path is not UTF-8");
280
+ .map_err(|error| {
281
+ format!("clang is required to link the Reactor libwebrtc archive on macOS: {error}")
282
+ })?;
283
+ if !output.status.success() {
284
+ return Err(format!("clang -print-resource-dir failed: {}", output.status).into());
285
+ }
286
+ let resource = String::from_utf8(output.stdout)?;
150
287
  let directory = PathBuf::from(resource.trim()).join("lib/darwin");
151
288
  let runtime = directory.join("libclang_rt.osx.a");
152
- assert!(
153
- runtime.is_file(),
154
- "missing macOS compiler-rt archive at {}",
155
- runtime.display()
156
- );
157
- println!("cargo:rustc-link-search=native={}", directory.display());
158
- println!("cargo:rustc-link-lib=static=clang_rt.osx");
289
+ if !runtime.is_file() {
290
+ return Err(format!("missing macOS compiler-rt archive at {}", runtime.display()).into());
291
+ }
292
+ println!("cargo::rustc-link-search=native={}", directory.display());
293
+ println!("cargo::rustc-link-lib=static=clang_rt.osx");
294
+ // Cargo passes rustc-link-lib only to the library target, and the far-peer
295
+ // example cannot link this cdylib, so it names the archive itself.
296
+ println!("cargo::rustc-link-arg-examples={}", runtime.display());
297
+ Ok(())
159
298
  }
@@ -0,0 +1,7 @@
1
+ # Lint levels live in Cargo.toml's [lints] table; this file only configures them.
2
+
3
+ # In a test, a failed unwrap, expect, index or panic is the assertion.
4
+ allow-unwrap-in-tests = true
5
+ allow-expect-in-tests = true
6
+ allow-indexing-slicing-in-tests = true
7
+ allow-panic-in-tests = true
@@ -8,16 +8,30 @@
8
8
  extern "C" {
9
9
  #endif
10
10
 
11
+ /* ABI 4. Every supported host is little-endian; headers are native-endian. */
12
+
11
13
  typedef struct ReactorEffectPeer ReactorEffectPeer;
12
14
 
15
+ /*
16
+ * Non-negative statuses are outcomes. Negative statuses are failure classes;
17
+ * the set is closed for this ABI, and a host maps each class to its own error
18
+ * type. Diagnostic text travels beside a class in ReactorEffectFailure.
19
+ *
20
+ * A misaligned pointer argument fails with INVALID_INPUT without being used.
21
+ * A request over 1 MiB or a message over 256 KiB fails with OVERFLOW without
22
+ * being read.
23
+ */
13
24
  enum ReactorEffectStatus {
14
25
  REACTOR_EFFECT_OK = 0,
15
- REACTOR_EFFECT_AGAIN = 1,
16
- REACTOR_EFFECT_BUFFER_TOO_SMALL = 2,
17
- REACTOR_EFFECT_CLOSED = 3,
18
- REACTOR_EFFECT_INVALID = -1,
19
- REACTOR_EFFECT_NATIVE = -2,
20
- REACTOR_EFFECT_OVERFLOW = -3
26
+ REACTOR_EFFECT_AGAIN = 1, /* take: the queue is empty */
27
+ REACTOR_EFFECT_BUFFER_TOO_SMALL = 2, /* sizes written; the item stays queued */
28
+ REACTOR_EFFECT_CLOSED = 3, /* the peer is fenced or shut down */
29
+ REACTOR_EFFECT_INVALID_INPUT = -1, /* an argument or request was rejected */
30
+ REACTOR_EFFECT_NATIVE = -2, /* an unclassified libwebrtc or bridge failure */
31
+ REACTOR_EFFECT_OVERFLOW = -3, /* a queue, buffer or message bound was exceeded */
32
+ REACTOR_EFFECT_PROTOCOL = -4, /* the remote peer broke the negotiated contract */
33
+ REACTOR_EFFECT_SDP_REJECTED = -5, /* libwebrtc refused to create or apply an SDP */
34
+ REACTOR_EFFECT_CHANNEL_CLOSED = -6 /* the data channel is not open */
21
35
  };
22
36
 
23
37
  enum ReactorEffectCall {
@@ -34,15 +48,63 @@ enum ReactorEffectChannel {
34
48
  REACTOR_EFFECT_DATA = 1
35
49
  };
36
50
 
51
+ /* Readiness bits passed to ReactorEffectNotify. */
52
+ enum ReactorEffectReady {
53
+ REACTOR_EFFECT_READY_EVENTS = 1,
54
+ REACTOR_EFFECT_READY_VIDEO = 2,
55
+ REACTOR_EFFECT_READY_AUDIO = 4
56
+ };
57
+
58
+ /* Diagnostic text for a failure status. Never match on it: the status is the class. */
59
+ typedef struct ReactorEffectFailure {
60
+ uint32_t message_len;
61
+ uint8_t message[1020]; /* UTF-8, truncated on a character boundary */
62
+ } ReactorEffectFailure;
63
+
64
+ typedef struct ReactorEffectVideoHeader {
65
+ uint32_t width;
66
+ uint32_t height;
67
+ uint32_t data_len; /* BGRA bytes: width * height * 4 */
68
+ uint32_t metadata_len; /* frame-metadata user data bytes */
69
+ uint64_t frame_id; /* 0 when the sender supplied none */
70
+ uint64_t timestamp_us; /* sender capture time; 0 when absent */
71
+ uint32_t track; /* index into the prepare request's tracks */
72
+ uint32_t reserved;
73
+ uint64_t sequence; /* per-track admission sequence from 0, stamped before any queue drop */
74
+ } ReactorEffectVideoHeader; /* 48 bytes */
75
+
76
+ typedef struct ReactorEffectAudioHeader {
77
+ uint32_t sample_rate;
78
+ uint32_t channels;
79
+ uint32_t samples; /* interleaved int16_t samples: frames * channels */
80
+ uint32_t track; /* index into the prepare request's tracks */
81
+ uint64_t sequence; /* per-track admission sequence from 0, stamped before any queue drop */
82
+ } ReactorEffectAudioHeader; /* 24 bytes */
83
+
84
+ /*
85
+ * Runs on the peer's notifier thread with the readiness bits of every queue
86
+ * that received an item since the previous call; a host must drain each named
87
+ * queue until AGAIN. The callback may call the take functions. libwebrtc
88
+ * threads never run it and never wait for it: while it has not returned, they
89
+ * keep queueing and the bounded queues count what they evict.
90
+ */
91
+ typedef void (*ReactorEffectNotify)(uint32_t ready);
92
+
37
93
  uint32_t reactor_effect_abi_version(void);
38
94
  /* Static source/build identity of this loaded image; never free the pointer. */
39
95
  const char *reactor_effect_build_identity(void);
40
96
 
41
- ReactorEffectPeer *reactor_effect_peer_create(void);
97
+ /*
98
+ * notify may be NULL: no notifier thread runs and the host polls the take
99
+ * functions itself. Every peer shares one process-wide libwebrtc factory,
100
+ * created on the first prepare and never destroyed.
101
+ */
102
+ ReactorEffectPeer *reactor_effect_peer_create(ReactorEffectNotify notify);
42
103
 
43
104
  /*
44
- * request is UTF-8. The response is UTF-8 JSON on both success and failure.
45
- * Calls are serialized by the native peer owner and may block on libwebrtc.
105
+ * request is UTF-8. On OK the response is UTF-8 JSON and response_cap must be
106
+ * at least 4 MiB (BUFFER_TOO_SMALL reports that size). Calls are serialized by
107
+ * the native peer owner and may block on libwebrtc. failure may be NULL.
46
108
  */
47
109
  int reactor_effect_peer_call(
48
110
  ReactorEffectPeer *peer,
@@ -51,63 +113,60 @@ int reactor_effect_peer_call(
51
113
  size_t request_len,
52
114
  uint8_t *response,
53
115
  size_t response_cap,
54
- size_t *response_len);
116
+ size_t *response_len,
117
+ ReactorEffectFailure *failure);
55
118
 
56
119
  int reactor_effect_peer_send(
57
120
  ReactorEffectPeer *peer,
58
121
  uint32_t channel,
59
122
  const uint8_t *data,
60
123
  size_t data_len,
61
- uint8_t *error,
62
- size_t error_cap,
63
- size_t *error_len);
124
+ ReactorEffectFailure *failure);
64
125
 
65
126
  /*
66
- * Poll packets are [u32 little-endian header length][UTF-8 JSON header][payload].
67
- * A zero-capacity call returns BUFFER_TOO_SMALL with the required packet size and
68
- * retains that exact packet until copy or close. Retained packets count toward
69
- * item and byte bounds and cannot be evicted by producer pressure. Each queue
70
- * permits one reader across probe/copy; the host must serialize that pair.
71
- * timeout_ms == 0 is a nonblocking poll.
127
+ * Nonblocking takes. Each copies its queue's oldest item into caller memory
128
+ * once and removes it. BUFFER_TOO_SMALL writes the required size (out_len or
129
+ * the header) and keeps the item at the front; AGAIN means the queue is empty.
130
+ * Events are [u32 little-endian header length][UTF-8 JSON header][payload].
72
131
  */
73
- int reactor_effect_peer_poll_event(
132
+ int reactor_effect_peer_take_event(
74
133
  ReactorEffectPeer *peer,
75
- uint32_t timeout_ms,
76
134
  uint8_t *out,
77
135
  size_t out_cap,
78
136
  size_t *out_len);
79
137
 
80
- int reactor_effect_peer_poll_video(
138
+ int reactor_effect_peer_take_video(
81
139
  ReactorEffectPeer *peer,
82
- uint32_t timeout_ms,
83
- uint8_t *out,
84
- size_t out_cap,
85
- size_t *out_len);
86
-
87
- int reactor_effect_peer_poll_audio(
140
+ ReactorEffectVideoHeader *header,
141
+ uint8_t *bgra,
142
+ size_t bgra_cap,
143
+ uint8_t *metadata,
144
+ size_t metadata_cap);
145
+
146
+ /* pcm_cap counts int16_t samples, not bytes. */
147
+ int reactor_effect_peer_take_audio(
88
148
  ReactorEffectPeer *peer,
89
- uint32_t timeout_ms,
90
- uint8_t *out,
91
- size_t out_cap,
92
- size_t *out_len);
149
+ ReactorEffectAudioHeader *header,
150
+ int16_t *pcm,
151
+ size_t pcm_cap);
93
152
 
94
153
  /* Immediate admission fence. No callback/event can be admitted after return. */
95
154
  void reactor_effect_peer_close(ReactorEffectPeer *peer);
96
155
 
97
156
  /*
98
- * Drops channels/tracks/PeerConnection on the owner thread, waits for all native
99
- * callback guards to leave, then joins that owner. Safe to call more than once.
157
+ * Drops channels/tracks/PeerConnection on the owner thread, waits for all
158
+ * native callback guards to leave, then joins that owner and the notifier
159
+ * thread. The notifier may be waiting for the host to run ReactorEffectNotify,
160
+ * so never call this from the thread that runs that callback. Safe to call
161
+ * more than once. failure may be NULL.
100
162
  */
101
- int reactor_effect_peer_shutdown(
102
- ReactorEffectPeer *peer,
103
- uint8_t *error,
104
- size_t error_cap,
105
- size_t *error_len);
163
+ int reactor_effect_peer_shutdown(ReactorEffectPeer *peer, ReactorEffectFailure *failure);
106
164
 
107
165
  /*
108
- * Performs shutdown if needed, then frees the opaque handle. The host must
109
- * first join EVERY foreign call using this handle, including work still queued
110
- * in its FFI executor. Native owner shutdown alone does not establish that.
166
+ * Performs shutdown if needed (so the same thread rule applies), then frees
167
+ * the opaque handle. The host must first join EVERY foreign call using this
168
+ * handle, including work still queued in its FFI executor. Native owner
169
+ * shutdown alone does not establish that.
111
170
  */
112
171
  void reactor_effect_peer_destroy(ReactorEffectPeer *peer);
113
172