Skip to content

Reorganize moq-native API: feature-gated modules and builder pattern#1141

Open
kixelated wants to merge 3 commits intodevfrom
moq-native-api
Open

Reorganize moq-native API: feature-gated modules and builder pattern#1141
kixelated wants to merge 3 commits intodevfrom
moq-native-api

Conversation

@kixelated
Copy link
Collaborator

Summary

  • Add with_* builder methods to ClientConfig and ServerConfig for fluent configuration
  • Move websocket types into public ws module (ClientWebSocketws::ClientConfig, WebSocketListenerws::Listener)
  • Move iroh types into public iroh module (IrohEndpointConfigiroh::EndpointConfig, etc.)
  • Remove redundant prefixes from internal quinn/noq/quiche types via module namespacing
  • Use explicit re-exports in lib.rs instead of glob re-exports
  • Add #[group(id = "...")] to all clap::Args structs to prevent name collisions

Closes #1097

Test plan

  • just fix — no formatting issues
  • cargo check -p moq-native --all-features — compiles
  • cargo check -p moq-cli -p moq-relay --all-features — downstream crates compile
  • cargo test -p moq-native --lib — all 8 unit tests pass

🤖 Generated with Claude Code

kixelated and others added 2 commits March 19, 2026 23:19
- Add with_* builder methods to ClientConfig and ServerConfig
- Move websocket types into public `ws` module (ClientWebSocket → ws::ClientConfig, WebSocketListener → ws::Listener)
- Move iroh types into public `iroh` module (IrohEndpointConfig → iroh::EndpointConfig, IrohEndpoint → iroh::Endpoint)
- Remove prefixes from internal quinn/noq/quiche types (e.g. QuinnClient → quinn::Client)
- Use explicit re-exports in lib.rs instead of glob re-exports
- Add #[group(id = "...")] to all clap::Args structs to prevent name collisions

Closes #1097

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

Walkthrough

This PR renames and reorganizes types and modules across crates. In moq-native, backend-specific types are renamed to generic names (e.g., IrohEndpointConfigEndpointConfig, QuinnClient/NoqClient/QuicheClientClient, various *RequestRequest), root wildcard exports are replaced with explicit exports, and websocket is moved to a ws module. ClientConfig and ServerConfig gain fluent builder methods and clap group attributes are added. Call sites in client, server, tests, moq-cli, and moq-relay are updated to use the new types and module paths (e.g., moq_native::iroh::EndpointConfig, moq_native::ws::Listener).

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: reorganizing moq-native API with feature-gated modules and adding builder pattern support.
Description check ✅ Passed The description provides clear summary of changes including builder methods, module reorganization, type renames, and test verification, directly related to the changeset.
Linked Issues check ✅ Passed All objectives from issue #1097 are met: feature-gated modules (ws, iroh), builder pattern methods added, explicit re-exports, clap group attributes, and internal type renames.
Out of Scope Changes check ✅ Passed All changes are directly related to #1097 objectives: module reorganization, type migrations, builder pattern implementation, and test updates to match new API.
Docstring Coverage ✅ Passed Docstring coverage is 91.38% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch moq-native-api
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch moq-native-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
rs/moq-native/src/iroh.rs (2)

47-61: ⚠️ Potential issue | 🟠 Major

Write generated iroh secrets with restricted permissions.

Line 61 uses a plain tokio::fs::write, so the freshly generated private key can inherit default filesystem permissions and end up readable by other local users. Since this key defines the endpoint identity, create it with owner-only permissions (create_new + 0600, or the platform equivalent) instead of a default-permissions write.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-native/src/iroh.rs` around lines 47 - 61, The generated secret is
written with tokio::fs::write in EndpointConfig::bind (when SecretKey::generate
is used), which can create a file with default permissive permissions; change
this to create the file with owner-only permissions instead: open the target
PathBuf with tokio::fs::OpenOptions (create_new = true, write = true) and set
mode to 0o600 (use std::os::unix::fs::OpenOptionsExt::mode on Unix) before
writing the hex-encoded secret bytes, falling back to a safe cross-platform
alternative on Windows; ensure you still handle and propagate the same errors as
the original tokio::fs::write usage.

124-133: ⚠️ Potential issue | 🟠 Major

Respect configured MoQ versions on the iroh path.

Lines 128-131 accept the first client-offered protocol, and Lines 172-178 always advertise the full moq_lite::ALPNS set. That makes ClientConfig::version and ServerConfig::version ineffective for iroh:// sessions, unlike the noq/quinn/quiche paths that negotiate against versions.alpns(). Thread the allowed ALPN list through these helpers and reject when there is no overlap.

Also applies to: 156-178

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-native/src/iroh.rs` around lines 124 - 133, The iroh path currently
accepts the first client-offered protocol and advertises the hard-coded
moq_lite::ALPNS, ignoring configured
ClientConfig::version/ServerConfig::version; change the logic in the
Request::WebTransport branch of ok (and the similar helper used around the
156-178 region) to accept only ALPNs that intersect with the configured allowed
list (use versions.alpns() or the server/client version-derived ALPN list), pick
the first matching ALPN from request.protocols that is also in the allowed set,
call response.with_protocol(matching) when present, and return a reject/error
when there is no overlap; also replace any use of moq_lite::ALPNS for advertised
ALPNs with the negotiated/allowed ALPN list so negotiation respects
ClientConfig::version/ServerConfig::version.
🧹 Nitpick comments (1)
rs/moq-native/src/server.rs (1)

