1use crate::codec::{ZmtpDecoder, ZmtpError, ZmtpFrame};
2use crate::greeting::ZmtpGreeting;
3use crate::handshake::parse_ready_command;
4use bytes::{Bytes, BytesMut};
5use monocoque_core::buffer::SegmentedBuffer;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SocketType {
10 Pair,
12 Dealer,
14 Router,
16 Pub,
18 Sub,
20 Req,
22 Rep,
24 Push,
26 Pull,
28 Xpub,
30 Xsub,
32}
33
34impl SocketType {
35 #[must_use]
37 pub const fn as_str(&self) -> &'static str {
38 match self {
39 Self::Pair => "PAIR",
40 Self::Dealer => "DEALER",
41 Self::Router => "ROUTER",
42 Self::Pub => "PUB",
43 Self::Sub => "SUB",
44 Self::Req => "REQ",
45 Self::Rep => "REP",
46 Self::Push => "PUSH",
47 Self::Pull => "PULL",
48 Self::Xpub => "XPUB",
49 Self::Xsub => "XSUB",
50 }
51 }
52}
53
54pub enum SessionEvent {
56 SendBytes(Bytes),
58
59 Frame(ZmtpFrame),
61
62 HandshakeComplete {
64 peer_identity: Option<Bytes>,
66 peer_socket_type: SocketType,
68 },
69
70 Error(ZmtpError),
72}
73
74enum State {
75 Greeting {
76 buffer: BytesMut,
77 },
78 Handshake {
79 decoder: ZmtpDecoder,
80 peer_socket_type: Option<SocketType>,
81 peer_identity: Option<Bytes>,
82 },
83 Active {
84 decoder: ZmtpDecoder,
85 },
86}
87
88pub struct ZmtpSession {
90 state: State,
91 local_socket_type: SocketType,
92 recv: SegmentedBuffer,
93 max_frame_size: Option<usize>,
96}
97
98fn make_decoder(max_frame_size: Option<usize>) -> ZmtpDecoder {
100 max_frame_size.map_or_else(ZmtpDecoder::new, ZmtpDecoder::with_max_frame_size)
101}
102
103impl ZmtpSession {
104 #[must_use]
109 pub fn new(local_socket_type: SocketType) -> Self {
110 Self::with_max_frame_size(local_socket_type, None)
111 }
112
113 #[must_use]
118 pub fn with_max_frame_size(
119 local_socket_type: SocketType,
120 max_frame_size: Option<usize>,
121 ) -> Self {
122 Self {
123 state: State::Greeting {
124 buffer: BytesMut::with_capacity(64),
125 },
126 local_socket_type,
127 recv: SegmentedBuffer::new(),
128 max_frame_size,
129 }
130 }
131
132 #[must_use]
137 pub fn new_active(local_socket_type: SocketType) -> Self {
138 Self::new_active_with_max_frame_size(local_socket_type, None)
139 }
140
141 #[must_use]
146 pub fn new_active_with_max_frame_size(
147 local_socket_type: SocketType,
148 max_frame_size: Option<usize>,
149 ) -> Self {
150 Self {
151 state: State::Active {
152 decoder: make_decoder(max_frame_size),
153 },
154 local_socket_type,
155 recv: SegmentedBuffer::new(),
156 max_frame_size,
157 }
158 }
159
160 pub fn local_greeting(&self) -> Bytes {
168 let mut b = BytesMut::with_capacity(64);
169
170 b.extend_from_slice(&[0xFF]);
172 b.extend_from_slice(&[0u8; 8]);
173 b.extend_from_slice(&[0x7F]);
174
175 b.extend_from_slice(&[0x03, 0x00]);
177
178 b.extend_from_slice(b"NULL");
180 b.extend_from_slice(&[0u8; 16]);
181
182 b.extend_from_slice(&[0x00]);
184
185 b.extend_from_slice(&[0u8; 31]);
187
188 b.freeze()
189 }
190
191 pub fn on_bytes(&mut self, src: Bytes) -> Vec<SessionEvent> {
193 let mut events = Vec::new();
194
195 self.recv.push(src);
196
197 loop {
198 match &mut self.state {
199 State::Greeting { buffer } => {
203 let needed = 64 - buffer.len();
204 let take = needed.min(self.recv.len());
205 if let Some(bytes) = self.recv.take_bytes(take) {
206 buffer.extend_from_slice(&bytes);
207 }
208
209 if buffer.len() < 64 {
210 break;
211 }
212
213 let greeting = buffer.split().freeze();
214
215 match ZmtpGreeting::parse(&greeting) {
216 Ok(_g) => {
217 self.state = State::Handshake {
219 decoder: make_decoder(self.max_frame_size),
220 peer_socket_type: None,
221 peer_identity: None,
222 };
223
224 use crate::utils::{FLAG_COMMAND, build_ready, encode_frame};
231 let socket_type_str = match self.local_socket_type {
232 SocketType::Dealer => "DEALER",
233 SocketType::Router => "ROUTER",
234 SocketType::Pub => "PUB",
235 SocketType::Sub => "SUB",
236 SocketType::Xpub => "XPUB",
237 SocketType::Xsub => "XSUB",
238 SocketType::Req => "REQ",
239 SocketType::Rep => "REP",
240 SocketType::Push => "PUSH",
241 SocketType::Pull => "PULL",
242 SocketType::Pair => "PAIR",
243 };
244 let ready_body = build_ready(socket_type_str, None);
245 let ready_frame = encode_frame(FLAG_COMMAND, &ready_body);
246 events.push(SessionEvent::SendBytes(ready_frame));
247 }
248 Err(e) => {
249 events.push(SessionEvent::Error(e));
250 break;
251 }
252 }
253 }
254
255 State::Handshake {
259 decoder,
260 peer_socket_type,
261 peer_identity,
262 } => {
263 match decoder.decode(&mut self.recv) {
264 Ok(Some(frame)) => {
265 if !frame.is_command() {
266 events.push(SessionEvent::Error(ZmtpError::Protocol));
267 break;
268 }
269
270 let (parsed_socket_type, parsed_identity) =
271 match parse_ready_command(&frame.payload) {
272 Ok(parsed) => parsed,
273 Err(e) => {
274 events.push(SessionEvent::Error(e));
275 break;
276 }
277 };
278 *peer_socket_type = Some(parsed_socket_type);
279 *peer_identity = parsed_identity;
280
281 let peer_id = peer_identity.take();
283 let peer_st = peer_socket_type.unwrap_or(self.local_socket_type);
284
285 let new_decoder = make_decoder(self.max_frame_size);
288 let old_decoder = std::mem::replace(decoder, new_decoder);
289
290 self.state = State::Active {
292 decoder: old_decoder,
293 };
294
295 events.push(SessionEvent::HandshakeComplete {
296 peer_identity: peer_id,
297 peer_socket_type: peer_st,
298 });
299 }
300 Ok(None) => break,
301 Err(e) => {
302 events.push(SessionEvent::Error(e));
303 break;
304 }
305 }
306 }
307
308 State::Active { decoder } => match decoder.decode(&mut self.recv) {
312 Ok(Some(frame)) => {
313 events.push(SessionEvent::Frame(frame));
314 }
315 Ok(None) => break,
316 Err(e) => {
317 events.push(SessionEvent::Error(e));
318 break;
319 }
320 },
321 }
322 }
323
324 events
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::utils::{FLAG_COMMAND, build_ready, encode_frame};
332
333 #[test]
336 fn active_session_enforces_max_frame_size() {
337 let mut session = ZmtpSession::new_active_with_max_frame_size(SocketType::Rep, Some(10));
338
339 let events = session.on_bytes(Bytes::from_static(&[0x00, 20]));
343
344 assert!(
345 events
346 .iter()
347 .any(|e| matches!(e, SessionEvent::Error(ZmtpError::SizeTooLarge))),
348 "oversized frame should produce a SizeTooLarge error, got {} events",
349 events.len()
350 );
351 }
352
353 #[test]
355 fn active_session_accepts_frame_within_limit() {
356 let mut session = ZmtpSession::new_active_with_max_frame_size(SocketType::Rep, Some(64));
357
358 let events = session.on_bytes(Bytes::from_static(&[0x00, 2, b'h', b'i']));
360
361 assert!(
362 events.iter().any(|e| matches!(e, SessionEvent::Frame(_))),
363 "frame within the limit should decode, got {} events",
364 events.len()
365 );
366 }
367
368 fn valid_null_greeting() -> Bytes {
369 let mut greeting = [0u8; 64];
370 greeting[0] = 0xFF;
371 greeting[9] = 0x7F;
372 greeting[10] = 0x03;
373 greeting[11] = 0x01;
374 greeting[12..16].copy_from_slice(b"NULL");
375 Bytes::copy_from_slice(&greeting)
376 }
377
378 fn input_with_handshake_command(command_body: Bytes) -> Bytes {
379 let command_frame = encode_frame(FLAG_COMMAND, &command_body);
380 let mut input = BytesMut::with_capacity(64 + command_frame.len());
381 input.extend_from_slice(&valid_null_greeting());
382 input.extend_from_slice(&command_frame);
383 input.freeze()
384 }
385
386 fn has_protocol_error(events: &[SessionEvent]) -> bool {
387 events
388 .iter()
389 .any(|event| matches!(event, SessionEvent::Error(ZmtpError::Protocol)))
390 }
391
392 fn handshake_complete(events: &[SessionEvent]) -> Option<(SocketType, Option<Bytes>)> {
393 events.iter().find_map(|event| match event {
394 SessionEvent::HandshakeComplete {
395 peer_socket_type,
396 peer_identity,
397 } => Some((*peer_socket_type, peer_identity.clone())),
398 _ => None,
399 })
400 }
401
402 #[test]
403 fn session_rejects_non_ready_command_during_handshake() {
404 let mut session = ZmtpSession::new(SocketType::Router);
405 let input = input_with_handshake_command(Bytes::from_static(b"\x04PING"));
406 let events = session.on_bytes(input);
407
408 assert!(has_protocol_error(&events));
409 assert!(handshake_complete(&events).is_none());
410 }
411
412 #[test]
413 fn session_rejects_ready_without_socket_type() {
414 let mut session = ZmtpSession::new(SocketType::Router);
415 let input = input_with_handshake_command(Bytes::from_static(b"\x05READY"));
416 let events = session.on_bytes(input);
417
418 assert!(has_protocol_error(&events));
419 assert!(handshake_complete(&events).is_none());
420 }
421
422 #[test]
423 fn session_uses_socket_type_and_identity_from_ready_metadata() {
424 let mut session = ZmtpSession::new(SocketType::Router);
425 let input = input_with_handshake_command(build_ready("DEALER", Some(b"client-1")));
426 let events = session.on_bytes(input);
427
428 let (peer_socket_type, peer_identity) =
429 handshake_complete(&events).expect("valid READY metadata should complete handshake");
430 assert_eq!(peer_socket_type, SocketType::Dealer);
431 assert_eq!(peer_identity.as_deref(), Some(&b"client-1"[..]));
432 }
433}