use super::{ActionDefinition, ParameterDefinition, StartupExamples};
use crate::protocol::dependencies::ProtocolDependency;
use crate::state::app_state::AppState;
pub trait Protocol: Send + Sync {
fn default_binding(&self) -> Option<crate::protocol::BindingDefaults> {
None
}
fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
Vec::new()
}
fn get_async_actions(&self, state: &AppState) -> Vec<ActionDefinition>;
fn get_sync_actions(&self) -> Vec<ActionDefinition>;
fn protocol_name(&self) -> &'static str;
fn get_event_types(&self) -> Vec<crate::protocol::EventType> {
Vec::new()
}
fn stack_name(&self) -> &'static str;
fn keywords(&self) -> Vec<&'static str>;
fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2;
fn description(&self) -> &'static str;
fn example_prompt(&self) -> &'static str;
fn get_startup_examples(&self) -> StartupExamples;
fn group_name(&self) -> &'static str;
fn get_dependencies(&self) -> Vec<ProtocolDependency> {
default_dependencies_from_privilege(self)
}
fn startup_dependencies(
&self,
_startup_params: Option<&serde_json::Value>,
) -> Vec<ProtocolDependency> {
self.get_dependencies()
}
fn startup_privilege_requirement(
&self,
_startup_params: Option<&serde_json::Value>,
) -> crate::protocol::metadata::PrivilegeRequirement {
self.metadata().privilege_requirement
}
}
pub fn unless_udp_transport(
declared: crate::protocol::metadata::PrivilegeRequirement,
startup_params: Option<&serde_json::Value>,
) -> crate::protocol::metadata::PrivilegeRequirement {
match startup_params
.and_then(|p| p.get("transport"))
.and_then(|t| t.as_str())
{
Some("udp" | "test") => crate::protocol::metadata::PrivilegeRequirement::None,
_ => declared,
}
}
pub fn default_dependencies_from_privilege<P: Protocol + ?Sized>(
protocol: &P,
) -> Vec<ProtocolDependency> {
use crate::protocol::metadata::PrivilegeRequirement as P9;
match protocol.metadata().privilege_requirement {
P9::None => Vec::new(),
P9::PrivilegedPort(_) => Vec::new(),
P9::RawSockets => vec![ProtocolDependency::RawSocketAccess],
P9::PacketCapture => vec![ProtocolDependency::PromiscuousMode],
P9::DeviceAccess(_) => Vec::new(),
P9::Root => vec![ProtocolDependency::RootAccess],
}
}
#[derive(Debug)]
pub enum ActionResult {
Output(Vec<u8>),
CloseConnection,
WaitForMore,
NoAction,
Multiple(Vec<ActionResult>),
Custom {
name: String,
data: serde_json::Value,
},
}
impl ActionResult {
pub fn has_output(&self) -> bool {
match self {
ActionResult::Output(_) => true,
ActionResult::Multiple(results) => results.iter().any(|r| r.has_output()),
_ => false,
}
}
pub fn closes_connection(&self) -> bool {
match self {
ActionResult::CloseConnection => true,
ActionResult::Multiple(results) => results.iter().any(|r| r.closes_connection()),
_ => false,
}
}
pub fn waits_for_more(&self) -> bool {
match self {
ActionResult::WaitForMore => true,
ActionResult::Multiple(results) => results.iter().any(|r| r.waits_for_more()),
_ => false,
}
}
pub fn get_all_output(&self) -> Vec<Vec<u8>> {
match self {
ActionResult::Output(data) => vec![data.clone()],
ActionResult::Multiple(results) => {
results.iter().flat_map(|r| r.get_all_output()).collect()
}
_ => Vec::new(),
}
}
}
pub trait Server: Protocol {
fn spawn(
&self,
ctx: crate::protocol::SpawnContext,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
>;
fn execute_action(&self, action: serde_json::Value) -> anyhow::Result<ActionResult>;
fn execute_action_with_state<'a>(
&'a self,
action: serde_json::Value,
state: AppState,
server_id: Option<crate::state::ServerId>,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = anyhow::Result<ActionResult>> + Send + 'a>,
> {
let _ = (state, server_id);
let result = self.execute_action(action);
Box::pin(async move { result })
}
}
pub fn audit_event_action_declarations(protocol: &dyn Protocol) -> Vec<String> {
let sync_action_count = protocol.get_sync_actions().len();
protocol
.get_event_types()
.into_iter()
.filter(|event_type| event_type.has_no_usable_actions())
.map(|event_type| {
if sync_action_count == 0 {
format!(
"event '{}' of protocol '{}' declares no actions, and the protocol declares \
no sync actions either, so this protocol offers the model nothing: every \
protocol-specific action it could return would be rejected as unknown and \
the event cannot be answered at all. Fix by declaring sync actions and \
attaching them with .with_actions(...), by delegating another protocol's \
action set (as doh/dot do for DNS), or by .with_no_actions() if the event \
genuinely needs none.",
event_type.id,
protocol.protocol_name(),
)
} else {
format!(
"event '{}' of protocol '{}' declares no actions of its own, so the model \
would be offered none of the protocol's {} sync action(s) and anything \
protocol-specific it returned would be rejected as an unknown action. Fix by \
adding .with_actions(...) to the event type, or .with_no_actions() if it \
genuinely needs none.",
event_type.id,
protocol.protocol_name(),
sync_action_count
)
}
})
.collect()
}