122-155: Add builders for the QUIC-LB fields too.

Lines 122-155 cover most of ServerConfig, but quic_lb_id and quic_lb_nonce still require direct field mutation. If this PR is meant to move callers toward a fluent API, those two knobs should get with_* helpers as well.

♻️ Minimal API completion
+	pub fn with_quic_lb_id(mut self, quic_lb_id: ServerId) -> Self {
+		self.quic_lb_id = Some(quic_lb_id);
+		self
+	}
+
+	pub fn with_quic_lb_nonce(mut self, quic_lb_nonce: usize) -> Self {
+		self.quic_lb_nonce = Some(quic_lb_nonce);
+		self
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-native/src/server.rs` around lines 122 - 155, ServerConfig's fluent
API is incomplete because quic_lb_id and quic_lb_nonce still require direct
mutation; add builder methods (e.g., with_quic_lb_id and with_quic_lb_nonce)
mirroring the style of with_bind/with_backend/etc. that set the corresponding
fields on self (accepting the appropriate types used by quic_lb_id and
quic_lb_nonce), return Self, and follow the same mut self, set field, return
self pattern so callers can use the same fluent API as with_bind, with_backend,
with_tls_cert, etc.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@rs/moq-native/src/iroh.rs`:
- Around line 47-61: The generated secret is written with tokio::fs::write in
EndpointConfig::bind (when SecretKey::generate is used), which can create a file
with default permissive permissions; change this to create the file with
owner-only permissions instead: open the target PathBuf with
tokio::fs::OpenOptions (create_new = true, write = true) and set mode to 0o600
(use std::os::unix::fs::OpenOptionsExt::mode on Unix) before writing the
hex-encoded secret bytes, falling back to a safe cross-platform alternative on
Windows; ensure you still handle and propagate the same errors as the original
tokio::fs::write usage.
- Around line 124-133: The iroh path currently accepts the first client-offered
protocol and advertises the hard-coded moq_lite::ALPNS, ignoring configured
ClientConfig::version/ServerConfig::version; change the logic in the
Request::WebTransport branch of ok (and the similar helper used around the
156-178 region) to accept only ALPNs that intersect with the configured allowed
list (use versions.alpns() or the server/client version-derived ALPN list), pick
the first matching ALPN from request.protocols that is also in the allowed set,
call response.with_protocol(matching) when present, and return a reject/error
when there is no overlap; also replace any use of moq_lite::ALPNS for advertised
ALPNs with the negotiated/allowed ALPN list so negotiation respects
ClientConfig::version/ServerConfig::version.

---

Nitpick comments:
In `@rs/moq-native/src/server.rs`:
- Around line 122-155: ServerConfig's fluent API is incomplete because
quic_lb_id and quic_lb_nonce still require direct mutation; add builder methods
(e.g., with_quic_lb_id and with_quic_lb_nonce) mirroring the style of
with_bind/with_backend/etc. that set the corresponding fields on self (accepting
the appropriate types used by quic_lb_id and quic_lb_nonce), return Self, and
follow the same mut self, set field, return self pattern so callers can use the
same fluent API as with_bind, with_backend, with_tls_cert, etc.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bcb380a-8abd-45c4-96f5-590bebdb46e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2626a25 and a8203ff.

📒 Files selected for processing (12)
  • rs/moq-cli/src/main.rs
  • rs/moq-native/src/client.rs
  • rs/moq-native/src/iroh.rs
  • rs/moq-native/src/lib.rs
  • rs/moq-native/src/noq.rs
  • rs/moq-native/src/quiche.rs
  • rs/moq-native/src/quinn.rs
  • rs/moq-native/src/server.rs
  • rs/moq-native/src/ws.rs
  • rs/moq-native/tests/backend.rs
  • rs/moq-native/tests/broadcast.rs
  • rs/moq-relay/src/config.rs

…uilder methods

- Write iroh secret key files with 0o600 permissions on Unix
- Pass configured ALPN list to iroh Request::accept/ok and connect(),
  matching the pattern used by quinn/noq/quiche backends
- Add with_quic_lb_id and with_quic_lb_nonce builder methods to ServerConfig

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rs/moq-native/src/iroh.rs (1)

104-123: ⚠️ Potential issue | 🟠 Major

Use the new alpns parameter on the raw QUIC branch too.

Line 119 still checks moq_lite::ALPNS, so the configured version subset is ignored for non-H3 iroh sessions.

Targeted fix
-			alpn if moq_lite::ALPNS.contains(&alpn) => Ok(Self::Quic {
+			alpn if alpns.contains(&alpn) => Ok(Self::Quic {
 				request: web_transport_iroh::QuicRequest::accept(conn),
 			}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-native/src/iroh.rs` around lines 104 - 123, The accept function
ignores the passed-in alpns for non-H3 sessions; replace the moq_lite::ALPNS
check with the local alpns parameter (use alpns.contains(&alpn)) in the Quic
match arm and ensure the Self::Quic construction carries the alpns value
(similar to Self::WebTransport) and uses
web_transport_iroh::QuicRequest::accept(conn) for the request.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rs/moq-native/src/iroh.rs`:
- Around line 56-63: The current bootstrap probes the path with
PathBuf::exists() then calls write_secret_file(path, ...) which opens with
create_new(true), creating a TOCTOU race; change the logic in the
secret-bootstrapping code that uses self.secret (and the duplicate block around
the same logic at the later location) to avoid the separate exists() check:
attempt to create/write the file via write_secret_file and if it returns an
AlreadyExists error, fall back to reading the file (e.g., call read_secret_file
or the same code path used when the file exists) instead of propagating the
error; keep using SecretKey::generate and rand::rng() for the generation path
and ensure write_secret_file remains the create_new(true) call so the race is
safely handled by the AlreadyExists branch.

---

Outside diff comments:
In `@rs/moq-native/src/iroh.rs`:
- Around line 104-123: The accept function ignores the passed-in alpns for
non-H3 sessions; replace the moq_lite::ALPNS check with the local alpns
parameter (use alpns.contains(&alpn)) in the Quic match arm and ensure the
Self::Quic construction carries the alpns value (similar to Self::WebTransport)
and uses web_transport_iroh::QuicRequest::accept(conn) for the request.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b8aef8c-67e8-4a0e-8515-1559f50ddfb7

📥 Commits

Reviewing files that changed from the base of the PR and between a8203ff and 390e2bb.

📒 Files selected for processing (3)
  • rs/moq-native/src/client.rs
  • rs/moq-native/src/iroh.rs
  • rs/moq-native/src/server.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rs/moq-native/src/server.rs

Comment on lines 56 to 63
} else if let Some(path) = self.secret {
let path = PathBuf::from(path);
if !path.exists() {
// Generate a new random secret and write it to the file.
// Generate a new random secret and write it to the file with restricted permissions.
let secret = SecretKey::generate(&mut rand::rng());
tokio::fs::write(path, hex::encode(secret.to_bytes())).await?;
let data = hex::encode(secret.to_bytes());
write_secret_file(&path, data.as_bytes()).await?;
secret
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid the exists() / create_new() TOCTOU on secret-file bootstrap.

Lines 58-63 do a separate existence probe before write_secret_file() opens the path with create_new(true). If another process wins that race, bind() fails even though the key file is now valid; please fall back to reading the file on AlreadyExists instead of bubbling the error.

Also applies to: 225-236

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-native/src/iroh.rs` around lines 56 - 63, The current bootstrap probes
the path with PathBuf::exists() then calls write_secret_file(path, ...) which
opens with create_new(true), creating a TOCTOU race; change the logic in the
secret-bootstrapping code that uses self.secret (and the duplicate block around
the same logic at the later location) to avoid the separate exists() check:
attempt to create/write the file via write_secret_file and if it returns an
AlreadyExists error, fall back to reading the file (e.g., call read_secret_file
or the same code path used when the file exists) instead of propagating the
error; keep using SecretKey::generate and rand::rng() for the generation path
and ensure write_secret_file remains the create_new(true) call so the race is
safely handled by the AlreadyExists branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant