{"record":{"id":"704152014bb6d41f","repo":"n0-computer/iroh","slug":"invalidalpn","errorCode":"InvalidAlpn","errorMessage":"Invalid ALPN","messagePattern":"Invalid ALPN","errorType":"validation","errorClass":"ConnectWithOptsError","httpStatus":null,"severity":"error","filePath":"iroh/src/endpoint.rs","lineNumber":1123,"sourceCode":"    ) -> Result<Connecting, ConnectWithOptsError> {\n        if self.is_closed() {\n            return Err(e!(ConnectWithOptsError::EndpointClosed));\n        }\n\n        let endpoint_addr: EndpointAddr = endpoint_addr.into();\n        let endpoint_id = endpoint_addr.id;\n\n        Span::current().record(\"remote\", tracing::field::display(endpoint_id.fmt_short()));\n\n        if let BeforeConnectOutcome::Reject =\n            self.inner.hooks.before_connect(&endpoint_addr, alpn).await\n        {\n            return Err(e!(ConnectWithOptsError::LocallyRejected));\n        }\n\n        // Connecting to ourselves is not supported.\n        ensure!(endpoint_id != self.id(), ConnectWithOptsError::SelfConnect);\n        ensure!(!alpn.is_empty(), ConnectWithOptsError::InvalidAlpn);\n\n        event!(\n            target: \"iroh::_events::conn::connecting\",\n            tracing::Level::DEBUG,\n            remote_id = %endpoint_id.fmt_short(),\n            alpn = %String::from_utf8_lossy(alpn),\n        );\n\n        debug!(\n            relay_url = ?endpoint_addr.relay_urls().next().cloned(),\n            ip_addresses = ?endpoint_addr.ip_addrs().cloned().collect::<Vec<_>>(),\n            \"connecting\",\n        );\n\n        let mapped_addr = self.inner.resolve_remote(endpoint_addr).await??;\n\n        let transport_config = options\n            .transport_config","sourceCodeStart":1105,"sourceCodeEnd":1141,"githubUrl":"https://github.com/n0-computer/iroh/blob/2b4de030ce5e0133f272871a76f0c685c63f552a/iroh/src/endpoint.rs#L1105-L1141","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nendpoint.connect(remote_id, b\"\").await?;\n// after\nendpoint.connect(remote_id, b\"my-app/1\").await?;","handlingStrategy":"validation","validationCode":"fn valid_alpn(alpn: &[u8]) -> bool { !alpn.is_empty() }","typeGuard":"fn non_empty_alpn(alpn: &[u8]) -> Option<&[u8]> { (!alpn.is_empty()).then_some(alpn) }","tryCatchPattern":"match endpoint.connect(id, alpn).await {\n    Err(ConnectWithOptsError::InvalidAlpn) => bail!(\"ALPN must be non-empty\"),\n    other => other?,\n}","preventionTips":["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."],"tags":["alpn","connection","validation","quic"],"backgroundTag":"empty-required-field","analyzedSha":"2b4de030ce5e0133f272871a76f0c685c63f552a","analyzedAt":"2026-09-08T04:26:47.755Z","contentChangedAt":"2026-09-08T04:26:47.755Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}