use std::num::NonZeroU32;
use serde::Serialize;
use super::error::ConsoleError;
use super::wall::WallBase;
use crate::tap::{self, TapIdent, TapOutcome, TapSource};
pub const RECORD_VERSION: u64 = 1;
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Device {
pub id: String,
pub name: String,
pub kind: DeviceKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub liminal: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub started: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub started_fault: Option<String>,
#[serde(rename = "box", skip_serializing_if = "Option::is_none")]
pub zone: Option<DeviceBox>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub struct DeviceBox {
pub x: i64,
pub y: i64,
pub w: i64,
pub h: i64,
}
impl DeviceBox {
pub const THIS: Self = Self {
x: -140,
y: 60,
w: 1340,
h: 520,
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DeviceKind {
Device,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Station {
pub id: String,
pub name: String,
pub device: String,
pub kind: StationKind,
pub state: StationState,
#[serde(skip_serializing_if = "Option::is_none")]
pub subjects: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activities: Option<Vec<StationActivity>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct StationActivity {
pub name: String,
pub input_schema_json: String,
pub output_schema_json: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum StationKind {
Service,
Worker,
Depot,
Exchange,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum StationState {
Live,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Line {
pub id: String,
pub kind: LineKind,
pub life: LineLife,
pub state: LineState,
pub stops: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LineKind {
Stream,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LineLife {
Passing,
Kept,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LineState {
Live,
Quiesced,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Link {
pub id: String,
pub name: String,
pub kind: LinkKind,
pub state: LinkState,
pub rtt_ms: Option<u64>,
pub ends: [String; 2],
}
impl Link {
#[must_use]
pub fn to_peer(local: &str, peer: &str) -> Self {
let ends = if peer < local {
[peer.to_owned(), local.to_owned()]
} else {
[local.to_owned(), peer.to_owned()]
};
Self {
id: format!("{local}~{peer}"),
name: peer.to_owned(),
kind: LinkKind::SameNetwork,
state: LinkState::Up,
rtt_ms: None,
ends,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum LinkKind {
#[serde(rename = "same network")]
SameNetwork,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LinkState {
Up,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Connection {
pub id: String,
pub mount: Mount,
#[serde(skip_serializing_if = "Option::is_none")]
pub participant: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Mount {
Tcp,
Websocket,
Loopback,
}
impl From<crate::server::mount::MountKind> for Mount {
fn from(mount: crate::server::mount::MountKind) -> Self {
match mount {
crate::server::mount::MountKind::Tcp => Self::Tcp,
crate::server::mount::MountKind::WebSocket => Self::Websocket,
crate::server::mount::MountKind::Loopback => Self::Loopback,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TopologyDocument {
v: u64,
#[serde(rename = "type")]
record_type: &'static str,
pub rev: u64,
pub this_device: String,
pub devices: Vec<Device>,
pub stations: Vec<Station>,
pub lines: Vec<Line>,
pub links: Vec<Link>,
pub connections: Vec<Connection>,
}
impl TopologyDocument {
#[must_use]
pub const fn new(
rev: u64,
this_device: String,
devices: Vec<Device>,
stations: Vec<Station>,
lines: Vec<Line>,
peer_links: Vec<Link>,
connections: Vec<Connection>,
) -> Self {
Self {
v: RECORD_VERSION,
record_type: "topology",
rev,
this_device,
devices,
stations,
lines,
links: peer_links,
connections,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Outcome {
Delivered,
Nobody,
Unaccepted,
Refused,
Unresolved,
}
impl From<TapOutcome> for Outcome {
fn from(outcome: TapOutcome) -> Self {
match outcome {
TapOutcome::Delivered => Self::Delivered,
TapOutcome::NoSubscriber => Self::Nobody,
TapOutcome::NoneAccepted => Self::Unaccepted,
TapOutcome::Unresolved => Self::Unresolved,
TapOutcome::Refused => Self::Refused,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Hop {
pub node: String,
pub at_ms: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TapMessage {
pub id: String,
pub subject: String,
pub size: u64,
pub outcome: Outcome,
pub t: u64,
pub from: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_station: Option<String>,
pub to: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub to_stations: Option<Vec<String>>,
pub hops: Vec<Hop>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<String>,
pub held: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub diverted: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TapBatch {
v: u64,
#[serde(rename = "type")]
record_type: &'static str,
pub target: String,
pub seq: u64,
pub sampled: NonZeroU32,
pub dropped: u64,
pub messages: Vec<TapMessage>,
}
impl TapBatch {
#[must_use]
pub const fn new(
target: String,
seq: u64,
sampled: NonZeroU32,
dropped: u64,
messages: Vec<TapMessage>,
) -> Self {
Self {
v: RECORD_VERSION,
record_type: "tap.batch",
target,
seq,
sampled,
dropped,
messages,
}
}
}
pub fn canonical_bytes<Record: Serialize>(
record: &Record,
kind: &'static str,
) -> Result<Vec<u8>, ConsoleError> {
serde_json::to_value(record)
.and_then(|value| serde_json::to_vec(&value))
.map_err(|source| ConsoleError::Encode {
record: kind,
source,
})
}
pub fn tap_batch_record(
device: &str,
target: &str,
batch: &tap::TapBatch,
wall: &WallBase,
) -> Result<TapBatch, ConsoleError> {
let messages = batch
.messages
.iter()
.map(|message| tap_message(device, message, wall))
.collect::<Result<Vec<_>, _>>()?;
Ok(TapBatch::new(
target.to_owned(),
batch.seq,
batch.sampled,
batch.dropped,
messages,
))
}
fn tap_message(
device: &str,
message: &tap::TapMessage,
wall: &WallBase,
) -> Result<TapMessage, ConsoleError> {
Ok(TapMessage {
id: match message.ident {
TapIdent::Message(id) => format!("{device}:{id}"),
TapIdent::Observation(observation) => format!("{device}:x{observation}"),
},
subject: message.subject.clone(),
size: message.size,
outcome: message.outcome.into(),
t: wall.unix_ms_at(message.seen_at)?,
from: match message.source {
TapSource::Connection(connection) => connection.to_string(),
TapSource::Server => message
.station
.as_deref()
.map_or_else(|| format!("liminal.{device}"), str::to_owned),
},
from_station: message.station.as_deref().map(str::to_owned),
to: device.to_owned(),
to_stations: message.to_stations.as_deref().map(|stations| {
stations
.iter()
.map(|reached| reached.station.to_string())
.collect()
}),
hops: message
.hops
.iter()
.map(|hop| Hop {
node: hop.node.to_string(),
at_ms: hop.at_ms,
})
.collect(),
reply_to: message.reply_to.clone(),
held: message.held,
diverted: None,
})
}