n0-computer/iroh · error · ConnectWithOptsError
InvalidAlpn
InvalidAlpn
Error message
Invalid ALPN
What it means
The ALPN bytes passed to endpoint.connect were empty. ALPN (application-layer protocol negotiation) is mandatory in iroh: every connection must declare which protocol it speaks, so an empty ALPN is rejected with ConnectWithOptsError::InvalidAlpn.
Solutions
- Pass a non-empty ALPN byte string, e.g. b"my-app/proto/1".
- Validate alpn.is_empty() at the call site and return a clearer application-level error.
- Set the ALPN in configuration or make it a required constructor parameter instead of a default empty value.
- Ensure the same ALPN is used by the accepting endpoint so the connection also succeeds post-handshake.
Example fix
// before endpoint.connect(remote_id, b"").await?; // after endpoint.connect(remote_id, b"my-app/1").await?;
Defensive patterns
Strategy: validation
Validate before calling
fn valid_alpn(alpn: &[u8]) -> bool { !alpn.is_empty() } Type guard
fn non_empty_alpn(alpn: &[u8]) -> Option<&[u8]> { (!alpn.is_empty()).then_some(alpn) } Try / catch
match endpoint.connect(id, alpn).await {
Err(ConnectWithOptsError::InvalidAlpn) => bail!("ALPN must be non-empty"),
other => other?,
} Prevention
- Define ALPN constants in one module; never leave them empty.
- Make ALPN a required parameter, not a defaulted empty value.
- Validate protocol config at startup.
- Use the same ALPN on dialer and acceptor.
When it happens
Trigger: Calling endpoint.connect(endpoint_id, b"") or passing an empty/zero-length alpn slice — often from a constant that was never set, or a protocol field deserialized as empty.
Common situations: Placeholder ALPN constants left as empty strings, config-driven ALPN values missing and defaulting to "", generic connect wrappers forwarding empty protocol fields from the caller.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/704152014bb6d41f.
Report an issue: GitHub.
Appendix: source
Thrown at iroh/src/endpoint.rs:1123
) -> Result<Connecting, ConnectWithOptsError> {
if self.is_closed() {
return Err(e!(ConnectWithOptsError::EndpointClosed));
}
let endpoint_addr: EndpointAddr = endpoint_addr.into();
let endpoint_id = endpoint_addr.id;
Span::current().record("remote", tracing::field::display(endpoint_id.fmt_short()));
if let BeforeConnectOutcome::Reject =
self.inner.hooks.before_connect(&endpoint_addr, alpn).await
{
return Err(e!(ConnectWithOptsError::LocallyRejected));
}
// Connecting to ourselves is not supported.
ensure!(endpoint_id != self.id(), ConnectWithOptsError::SelfConnect);
ensure!(!alpn.is_empty(), ConnectWithOptsError::InvalidAlpn);
event!(
target: "iroh::_events::conn::connecting",
tracing::Level::DEBUG,
remote_id = %endpoint_id.fmt_short(),
alpn = %String::from_utf8_lossy(alpn),
);
debug!(
relay_url = ?endpoint_addr.relay_urls().next().cloned(),
ip_addresses = ?endpoint_addr.ip_addrs().cloned().collect::<Vec<_>>(),
"connecting",
);
let mapped_addr = self.inner.resolve_remote(endpoint_addr).await??;
let transport_config = options
.transport_configView on GitHub (pinned to 2b4de030ce)