use crate::utils::clock::Instant;
use std::collections::{HashMap, VecDeque};
use std::net::SocketAddr;
use std::path::PathBuf;
use crate::server::connection::ConnectionId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ServerId(u32);
impl ServerId {
pub fn new(id: u32) -> Self {
Self(id)
}
pub fn as_u32(&self) -> u32 {
self.0
}
pub fn from_string(s: &str) -> Option<Self> {
let s = s.trim();
let id_str = s.strip_prefix("server-").unwrap_or(s);
id_str.parse::<u32>().ok().map(Self)
}
}
impl std::fmt::Display for ServerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "server-{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ServerStatus {
Starting,
Running,
Stopped,
Error(String),
}
impl ServerStatus {
pub fn as_str(&self) -> &str {
match self {
Self::Starting => "Starting",
Self::Running => "Running",
Self::Stopped => "Stopped",
Self::Error(_) => "Error",
}
}
}
impl std::fmt::Display for ServerStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Starting => write!(f, "Starting"),
Self::Running => write!(f, "Running"),
Self::Stopped => write!(f, "Stopped"),
Self::Error(msg) => write!(f, "Error: {}", msg),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ProtocolState {
Idle,
Processing,
Accumulating,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ImapSessionState {
NotAuthenticated,
Authenticated,
Selected,
Logout,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BgpSessionState {
Idle,
Connect,
Active,
OpenSent,
OpenConfirm,
Established,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OspfNeighborState {
Down,
Init,
TwoWay,
ExStart,
Exchange,
Loading,
Full,
}
#[derive(Debug, Clone)]
pub struct ProtocolConnectionInfo {
pub data: serde_json::Value,
}
impl ProtocolConnectionInfo {
pub fn new<T: serde::Serialize>(data: T) -> Self {
Self {
data: serde_json::to_value(data).unwrap_or(serde_json::Value::Null),
}
}
pub fn empty() -> Self {
Self {
data: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
self.data.get(key)
}
pub fn try_into<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.data.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionStatus {
Active,
Closed,
}
#[derive(Debug, Clone)]
pub struct ConnectionState {
pub id: ConnectionId,
pub remote_addr: SocketAddr,
pub local_addr: SocketAddr,
pub bytes_sent: u64,
pub bytes_received: u64,
pub packets_sent: u64,
pub packets_received: u64,
pub last_activity: Instant,
pub status: ConnectionStatus,
pub status_changed_at: Instant,
pub protocol_info: ProtocolConnectionInfo,
}
pub const RECENT_CONNECTION_CAPACITY: usize = 50;
#[derive(Debug, Clone, serde::Serialize)]
pub struct ClosedConnectionSummary {
pub id: u32,
pub remote_addr: String,
pub opened_unix_ms: Option<u64>,
pub closed_unix_ms: u64,
pub bytes_sent: u64,
pub bytes_received: u64,
pub packets_sent: u64,
pub packets_received: u64,
pub protocol_info: serde_json::Value,
}
fn instant_to_unix_ms(at: Instant) -> u64 {
let system_time = crate::utils::clock::SystemTime::now() - at.elapsed();
system_time
.duration_since(crate::utils::clock::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[derive(Debug, Clone)]
pub struct ServerInstance {
pub id: ServerId,
pub port: u16,
pub protocol_name: String,
pub instruction: String,
pub memory: String,
pub status: ServerStatus,
pub connections: HashMap<ConnectionId, ConnectionState>,
pub created_at: Instant,
pub status_changed_at: Instant,
pub local_addr: Option<SocketAddr>,
pub startup_params: Option<serde_json::Value>,
pub event_handler_config: Option<crate::scripting::EventHandlerConfig>,
pub protocol_data: serde_json::Value,
pub log_files: HashMap<String, PathBuf>,
pub feedback_instructions: Option<String>,
pub feedback_buffer: Vec<serde_json::Value>,
pub last_feedback_processed: Option<Instant>,
pub recent_connections: VecDeque<ClosedConnectionSummary>,
pub connection_opened_at: HashMap<ConnectionId, Instant>,
}
pub const MAX_LOG_OUTPUT_NAME_LEN: usize = 64;
pub fn validate_log_output_name(name: &str) -> anyhow::Result<()> {
if name.is_empty() {
anyhow::bail!(
"Invalid output_name: it must not be empty. Allowed: 1-{} characters from A-Z, \
a-z, 0-9, '_' and '-' (it becomes part of the filename netget_<name>_<time>.log).",
MAX_LOG_OUTPUT_NAME_LEN
);
}
if name.len() > MAX_LOG_OUTPUT_NAME_LEN {
anyhow::bail!(
"Invalid output_name '{}': {} bytes exceeds the {}-byte limit.",
name,
name.len(),
MAX_LOG_OUTPUT_NAME_LEN
);
}
if let Some(bad) = name
.chars()
.find(|c| !(c.is_ascii_alphanumeric() || *c == '_' || *c == '-'))
{
anyhow::bail!(
"Invalid output_name '{}': character {:?} is not allowed. An output name may \
contain only A-Z, a-z, 0-9, '_' and '-' (it becomes part of the filename \
netget_<name>_<time>.log).",
name,
bad
);
}
Ok(())
}
impl ServerInstance {
pub fn new(id: ServerId, port: u16, protocol_name: String, instruction: String) -> Self {
let now = Instant::now();
Self {
id,
port,
protocol_name,
instruction,
memory: String::new(),
status: ServerStatus::Starting,
connections: HashMap::new(),
created_at: now,
status_changed_at: now,
local_addr: None,
startup_params: None,
event_handler_config: None,
protocol_data: serde_json::Value::Object(serde_json::Map::new()),
log_files: HashMap::new(),
feedback_instructions: None,
feedback_buffer: Vec::new(),
last_feedback_processed: None,
recent_connections: VecDeque::new(),
connection_opened_at: HashMap::new(),
}
}
pub fn get_protocol_data<T: serde::de::DeserializeOwned>(
&self,
) -> Result<T, serde_json::Error> {
serde_json::from_value(self.protocol_data.clone())
}
pub fn set_protocol_data<T: serde::Serialize>(
&mut self,
data: T,
) -> Result<(), serde_json::Error> {
self.protocol_data = serde_json::to_value(data)?;
Ok(())
}
pub fn get_protocol_field(&self, key: &str) -> Option<&serde_json::Value> {
self.protocol_data.get(key)
}
pub fn set_protocol_field(&mut self, key: String, value: serde_json::Value) {
if let Some(obj) = self.protocol_data.as_object_mut() {
obj.insert(key, value);
} else {
let mut map = serde_json::Map::new();
map.insert(key, value);
self.protocol_data = serde_json::Value::Object(map);
}
}
pub fn get_or_create_log_path(&mut self, output_name: &str) -> anyhow::Result<PathBuf> {
validate_log_output_name(output_name)?;
if let Some(path) = self.log_files.get(output_name) {
return Ok(path.clone());
}
let now = crate::utils::clock::SystemTime::now();
let elapsed = self.created_at.elapsed();
let created_system_time = now - elapsed;
let timestamp = crate::utils::clock::to_local_datetime(created_system_time);
let timestamp_str = timestamp.format("%Y_%m_%d_%H_%M_%S").to_string();
let log_filename = format!(
"netget_{}_{}_s{}.log",
output_name,
timestamp_str,
self.id.as_u32()
);
let log_path = PathBuf::from(log_filename);
self.log_files
.insert(output_name.to_string(), log_path.clone());
Ok(log_path)
}
pub fn summary(&self) -> String {
format!(
"#{} {} on port {} ({}) - {} connections",
self.id.as_u32(),
self.protocol_name,
self.port,
self.status.as_str(),
self.connections.len()
)
}
pub fn add_connection(&mut self, state: ConnectionState) {
self.connection_opened_at.insert(state.id, Instant::now());
self.connections.insert(state.id, state);
}
pub fn remove_connection(&mut self, id: ConnectionId) -> Option<ConnectionState> {
let removed = self.connections.remove(&id);
if let Some(state) = &removed {
self.record_closed_connection(state);
}
removed
}
fn record_closed_connection(&mut self, state: &ConnectionState) {
let opened_unix_ms = self
.connection_opened_at
.remove(&state.id)
.map(instant_to_unix_ms);
self.recent_connections.push_front(ClosedConnectionSummary {
id: state.id.as_u32(),
remote_addr: state.remote_addr.to_string(),
opened_unix_ms,
closed_unix_ms: instant_to_unix_ms(state.status_changed_at),
bytes_sent: state.bytes_sent,
bytes_received: state.bytes_received,
packets_sent: state.packets_sent,
packets_received: state.packets_received,
protocol_info: state.protocol_info.data.clone(),
});
self.recent_connections.truncate(RECENT_CONNECTION_CAPACITY);
}
pub fn get_connection(&self, id: ConnectionId) -> Option<&ConnectionState> {
self.connections.get(&id)
}
pub fn get_connection_mut(&mut self, id: ConnectionId) -> Option<&mut ConnectionState> {
self.connections.get_mut(&id)
}
pub fn get_all_connections(&self) -> Vec<&ConnectionState> {
self.connections.values().collect()
}
pub fn cleanup_old_connections(&mut self, max_age_secs: u64) {
let now = Instant::now();
let stale: Vec<ConnectionId> = self
.connections
.values()
.filter(|state| now.duration_since(state.last_activity).as_secs() >= max_age_secs)
.map(|state| state.id)
.collect();
for id in stale {
self.remove_connection(id);
}
}
}