- schema: createChannel returns wasNew :Bool to elect the MLS initiator unambiguously; prevents duplicate group creation on concurrent /dm calls - core: group helpers for epoch tracking and key-package lifecycle - server: federation subsystem — mTLS QUIC server-to-server relay with Cap'n Proto RPC; enqueue/batchEnqueue relay unknown recipients to their home domain via FederationClient - server: mDNS _quicproquo._udp.local. service announcement on startup - server: storage + sql_store — identity_exists, peek/ack, federation home-server lookup helpers - client: /mesh peers REPL command (mDNS discovery, feature = "mesh") - client: MeshDiscovery — background mDNS browse with ServiceDaemon - client: was_new=false path in cmd_dm waits for peer Welcome instead of creating a duplicate initiator group - p2p: fix ALPN from quicnprotochat/p2p/1 → quicproquo/p2p/1 - workspace: re-include quicproquo-p2p in members
81 lines
2.3 KiB
Rust
81 lines
2.3 KiB
Rust
//! Parse `username@domain` federated addresses.
|
|
//!
|
|
//! A bare `username` (no `@`) is treated as local.
|
|
|
|
#![allow(dead_code)] // federation not yet wired up
|
|
|
|
/// A parsed federated address.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct FederatedAddress {
|
|
pub username: String,
|
|
pub domain: Option<String>,
|
|
}
|
|
|
|
impl FederatedAddress {
|
|
/// Parse a `user@domain` string. Bare `user` → domain is `None`.
|
|
pub fn parse(input: &str) -> Self {
|
|
// Split on the *last* '@' so usernames can contain '@' in theory.
|
|
match input.rsplit_once('@') {
|
|
Some((user, domain)) if !domain.is_empty() && !user.is_empty() => Self {
|
|
username: user.to_string(),
|
|
domain: Some(domain.to_string()),
|
|
},
|
|
_ => Self {
|
|
username: input.to_string(),
|
|
domain: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Returns true if this address refers to a local user (no domain or domain matches local).
|
|
pub fn is_local(&self, local_domain: &str) -> bool {
|
|
match &self.domain {
|
|
None => true,
|
|
Some(d) => d == local_domain,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn bare_username() {
|
|
let addr = FederatedAddress::parse("alice");
|
|
assert_eq!(addr.username, "alice");
|
|
assert_eq!(addr.domain, None);
|
|
assert!(addr.is_local("example.com"));
|
|
}
|
|
|
|
#[test]
|
|
fn user_at_domain() {
|
|
let addr = FederatedAddress::parse("alice@remote.example.com");
|
|
assert_eq!(addr.username, "alice");
|
|
assert_eq!(addr.domain, Some("remote.example.com".into()));
|
|
assert!(!addr.is_local("local.example.com"));
|
|
assert!(addr.is_local("remote.example.com"));
|
|
}
|
|
|
|
#[test]
|
|
fn trailing_at_is_bare() {
|
|
let addr = FederatedAddress::parse("alice@");
|
|
assert_eq!(addr.username, "alice@");
|
|
assert_eq!(addr.domain, None);
|
|
}
|
|
|
|
#[test]
|
|
fn leading_at_is_bare() {
|
|
let addr = FederatedAddress::parse("@domain.com");
|
|
assert_eq!(addr.username, "@domain.com");
|
|
assert_eq!(addr.domain, None);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_at_uses_last() {
|
|
let addr = FederatedAddress::parse("user@org@domain.com");
|
|
assert_eq!(addr.username, "user@org");
|
|
assert_eq!(addr.domain, Some("domain.com".into()));
|
|
}
|
|
}
|