use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, MutexGuard};
use kernel::event::Who;
use kernel::event::record::{DeviceRevoked, RemoteClosed, RemoteClosing, RemoteOpened};
use kernel::{AxCode, AxError, EventDraft, EventKind, Ledger, Payload, RunId, TimeMs};
use remote_access::door::{Device, DeviceName, Door, Epoch};
use remote_access::handshake::NONCE_BYTES;
use remote_access::keys::SigningKey;
use remote_access::route::{Opened, Route};
use super::console::Lasting;
mod city_key;
mod pairing;
mod sessions;
pub(crate) use city_key::CityKey;
pub(crate) use pairing::Inviting;
pub(crate) type Clock = Arc<dyn Fn() -> Result<TimeMs, AxError> + Send + Sync>;
pub(crate) type Entropy = Arc<dyn Fn(&mut [u8]) -> Result<(), AxError> + Send + Sync>;
pub(crate) type Choosing = Box<dyn FnMut() -> Result<Box<dyn Route + Send>, AxError> + Send>;
#[derive(Clone)]
pub(crate) struct Senses {
pub(crate) clock: Clock,
pub(crate) entropy: Entropy,
}
pub(crate) struct Keeping {
pub(crate) devices: PathBuf,
pub(crate) ledger: Box<dyn Ledger + Send>,
pub(crate) choose: Choosing,
pub(crate) senses: Senses,
pub(crate) key: CityKey,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Revoking {
Named(String),
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Phase {
Open,
Closed,
}
#[derive(Clone)]
pub(crate) struct Doorway {
kept: Arc<Mutex<Kept>>,
senses: Senses,
}
struct Kept {
door: Door,
city: Result<SigningKey, AxError>,
key: CityKey,
devices: PathBuf,
ledger: Box<dyn Ledger + Send>,
choose: Choosing,
standing: Option<Standing>,
}
struct Standing {
route: Box<dyn Route + Send>,
opened: Opened,
listening: Option<Box<dyn Send>>,
}
impl Doorway {
pub(crate) fn keep(keeping: Keeping) -> Result<Doorway, AxError> {
let Keeping {
devices,
ledger,
choose,
senses,
key,
} = keeping;
let known = super::devices::read(&devices)?;
let city = key.held(&senses.entropy);
let door = Door::start(known, Epoch::from_entropy(drawn(&senses)?));
Ok(Doorway {
kept: Arc::new(Mutex::new(Kept {
door,
city,
key,
devices,
ledger,
choose,
standing: None,
})),
senses,
})
}
pub(crate) fn open(&self, local: SocketAddr, lasting: Lasting) -> Result<Opened, AxError> {
let now = (self.senses.clock)()?;
let epoch = Epoch::from_entropy(drawn(&self.senses)?);
let mut kept = self.kept()?;
if let Some(standing) = &kept.standing {
return Err(AxError::failure(
AxCode::Busy,
"open the remote door",
format!("it is open at {}", standing.opened.url.as_str()),
)
.with_recovery("close it first with `/remote close`, or leave it open"));
}
let mut route = (kept.choose)()?;
let opened = route.open(local)?;
let closes_at = TimeMs::new(now.value().saturating_add(lasting.ms()));
let line = RemoteOpened {
closes_at,
url: opened.url.as_str().to_owned(),
};
if let Err(refused) = kept.record(now, Who::Person, EventKind::RemoteOpened, &line) {
route.close()?;
return Err(refused);
}
kept.door.open(epoch, closes_at)?;
kept.standing = Some(Standing {
route,
opened: opened.clone(),
listening: None,
});
Ok(opened)
}
pub(crate) fn senses(&self) -> &Senses {
&self.senses
}
pub(crate) fn attend(&self, listening: Box<dyn Send>) -> Result<(), AxError> {
let mut kept = self.kept()?;
if let Some(standing) = kept.standing.as_mut() {
standing.listening = Some(listening);
}
Ok(())
}
pub(crate) fn close(&self, why: RemoteClosing) -> Result<Phase, AxError> {
let now = (self.senses.clock)()?;
self.kept()?.close(now, why)?;
Ok(Phase::Closed)
}
pub(crate) fn tick(&self) -> Result<Phase, AxError> {
let now = (self.senses.clock)()?;
let mut kept = self.kept()?;
match kept.door.closes_at() {
Some(closes_at) if now >= closes_at => {
kept.close(now, RemoteClosing::Expired)?;
Ok(Phase::Closed)
}
Some(_) => {
kept.door.tick(now);
Ok(Phase::Open)
}
None => Ok(Phase::Closed),
}
}
pub(crate) fn devices(&self) -> Result<Vec<Device>, AxError> {
Ok(self.kept()?.door.devices().to_vec())
}
pub(crate) fn revoke(&self, which: &Revoking) -> Result<Vec<Device>, AxError> {
let now = (self.senses.clock)()?;
let mut kept = self.kept()?;
let chosen: Vec<Device> = match which {
Revoking::All => kept.door.devices().to_vec(),
Revoking::Named(name) => {
let name = DeviceName::parse(name)?;
let named: Vec<Device> = kept
.door
.devices()
.iter()
.filter(|device| device.name == name)
.cloned()
.collect();
if named.is_empty() {
return Err(AxError::failure(
AxCode::InvalidArgs,
"revoke a device",
format!("no paired device is called `{}`", name.as_str()),
)
.with_recovery("list the paired devices with `/remote devices`"));
}
named
}
};
kept.revoked(now, chosen)
}
fn kept(&self) -> Result<MutexGuard<'_, Kept>, AxError> {
self.kept.lock().map_err(|_| {
AxError::failure(
AxCode::StorageFatal,
"reach the remote door",
"a thread ended in a panic while holding it",
)
.with_recovery(
"restart the city: the door starts closed and the device table is read again",
)
})
}
}
impl Kept {
fn revoked(&mut self, now: TimeMs, chosen: Vec<Device>) -> Result<Vec<Device>, AxError> {
for device in &chosen {
self.door.revoke(device.id)?;
}
super::devices::write(&self.devices, self.door.devices())?;
for device in &chosen {
let line = DeviceRevoked {
device: device.id.text(),
name: device.name.as_str().to_owned(),
};
self.record(now, Who::Person, EventKind::DeviceRevoked, &line)?;
}
Ok(chosen)
}
fn city(&self) -> Result<&SigningKey, AxError> {
self.city.as_ref().map_err(Clone::clone)
}
fn close(&mut self, now: TimeMs, why: RemoteClosing) -> Result<(), AxError> {
let Some(Standing {
mut route,
listening,
..
}) = self.standing.take()
else {
return Ok(());
};
self.door.close();
drop(listening);
let closed_route = route.close();
let who = match why {
RemoteClosing::Expired => Who::City,
RemoteClosing::Console | RemoteClosing::Locked => Who::Person,
};
self.record(now, who, EventKind::RemoteClosed, &RemoteClosed { why })?;
closed_route
}
fn require_open(&self, action: &str) -> Result<TimeMs, AxError> {
self.door.closes_at().ok_or_else(|| closed(action))
}
fn record<T: serde::Serialize>(
&mut self,
t: TimeMs,
who: Who,
kind: EventKind,
line: &T,
) -> Result<(), AxError> {
let draft = EventDraft {
run: RunId::CITY,
t,
who: who.to_string(),
addr: None,
kind,
data: Payload::of(line)?,
ig: false,
};
self.ledger.append(draft).map(drop)
}
}
fn drawn(senses: &Senses) -> Result<[u8; 16], AxError> {
let mut bytes = [0u8; 16];
(senses.entropy)(&mut bytes)?;
Ok(bytes)
}
fn nonce(senses: &Senses) -> Result<[u8; NONCE_BYTES], AxError> {
let mut bytes = [0u8; NONCE_BYTES];
(senses.entropy)(&mut bytes)?;
Ok(bytes)
}
fn closed(action: &str) -> AxError {
AxError::failure(AxCode::GateDenied, action, "the remote door is closed")
.with_recovery("open it on the city's console with `/remote open`")
}