Add Iroh protocol router scaffold
This commit is contained in:
parent
0f2ad755d9
commit
cf5a405276
4 changed files with 170 additions and 16 deletions
|
|
@ -1,4 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{SocketAddrV4, SocketAddrV6};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
|
@ -36,11 +37,123 @@ impl GethIrohConfig {
|
|||
relay_mode,
|
||||
bind_ipv4: None,
|
||||
bind_ipv6: None,
|
||||
alpns: all_alpns(),
|
||||
alpns: default_protocol_router().alpns(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ProtocolKind {
|
||||
Control,
|
||||
Kv,
|
||||
Cas,
|
||||
Pubsub,
|
||||
Pipe,
|
||||
Db,
|
||||
Document,
|
||||
SshProxy,
|
||||
}
|
||||
|
||||
impl ProtocolKind {
|
||||
#[must_use]
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Control => "control",
|
||||
Self::Kv => "kv",
|
||||
Self::Cas => "cas",
|
||||
Self::Pubsub => "pubsub",
|
||||
Self::Pipe => "pipe",
|
||||
Self::Db => "db",
|
||||
Self::Document => "document",
|
||||
Self::SshProxy => "ssh-proxy",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProtocolDescriptor {
|
||||
pub kind: ProtocolKind,
|
||||
pub name: String,
|
||||
pub alpn: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ProtocolDescriptor {
|
||||
#[must_use]
|
||||
pub fn new(kind: ProtocolKind, alpn: &'static [u8]) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
name: kind.name().to_owned(),
|
||||
alpn: alpn.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ProtocolRouter {
|
||||
protocols: BTreeMap<Vec<u8>, ProtocolDescriptor>,
|
||||
}
|
||||
|
||||
impl ProtocolRouter {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn register(&mut self, descriptor: ProtocolDescriptor) -> Result<(), RouterError> {
|
||||
if self.protocols.contains_key(&descriptor.alpn) {
|
||||
return Err(RouterError::DuplicateAlpn {
|
||||
alpn: display_alpn(&descriptor.alpn),
|
||||
});
|
||||
}
|
||||
self.protocols.insert(descriptor.alpn.clone(), descriptor);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require(&self, alpn: &[u8]) -> Result<&ProtocolDescriptor, RouterError> {
|
||||
self.protocols
|
||||
.get(alpn)
|
||||
.ok_or_else(|| RouterError::UnknownAlpn {
|
||||
alpn: display_alpn(alpn),
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn alpns(&self) -> Vec<Vec<u8>> {
|
||||
self.protocols.keys().cloned().collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn descriptors(&self) -> Vec<ProtocolDescriptor> {
|
||||
self.protocols.values().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_protocol_router() -> ProtocolRouter {
|
||||
let mut router = ProtocolRouter::new();
|
||||
for descriptor in default_protocol_descriptors() {
|
||||
router
|
||||
.register(descriptor)
|
||||
.expect("default geth ALPNs are unique");
|
||||
}
|
||||
router
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_protocol_descriptors() -> Vec<ProtocolDescriptor> {
|
||||
vec![
|
||||
ProtocolDescriptor::new(ProtocolKind::Control, ALPN_CONTROL),
|
||||
ProtocolDescriptor::new(ProtocolKind::Kv, ALPN_KV),
|
||||
ProtocolDescriptor::new(ProtocolKind::Cas, ALPN_CAS),
|
||||
ProtocolDescriptor::new(ProtocolKind::Pubsub, ALPN_PUBSUB),
|
||||
ProtocolDescriptor::new(ProtocolKind::Pipe, ALPN_PIPE),
|
||||
ProtocolDescriptor::new(ProtocolKind::Db, ALPN_DB),
|
||||
ProtocolDescriptor::new(ProtocolKind::Document, ALPN_DOCUMENT),
|
||||
ProtocolDescriptor::new(ProtocolKind::SshProxy, ALPN_SSH_PROXY),
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum GethRelayMode {
|
||||
|
|
@ -142,19 +255,7 @@ pub fn load_secret_key(path: &Path) -> Result<iroh::SecretKey, IrohError> {
|
|||
|
||||
#[must_use]
|
||||
pub fn all_alpns() -> Vec<Vec<u8>> {
|
||||
[
|
||||
ALPN_CONTROL,
|
||||
ALPN_KV,
|
||||
ALPN_CAS,
|
||||
ALPN_PUBSUB,
|
||||
ALPN_PIPE,
|
||||
ALPN_DB,
|
||||
ALPN_DOCUMENT,
|
||||
ALPN_SSH_PROXY,
|
||||
]
|
||||
.into_iter()
|
||||
.map(<[u8]>::to_vec)
|
||||
.collect()
|
||||
default_protocol_router().alpns()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -189,12 +290,24 @@ pub enum IrohError {
|
|||
Bind(Box<iroh::endpoint::BindError>),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RouterError {
|
||||
#[error("duplicate geth ALPN registration: {alpn}")]
|
||||
DuplicateAlpn { alpn: String },
|
||||
#[error("unknown geth ALPN: {alpn}")]
|
||||
UnknownAlpn { alpn: String },
|
||||
}
|
||||
|
||||
impl From<iroh::endpoint::BindError> for IrohError {
|
||||
fn from(error: iroh::endpoint::BindError) -> Self {
|
||||
Self::Bind(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
fn display_alpn(alpn: &[u8]) -> String {
|
||||
String::from_utf8(alpn.to_vec()).unwrap_or_else(|_| format!("0x{}", hex::encode(alpn)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -208,6 +321,41 @@ mod tests {
|
|||
assert_eq!(alpns.len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_router_registers_all_protocols() {
|
||||
let router = default_protocol_router();
|
||||
assert_eq!(
|
||||
router.require(ALPN_CONTROL).expect("control").kind,
|
||||
ProtocolKind::Control
|
||||
);
|
||||
assert_eq!(
|
||||
router.require(ALPN_SSH_PROXY).expect("ssh proxy").kind,
|
||||
ProtocolKind::SshProxy
|
||||
);
|
||||
assert_eq!(router.descriptors().len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_rejects_duplicate_alpns() {
|
||||
let mut router = ProtocolRouter::new();
|
||||
router
|
||||
.register(ProtocolDescriptor::new(ProtocolKind::Control, ALPN_CONTROL))
|
||||
.expect("first registration");
|
||||
let error = router
|
||||
.register(ProtocolDescriptor::new(ProtocolKind::Cas, ALPN_CONTROL))
|
||||
.expect_err("duplicate error");
|
||||
assert!(matches!(error, RouterError::DuplicateAlpn { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_rejects_unknown_alpn() {
|
||||
let router = default_protocol_router();
|
||||
let error = router
|
||||
.require(b"/geth/unknown/1")
|
||||
.expect_err("unknown error");
|
||||
assert!(matches!(error, RouterError::UnknownAlpn { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iroh_secret_key_persists() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
|
|
|
|||
Loading…
Reference in a new issue