1mod agent;
15mod host;
16
17use core::fmt;
18use std::time::Duration;
19
20pub use agent::{AgentConfig, AgentEvent, AgentSession, Responded};
21pub use host::{HostConfig, HostEvent, HostSession};
22
23use crate::capability::Capabilities;
24use crate::error::{Error, code};
25use crate::frame::MIN_MAX_FRAME;
26
27pub const PING_INTERVAL: Duration = Duration::from_secs(1);
29
30pub const PEER_TIMEOUT: Duration = Duration::from_secs(3);
34
35pub const DEADLINE_GRACE: Duration = Duration::from_millis(500);
38
39pub const RESYNC_BACKOFF: Duration = Duration::from_millis(100);
42
43pub const RESYNC_BACKOFF_MAX: Duration = Duration::from_secs(2);
45
46pub fn select_version(ours: &[u16], theirs: &[u16]) -> Option<u16> {
48 ours.iter().filter(|v| theirs.contains(v)).max().copied()
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53enum Phase {
54 Handshake,
55 Ready,
56 Closed,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct Negotiated {
62 pub version: u16,
64 pub max_frame: u32,
66 pub capabilities: Capabilities,
68 pub agent: String,
70 pub host_versions: Vec<u16>,
71 pub agent_versions: Vec<u16>,
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
76pub enum HandshakeError {
77 NoCommonVersion { host: Vec<u16>, agent: Vec<u16> },
79 TokenMismatch,
81 FrameLimitTooSmall { limit: u32 },
83 VersionNotOffered { version: u16, offered: Vec<u16> },
85 Refused(Error),
87}
88
89impl HandshakeError {
90 pub fn to_error(&self) -> Error {
92 match self {
93 HandshakeError::NoCommonVersion { .. } | HandshakeError::VersionNotOffered { .. } => {
94 Error::new(code::VERSION_MISMATCH, self.to_string())
95 }
96 HandshakeError::TokenMismatch => Error::new(code::TOKEN_MISMATCH, self.to_string()),
97 HandshakeError::FrameLimitTooSmall { .. } => {
98 Error::new(code::FRAME_LIMIT_TOO_SMALL, self.to_string())
99 }
100 HandshakeError::Refused(error) => error.clone(),
101 }
102 }
103}
104
105impl fmt::Display for HandshakeError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 HandshakeError::NoCommonVersion { host, agent } => write!(
109 f,
110 "no common protocol version: the daemon speaks {host:?} and the agent speaks {agent:?}; redeploy the agent that matches this daemon"
111 ),
112 HandshakeError::TokenMismatch => f.write_str(
113 "the agent's hello carries another per-launch token, so this daemon did not start it",
114 ),
115 HandshakeError::FrameLimitTooSmall { limit } => write!(
116 f,
117 "the negotiated maximum frame size of {limit} bytes is below the protocol minimum of {MIN_MAX_FRAME} bytes"
118 ),
119 HandshakeError::VersionNotOffered { version, offered } => write!(
120 f,
121 "the daemon selected protocol version {version}, which the agent does not speak (it speaks {offered:?})"
122 ),
123 HandshakeError::Refused(error) => write!(f, "the daemon refused the connection: {error}"),
124 }
125 }
126}
127
128impl std::error::Error for HandshakeError {}
129
130#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum DisconnectReason {
133 Eof,
135 Silent(Duration),
137 Handshake(HandshakeError),
139 Protocol(String),
141 Closed,
143}
144
145impl fmt::Display for DisconnectReason {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 DisconnectReason::Eof => f.write_str("the other side closed the connection"),
149 DisconnectReason::Silent(silence) => write!(
150 f,
151 "nothing arrived from the other side for {:.1} s",
152 silence.as_secs_f64()
153 ),
154 DisconnectReason::Handshake(e) => write!(f, "handshake failed: {e}"),
155 DisconnectReason::Protocol(detail) => write!(f, "protocol error: {detail}"),
156 DisconnectReason::Closed => f.write_str("the session was closed"),
157 }
158 }
159}