Scaffold the federated services daemon
Protocol-agnostic core: a Protocol trait maps raw server-to-server lines to a normalized event/action model, so the engine never touches a raw line and a new ircd is one new module. Adds event-log state (state is a fold over the log) and a service framework. First protocol impl links to an InspIRCd (insp4) uplink — completes the CAPAB/SERVER handshake, bursts, and introduces NickServ.
This commit is contained in:
commit
f82ab0c70a
15 changed files with 890 additions and 0 deletions
61
src/engine/mod.rs
Normal file
61
src/engine/mod.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
pub mod service;
|
||||
pub mod state;
|
||||
|
||||
use crate::proto::{NetAction, NetEvent};
|
||||
use service::{Service, ServiceCtx};
|
||||
use state::{EventLog, Network};
|
||||
|
||||
pub struct Engine {
|
||||
services: Vec<Box<dyn Service>>,
|
||||
#[allow(dead_code)]
|
||||
network: Network,
|
||||
#[allow(dead_code)]
|
||||
log: EventLog,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(services: Vec<Box<dyn Service>>) -> Self {
|
||||
Self {
|
||||
services,
|
||||
network: Network::default(),
|
||||
log: EventLog::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Sent right after the SERVER line: burst, introduce every service, endburst.
|
||||
pub fn startup_actions(&self) -> Vec<NetAction> {
|
||||
let mut out = vec![NetAction::Burst];
|
||||
for svc in &self.services {
|
||||
out.push(NetAction::IntroduceUser {
|
||||
uid: svc.uid().to_string(),
|
||||
nick: svc.nick().to_string(),
|
||||
ident: "services".to_string(),
|
||||
host: svc.host().to_string(),
|
||||
gecos: svc.gecos().to_string(),
|
||||
});
|
||||
}
|
||||
out.push(NetAction::EndBurst);
|
||||
out
|
||||
}
|
||||
|
||||
pub fn handle(&mut self, event: NetEvent) -> Vec<NetAction> {
|
||||
match event {
|
||||
NetEvent::Ping { token, from } => vec![NetAction::Pong { token, from }],
|
||||
NetEvent::Privmsg { from, to, text } => self.dispatch(&from, &to, &text),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Route a PRIVMSG addressed to a service (by uid or nick) into that service.
|
||||
fn dispatch(&mut self, from: &str, to: &str, text: &str) -> Vec<NetAction> {
|
||||
let mut ctx = ServiceCtx::default();
|
||||
for svc in self.services.iter_mut() {
|
||||
if to.eq_ignore_ascii_case(svc.uid()) || to.eq_ignore_ascii_case(svc.nick()) {
|
||||
let args: Vec<&str> = text.split_whitespace().collect();
|
||||
svc.on_command(from, &args, &mut ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.actions
|
||||
}
|
||||
}
|
||||
29
src/engine/service.rs
Normal file
29
src/engine/service.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::proto::NetAction;
|
||||
|
||||
// A pseudo-client (NickServ, ChanServ, OperServ, ...). Introduced at burst,
|
||||
// receives the commands users message it, and pushes actions onto the ctx.
|
||||
pub trait Service: Send {
|
||||
fn nick(&self) -> &str;
|
||||
fn uid(&self) -> &str;
|
||||
fn host(&self) -> &str {
|
||||
"services.local"
|
||||
}
|
||||
fn gecos(&self) -> &str;
|
||||
fn on_command(&mut self, from: &str, args: &[&str], ctx: &mut ServiceCtx);
|
||||
}
|
||||
|
||||
// What a service can do in response to a command, collected for the link to flush.
|
||||
#[derive(Default)]
|
||||
pub struct ServiceCtx {
|
||||
pub actions: Vec<NetAction>,
|
||||
}
|
||||
|
||||
impl ServiceCtx {
|
||||
pub fn notice(&mut self, from: &str, to: &str, text: impl Into<String>) {
|
||||
self.actions.push(NetAction::Notice {
|
||||
from: from.to_string(),
|
||||
to: to.to_string(),
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
39
src/engine/state.rs
Normal file
39
src/engine/state.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
// Live network view the services reason about.
|
||||
#[derive(Default)]
|
||||
pub struct Network {
|
||||
pub users: HashMap<String, User>, // keyed by UID
|
||||
pub channels: HashMap<String, Channel>,
|
||||
}
|
||||
|
||||
pub struct User {
|
||||
pub uid: String,
|
||||
pub nick: String,
|
||||
pub account: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Channel {
|
||||
pub name: String,
|
||||
pub ts: u64,
|
||||
}
|
||||
|
||||
// The Sable-inspired core: every persistent change is an Event, and state is a
|
||||
// fold over the log. Single-node today; replicating this log across service
|
||||
// nodes is what turns it federated later, without rewriting the services.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
AccountRegistered { account: String, uid: String },
|
||||
ChannelRegistered { channel: String, founder: String },
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EventLog {
|
||||
pub events: Vec<Event>,
|
||||
}
|
||||
|
||||
impl EventLog {
|
||||
pub fn append(&mut self, event: Event) {
|
||||
self.events.push(event);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue