siphon_protocol/
messages.rs

1use serde::{Deserialize, Serialize};
2
3/// Type of tunnel to establish
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "snake_case")]
6pub enum TunnelType {
7    /// HTTP tunnel (proxied through Cloudflare)
8    Http,
9    /// Raw TCP tunnel (DNS-only, direct connection)
10    Tcp,
11}
12
13/// Messages sent from client to server
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(tag = "type", rename_all = "snake_case")]
16pub enum ClientMessage {
17    /// Request to establish a tunnel
18    RequestTunnel {
19        /// Requested subdomain (None = auto-generate)
20        subdomain: Option<String>,
21        /// Type of tunnel
22        tunnel_type: TunnelType,
23        /// Local port description (for display purposes)
24        local_port: u16,
25    },
26
27    /// Response data for an HTTP request
28    HttpResponse {
29        /// Stream ID this response belongs to
30        stream_id: u64,
31        /// HTTP status code
32        status: u16,
33        /// Response headers
34        headers: Vec<(String, String)>,
35        /// Response body
36        body: Vec<u8>,
37    },
38
39    /// TCP data from client to server (response to TcpData)
40    TcpData {
41        /// Stream ID for this TCP connection
42        stream_id: u64,
43        /// Data bytes
44        data: Vec<u8>,
45    },
46
47    /// TCP connection closed by local service
48    TcpClose {
49        /// Stream ID for this TCP connection
50        stream_id: u64,
51    },
52
53    /// Keepalive ping
54    Ping {
55        /// Timestamp for RTT measurement
56        timestamp: u64,
57    },
58}
59
60/// Messages sent from server to client
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(tag = "type", rename_all = "snake_case")]
63pub enum ServerMessage {
64    /// Tunnel successfully established
65    TunnelEstablished {
66        /// Assigned subdomain
67        subdomain: String,
68        /// Full URL for HTTP tunnels
69        url: String,
70        /// Assigned port for TCP tunnels (None for HTTP)
71        port: Option<u16>,
72    },
73
74    /// Tunnel request denied
75    TunnelDenied {
76        /// Reason for denial
77        reason: String,
78    },
79
80    /// Incoming HTTP request to forward to local service
81    HttpRequest {
82        /// Unique stream ID for this request
83        stream_id: u64,
84        /// HTTP method
85        method: String,
86        /// Request URI (path + query)
87        uri: String,
88        /// Request headers
89        headers: Vec<(String, String)>,
90        /// Request body
91        body: Vec<u8>,
92    },
93
94    /// New TCP connection established
95    TcpConnect {
96        /// Stream ID for this TCP connection
97        stream_id: u64,
98    },
99
100    /// Incoming TCP data
101    TcpData {
102        /// Stream ID for this TCP connection
103        stream_id: u64,
104        /// Data bytes
105        data: Vec<u8>,
106    },
107
108    /// TCP connection closed by remote
109    TcpClose {
110        /// Stream ID for this TCP connection
111        stream_id: u64,
112    },
113
114    /// Keepalive pong (response to Ping)
115    Pong {
116        /// Echo back the timestamp
117        timestamp: u64,
118    },
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_client_message_serialization() {
127        let msg = ClientMessage::RequestTunnel {
128            subdomain: Some("myapp".to_string()),
129            tunnel_type: TunnelType::Http,
130            local_port: 3000,
131        };
132        let json = serde_json::to_string(&msg).unwrap();
133        let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
134
135        match parsed {
136            ClientMessage::RequestTunnel {
137                subdomain,
138                tunnel_type,
139                local_port,
140            } => {
141                assert_eq!(subdomain, Some("myapp".to_string()));
142                assert_eq!(tunnel_type, TunnelType::Http);
143                assert_eq!(local_port, 3000);
144            }
145            _ => panic!("Wrong variant"),
146        }
147    }
148
149    #[test]
150    fn test_server_message_serialization() {
151        let msg = ServerMessage::TunnelEstablished {
152            subdomain: "myapp".to_string(),
153            url: "https://myapp.tunnel.example.com".to_string(),
154            port: None,
155        };
156        let json = serde_json::to_string(&msg).unwrap();
157        let parsed: ServerMessage = serde_json::from_str(&json).unwrap();
158
159        match parsed {
160            ServerMessage::TunnelEstablished {
161                subdomain,
162                url,
163                port,
164            } => {
165                assert_eq!(subdomain, "myapp");
166                assert_eq!(url, "https://myapp.tunnel.example.com");
167                assert_eq!(port, None);
168            }
169            _ => panic!("Wrong variant"),
170        }
171    }
172}