refactor: centralize peer control routing
This commit is contained in:
parent
d443b82f1a
commit
a32cab645f
4 changed files with 218 additions and 126 deletions
|
|
@ -3,6 +3,7 @@ mod daemon;
|
||||||
pub mod doctor;
|
pub mod doctor;
|
||||||
mod local_control;
|
mod local_control;
|
||||||
mod peer_client;
|
mod peer_client;
|
||||||
|
mod peer_control;
|
||||||
mod resource_contracts;
|
mod resource_contracts;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
|
@ -68,6 +69,10 @@ use iroh::protocol::ProtocolHandler;
|
||||||
use iroh_docs::api::protocol::{AddrInfoOptions, ShareMode};
|
use iroh_docs::api::protocol::{AddrInfoOptions, ShareMode};
|
||||||
pub use local_control::handle_request_async;
|
pub use local_control::handle_request_async;
|
||||||
use peer_client::{request_overlay_wire, request_peer_control, request_pipe_wire};
|
use peer_client::{request_overlay_wire, request_peer_control, request_pipe_wire};
|
||||||
|
use peer_control::{
|
||||||
|
IrohAlpnRoute, PeerControlCaller, authenticate_peer_control_caller, classify_iroh_alpn,
|
||||||
|
display_alpn, ensure_peer_card_matches_endpoint, iroh_node_addr_from_candidate,
|
||||||
|
};
|
||||||
use runtime::{
|
use runtime::{
|
||||||
NodeRuntime, OverlayTunCounters, OverlayTunRuntime, PipeRuntime, PubsubGossipTopicRuntime,
|
NodeRuntime, OverlayTunCounters, OverlayTunRuntime, PipeRuntime, PubsubGossipTopicRuntime,
|
||||||
PubsubRuntime,
|
PubsubRuntime,
|
||||||
|
|
@ -223,12 +228,6 @@ struct PipeUnixConnectWire {
|
||||||
bearer_proof: Option<BearerProof>,
|
bearer_proof: Option<BearerProof>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeerControlCaller {
|
|
||||||
peer_card: PeerCard,
|
|
||||||
remote_endpoint_id: String,
|
|
||||||
store: Store,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct InitOwnerOptions {
|
pub struct InitOwnerOptions {
|
||||||
pub admin_key_path: Option<PathBuf>,
|
pub admin_key_path: Option<PathBuf>,
|
||||||
|
|
@ -4064,21 +4063,24 @@ async fn handle_iroh_control_connection(
|
||||||
.await
|
.await
|
||||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||||
let remote_endpoint_id = conn.remote_id().to_string();
|
let remote_endpoint_id = conn.remote_id().to_string();
|
||||||
|
let route = classify_iroh_alpn(conn.alpn());
|
||||||
let alpn = display_alpn(conn.alpn());
|
let alpn = display_alpn(conn.alpn());
|
||||||
let log_remote_endpoint_id = remote_endpoint_id.clone();
|
let log_remote_endpoint_id = remote_endpoint_id.clone();
|
||||||
let log_alpn = alpn.clone();
|
let log_alpn = alpn.clone();
|
||||||
tracing::debug!(remote_endpoint_id = %log_remote_endpoint_id, alpn = %log_alpn, "iroh connection established");
|
tracing::debug!(remote_endpoint_id = %log_remote_endpoint_id, alpn = %log_alpn, "iroh connection established");
|
||||||
if alpn == display_alpn(iroh_blobs::ALPN) {
|
match route {
|
||||||
|
IrohAlpnRoute::NativeBlobs => {
|
||||||
let blob_store = iroh_blob_store(&node)?.ok_or_else(|| {
|
let blob_store = iroh_blob_store(&node)?.ok_or_else(|| {
|
||||||
NodeError::IrohPeer("iroh-blobs ALPN accepted but blob store is unavailable".to_owned())
|
NodeError::IrohPeer(
|
||||||
|
"iroh-blobs ALPN accepted but blob store is unavailable".to_owned(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
let protocol = iroh_blobs::BlobsProtocol::new(&blob_store, None);
|
let protocol = iroh_blobs::BlobsProtocol::new(&blob_store, None);
|
||||||
return protocol
|
return protocol.accept(conn).await.map_err(|error| {
|
||||||
.accept(conn)
|
NodeError::IrohPeer(format!("iroh-blobs accept failed: {error}"))
|
||||||
.await
|
});
|
||||||
.map_err(|error| NodeError::IrohPeer(format!("iroh-blobs accept failed: {error}")));
|
|
||||||
}
|
}
|
||||||
if alpn == display_alpn(iroh_docs::ALPN) {
|
IrohAlpnRoute::NativeDocs => {
|
||||||
let docs = iroh_docs(&node)?.ok_or_else(|| {
|
let docs = iroh_docs(&node)?.ok_or_else(|| {
|
||||||
NodeError::IrohPeer(
|
NodeError::IrohPeer(
|
||||||
"iroh-docs ALPN accepted but docs runtime is unavailable".to_owned(),
|
"iroh-docs ALPN accepted but docs runtime is unavailable".to_owned(),
|
||||||
|
|
@ -4089,16 +4091,20 @@ async fn handle_iroh_control_connection(
|
||||||
.await
|
.await
|
||||||
.map_err(|error| NodeError::IrohPeer(format!("iroh-docs accept failed: {error}")));
|
.map_err(|error| NodeError::IrohPeer(format!("iroh-docs accept failed: {error}")));
|
||||||
}
|
}
|
||||||
if alpn == display_alpn(iroh_gossip::ALPN) {
|
IrohAlpnRoute::NativeGossip => {
|
||||||
let gossip = iroh_gossip(&node)?.ok_or_else(|| {
|
let gossip = iroh_gossip(&node)?.ok_or_else(|| {
|
||||||
NodeError::IrohPeer(
|
NodeError::IrohPeer(
|
||||||
"iroh-gossip ALPN accepted but gossip runtime is unavailable".to_owned(),
|
"iroh-gossip ALPN accepted but gossip runtime is unavailable".to_owned(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
return gossip
|
return gossip.accept(conn).await.map_err(|error| {
|
||||||
.accept(conn)
|
NodeError::IrohPeer(format!("iroh-gossip accept failed: {error}"))
|
||||||
.await
|
});
|
||||||
.map_err(|error| NodeError::IrohPeer(format!("iroh-gossip accept failed: {error}")));
|
}
|
||||||
|
IrohAlpnRoute::SshProxy
|
||||||
|
| IrohAlpnRoute::Pipe
|
||||||
|
| IrohAlpnRoute::Overlay
|
||||||
|
| IrohAlpnRoute::Control => {}
|
||||||
}
|
}
|
||||||
tracing::debug!(%remote_endpoint_id, %alpn, "waiting for iroh bidirectional stream");
|
tracing::debug!(%remote_endpoint_id, %alpn, "waiting for iroh bidirectional stream");
|
||||||
let (mut send, recv) = conn
|
let (mut send, recv) = conn
|
||||||
|
|
@ -4106,15 +4112,21 @@ async fn handle_iroh_control_connection(
|
||||||
.await
|
.await
|
||||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||||
tracing::debug!(%remote_endpoint_id, %alpn, "accepted iroh bidirectional stream");
|
tracing::debug!(%remote_endpoint_id, %alpn, "accepted iroh bidirectional stream");
|
||||||
if alpn == display_alpn(geth_iroh::ALPN_SSH_PROXY) {
|
match route {
|
||||||
|
IrohAlpnRoute::SshProxy => {
|
||||||
return handle_ssh_proxy_wire_connection(node, remote_endpoint_id, send, recv).await;
|
return handle_ssh_proxy_wire_connection(node, remote_endpoint_id, send, recv).await;
|
||||||
}
|
}
|
||||||
if alpn == display_alpn(geth_iroh::ALPN_PIPE) {
|
IrohAlpnRoute::Pipe => {
|
||||||
return handle_pipe_wire_connection(node, remote_endpoint_id, send, recv).await;
|
return handle_pipe_wire_connection(node, remote_endpoint_id, send, recv).await;
|
||||||
}
|
}
|
||||||
if alpn == display_alpn(geth_iroh::ALPN_OVERLAY) {
|
IrohAlpnRoute::Overlay => {
|
||||||
return handle_overlay_wire_connection(node, remote_endpoint_id, send, recv).await;
|
return handle_overlay_wire_connection(node, remote_endpoint_id, send, recv).await;
|
||||||
}
|
}
|
||||||
|
IrohAlpnRoute::Control => {}
|
||||||
|
IrohAlpnRoute::NativeBlobs | IrohAlpnRoute::NativeDocs | IrohAlpnRoute::NativeGossip => {
|
||||||
|
unreachable!("native ALPN routes return before bidirectional stream handling")
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut recv = recv;
|
let mut recv = recv;
|
||||||
let request_line = read_iroh_line(&mut recv, PEER_CONTROL_LINE_MAX).await?;
|
let request_line = read_iroh_line(&mut recv, PEER_CONTROL_LINE_MAX).await?;
|
||||||
let request_bytes = request_line.len();
|
let request_bytes = request_line.len();
|
||||||
|
|
@ -5470,75 +5482,6 @@ async fn handle_pipe_unix_wire_connection(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iroh_node_addr_from_candidate(
|
|
||||||
candidate: &EndpointCandidate,
|
|
||||||
) -> Result<iroh::EndpointAddr, NodeError> {
|
|
||||||
let endpoint_id = candidate
|
|
||||||
.endpoint_id
|
|
||||||
.parse::<iroh::EndpointId>()
|
|
||||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
|
||||||
let direct_addresses = candidate
|
|
||||||
.direct_addresses
|
|
||||||
.iter()
|
|
||||||
.map(|addr| {
|
|
||||||
addr.parse::<std::net::SocketAddr>()
|
|
||||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
let mut node_addr = iroh::EndpointAddr::new(endpoint_id);
|
|
||||||
for address in direct_addresses {
|
|
||||||
node_addr = node_addr.with_ip_addr(address);
|
|
||||||
}
|
|
||||||
if let Some(relay_url) = &candidate.relay_url {
|
|
||||||
node_addr = node_addr.with_relay_url(
|
|
||||||
relay_url
|
|
||||||
.parse::<iroh::RelayUrl>()
|
|
||||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(node_addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_peer_card_matches_endpoint(card: &PeerCard, endpoint_id: &str) -> Result<(), NodeError> {
|
|
||||||
if card
|
|
||||||
.endpoints
|
|
||||||
.iter()
|
|
||||||
.any(|candidate| candidate.endpoint_id == endpoint_id)
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(NodeError::IrohPeer(format!(
|
|
||||||
"signed peer card for {} does not bind Iroh endpoint {}",
|
|
||||||
card.node_id, endpoint_id
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn authenticate_peer_control_caller(
|
|
||||||
node: &LocalNode,
|
|
||||||
peer_card: PeerCard,
|
|
||||||
remote_endpoint_id: &str,
|
|
||||||
) -> Result<PeerControlCaller, NodeError> {
|
|
||||||
peer_card.validate_candidate()?;
|
|
||||||
ensure_peer_card_matches_endpoint(&peer_card, remote_endpoint_id)?;
|
|
||||||
let discovered = DiscoveredPeer::candidate(
|
|
||||||
peer_card.clone(),
|
|
||||||
UnixMillis(geth_store::now_ms()),
|
|
||||||
DiscoverySource::PeerExchange,
|
|
||||||
)?;
|
|
||||||
let store = Store::open(&node.paths.metadata_db())?;
|
|
||||||
store.upsert_peer_card(&StoredPeerCard {
|
|
||||||
peer_id: peer_card.node_id.to_string(),
|
|
||||||
card_json: serde_json::to_string(&peer_card)?,
|
|
||||||
updated_at_ms: discovered.discovered_at.0,
|
|
||||||
})?;
|
|
||||||
Ok(PeerControlCaller {
|
|
||||||
peer_card,
|
|
||||||
remote_endpoint_id: remote_endpoint_id.to_owned(),
|
|
||||||
store,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peer_gossip_endpoint_id(
|
fn peer_gossip_endpoint_id(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
|
|
@ -5581,10 +5524,6 @@ fn restricted_admin_shell_output(node: &LocalNode, command: &str) -> Result<Stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_alpn(alpn: &[u8]) -> String {
|
|
||||||
String::from_utf8_lossy(alpn).into_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
request: ControlRequest,
|
request: ControlRequest,
|
||||||
|
|
|
||||||
151
crates/geth-node/src/peer_control.rs
Normal file
151
crates/geth-node/src/peer_control.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
//! Protected peer-control routing and caller authentication helpers.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub(crate) enum IrohAlpnRoute {
|
||||||
|
NativeBlobs,
|
||||||
|
NativeDocs,
|
||||||
|
NativeGossip,
|
||||||
|
SshProxy,
|
||||||
|
Pipe,
|
||||||
|
Overlay,
|
||||||
|
Control,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct PeerControlCaller {
|
||||||
|
pub(crate) peer_card: PeerCard,
|
||||||
|
pub(crate) remote_endpoint_id: String,
|
||||||
|
pub(crate) store: Store,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn classify_iroh_alpn(alpn: &[u8]) -> IrohAlpnRoute {
|
||||||
|
if alpn == iroh_blobs::ALPN {
|
||||||
|
IrohAlpnRoute::NativeBlobs
|
||||||
|
} else if alpn == iroh_docs::ALPN {
|
||||||
|
IrohAlpnRoute::NativeDocs
|
||||||
|
} else if alpn == iroh_gossip::ALPN {
|
||||||
|
IrohAlpnRoute::NativeGossip
|
||||||
|
} else if alpn == geth_iroh::ALPN_SSH_PROXY {
|
||||||
|
IrohAlpnRoute::SshProxy
|
||||||
|
} else if alpn == geth_iroh::ALPN_PIPE {
|
||||||
|
IrohAlpnRoute::Pipe
|
||||||
|
} else if alpn == geth_iroh::ALPN_OVERLAY {
|
||||||
|
IrohAlpnRoute::Overlay
|
||||||
|
} else {
|
||||||
|
IrohAlpnRoute::Control
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn display_alpn(alpn: &[u8]) -> String {
|
||||||
|
String::from_utf8_lossy(alpn).into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iroh_node_addr_from_candidate(
|
||||||
|
candidate: &EndpointCandidate,
|
||||||
|
) -> Result<iroh::EndpointAddr, NodeError> {
|
||||||
|
let endpoint_id = candidate
|
||||||
|
.endpoint_id
|
||||||
|
.parse::<iroh::EndpointId>()
|
||||||
|
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||||
|
let direct_addresses = candidate
|
||||||
|
.direct_addresses
|
||||||
|
.iter()
|
||||||
|
.map(|addr| {
|
||||||
|
addr.parse::<std::net::SocketAddr>()
|
||||||
|
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let mut node_addr = iroh::EndpointAddr::new(endpoint_id);
|
||||||
|
for address in direct_addresses {
|
||||||
|
node_addr = node_addr.with_ip_addr(address);
|
||||||
|
}
|
||||||
|
if let Some(relay_url) = &candidate.relay_url {
|
||||||
|
node_addr = node_addr.with_relay_url(
|
||||||
|
relay_url
|
||||||
|
.parse::<iroh::RelayUrl>()
|
||||||
|
.map_err(|error| NodeError::IrohPeer(error.to_string()))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(node_addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ensure_peer_card_matches_endpoint(
|
||||||
|
card: &PeerCard,
|
||||||
|
endpoint_id: &str,
|
||||||
|
) -> Result<(), NodeError> {
|
||||||
|
if card
|
||||||
|
.endpoints
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| candidate.endpoint_id == endpoint_id)
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(NodeError::IrohPeer(format!(
|
||||||
|
"signed peer card for {} does not bind Iroh endpoint {}",
|
||||||
|
card.node_id, endpoint_id
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn authenticate_peer_control_caller(
|
||||||
|
node: &LocalNode,
|
||||||
|
peer_card: PeerCard,
|
||||||
|
remote_endpoint_id: &str,
|
||||||
|
) -> Result<PeerControlCaller, NodeError> {
|
||||||
|
peer_card.validate_candidate()?;
|
||||||
|
ensure_peer_card_matches_endpoint(&peer_card, remote_endpoint_id)?;
|
||||||
|
let discovered = DiscoveredPeer::candidate(
|
||||||
|
peer_card.clone(),
|
||||||
|
UnixMillis(geth_store::now_ms()),
|
||||||
|
DiscoverySource::PeerExchange,
|
||||||
|
)?;
|
||||||
|
let store = Store::open(&node.paths.metadata_db())?;
|
||||||
|
store.upsert_peer_card(&StoredPeerCard {
|
||||||
|
peer_id: peer_card.node_id.to_string(),
|
||||||
|
card_json: serde_json::to_string(&peer_card)?,
|
||||||
|
updated_at_ms: discovered.discovered_at.0,
|
||||||
|
})?;
|
||||||
|
Ok(PeerControlCaller {
|
||||||
|
peer_card,
|
||||||
|
remote_endpoint_id: remote_endpoint_id.to_owned(),
|
||||||
|
store,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classifies_geth_and_native_alpns() {
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(iroh_blobs::ALPN),
|
||||||
|
IrohAlpnRoute::NativeBlobs
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(iroh_docs::ALPN),
|
||||||
|
IrohAlpnRoute::NativeDocs
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(iroh_gossip::ALPN),
|
||||||
|
IrohAlpnRoute::NativeGossip
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(geth_iroh::ALPN_SSH_PROXY),
|
||||||
|
IrohAlpnRoute::SshProxy
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(geth_iroh::ALPN_PIPE),
|
||||||
|
IrohAlpnRoute::Pipe
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(geth_iroh::ALPN_OVERLAY),
|
||||||
|
IrohAlpnRoute::Overlay
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_iroh_alpn(geth_iroh::ALPN_CONTROL),
|
||||||
|
IrohAlpnRoute::Control
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,12 +11,14 @@ Within `geth-node`, daemon lifecycle code is separated from feature handlers:
|
||||||
signal handling, Iroh endpoint startup, the Iroh accept loop, and background
|
signal handling, Iroh endpoint startup, the Iroh accept loop, and background
|
||||||
live-sync task spawning. `local_control.rs` owns async local `ControlRequest`
|
live-sync task spawning. `local_control.rs` owns async local `ControlRequest`
|
||||||
routing, safe trace-field classification, and named peer/resource/local handler
|
routing, safe trace-field classification, and named peer/resource/local handler
|
||||||
families before delegating to feature implementations. `resource_contracts.rs`
|
families before delegating to feature implementations. `peer_control.rs`
|
||||||
records the review boundary for each resource family: resource ID patterns,
|
centralizes inbound Iroh ALPN classification, peer endpoint-address parsing,
|
||||||
capabilities, and mutation or host-access points. Runtime registries for
|
signed peer-card endpoint binding validation, and authenticated caller context
|
||||||
pubsub, pipes, and overlays live behind narrow mutex-protected structs in
|
creation for protected peer-control, pipe, SSH-proxy, and overlay paths.
|
||||||
`runtime.rs`. Protected peer-control ALPN dispatch remains a separate refactor
|
`resource_contracts.rs` records the review boundary for each resource family:
|
||||||
target.
|
resource ID patterns, capabilities, and mutation or host-access points. Runtime
|
||||||
|
registries for pubsub, pipes, and overlays live behind narrow mutex-protected
|
||||||
|
structs in `runtime.rs`.
|
||||||
|
|
||||||
The local metadata store is SQLite product state. `geth-store` tracks a numeric
|
The local metadata store is SQLite product state. `geth-store` tracks a numeric
|
||||||
`schema_version` in the `meta` table and applies ordered migrations up to the
|
`schema_version` in the `meta` table and applies ordered migrations up to the
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,13 @@ behavior.
|
||||||
- `[x]` Each command family has a small handler module or function group.
|
- `[x]` Each command family has a small handler module or function group.
|
||||||
- `[x]` Local-only behavior remains covered by existing integration tests.
|
- `[x]` Local-only behavior remains covered by existing integration tests.
|
||||||
|
|
||||||
- `[~]` Extract protected peer-control routing.
|
- `[x]` Extract protected peer-control routing.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
- `[x]` Shared bounded Iroh line-read and send-finish helpers live outside
|
- `[x]` Shared bounded Iroh line-read and send-finish helpers live outside
|
||||||
the main feature handler module.
|
the main feature handler module.
|
||||||
- `[x]` Outbound peer-control, pipe-wire, and overlay-wire request helpers
|
- `[x]` Outbound peer-control, pipe-wire, and overlay-wire request helpers
|
||||||
live outside the main feature handler module.
|
live outside the main feature handler module.
|
||||||
- `[~]` Iroh control ALPN handling, nonce checks, peer-card validation, and
|
- `[x]` Iroh control ALPN handling, nonce checks, peer-card validation, and
|
||||||
endpoint-binding validation are centralized.
|
endpoint-binding validation are centralized.
|
||||||
- `[x]` Feature handlers receive authenticated caller context rather than
|
- `[x]` Feature handlers receive authenticated caller context rather than
|
||||||
repeating peer-card boilerplate.
|
repeating peer-card boilerplate.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue