use crate::error::ZmqError;
use bytes::{BufMut, BytesMut};
use std::convert::TryInto;
use tracing;
pub const GREETING_LENGTH: usize = 64;
pub const MECHANISM_LENGTH: usize = 20;
pub const GREETING_VERSION_MAJOR_BYTE: u8 = 0x03;
pub const GREETING_VERSION_MINOR_BYTE: u8 = 0x00;
const VERSION_MAJOR_OFFSET: usize = 10;
const VERSION_MINOR_OFFSET: usize = 11;
pub const MECHANISM_OFFSET: usize = 12;
pub const AS_SERVER_OFFSET: usize = MECHANISM_OFFSET + MECHANISM_LENGTH; const PADDING_OFFSET: usize = AS_SERVER_OFFSET + 1; const PADDING_LENGTH: usize = GREETING_LENGTH - PADDING_OFFSET;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZmtpGreeting {
pub version: (u8, u8),
pub mechanism: [u8; MECHANISM_LENGTH], pub as_server: bool,
}
impl ZmtpGreeting {
pub fn encode(mechanism: &[u8; MECHANISM_LENGTH], as_server: bool, buffer: &mut BytesMut) {
buffer.reserve(GREETING_LENGTH);
buffer.put_u8(0xFF);
buffer.put_bytes(0, 8); buffer.put_u8(0x7F);
buffer.put_u8(GREETING_VERSION_MAJOR_BYTE);
buffer.put_u8(GREETING_VERSION_MINOR_BYTE);
buffer.put_slice(mechanism);
buffer.put_u8(as_server as u8);
let current_len = buffer.len();
let padding_len = GREETING_LENGTH - current_len;
if padding_len > 0 {
buffer.put_bytes(0, padding_len);
}
debug_assert_eq!(buffer.len(), GREETING_LENGTH);
}
pub fn decode(buffer: &mut BytesMut) -> Result<Option<Self>, ZmqError> {
if buffer.len() < GREETING_LENGTH {
return Ok(None); }
let data = buffer.split_to(GREETING_LENGTH);
if data[0] != 0xFF {
tracing::error!("Greeting does not start with 0xFF (got {:#04x})", data[0]);
return Err(ZmqError::ProtocolViolation(
"Greeting does not start with 0xFF".into(),
));
}
for i in 0..PADDING_LENGTH {
let idx = PADDING_OFFSET + i;
if data[idx] != 0x00 {
tracing::error!(
"Invalid ZMTP greeting: non-zero padding byte at index {}: {:#04x}",
idx,
data[idx]
);
return Err(ZmqError::ProtocolViolation(
"Non-zero byte in greeting padding".into(),
));
}
}
let major_version = data[VERSION_MAJOR_OFFSET];
let minor_version = data[VERSION_MINOR_OFFSET];
if major_version != GREETING_VERSION_MAJOR_BYTE {
return Err(ZmqError::ProtocolViolation(format!(
"Unsupported ZMTP major version {}.{}",
major_version, minor_version
)));
}
let version = (major_version, minor_version);
let mechanism_slice = &data[MECHANISM_OFFSET..MECHANISM_OFFSET + MECHANISM_LENGTH];
let mechanism: [u8; MECHANISM_LENGTH] = mechanism_slice.try_into().unwrap();
let as_server_byte = data[AS_SERVER_OFFSET];
let as_server = match as_server_byte {
0x00 => false,
0x01 => true,
_ => return Err(ZmqError::ProtocolViolation("Invalid as-server flag".into())),
};
tracing::debug!(?version, mechanism_name = %std::str::from_utf8(&mechanism).unwrap_or("").trim_end_matches('\0'), as_server, "Parsed ZMTP Greeting");
Ok(Some(Self {
version,
mechanism,
as_server,
}))
}
pub fn mechanism_name(&self) -> &str {
let first_null = self
.mechanism
.iter()
.position(|&b| b == 0)
.unwrap_or(MECHANISM_LENGTH);
std::str::from_utf8(&self.mechanism[..first_null]).unwrap_or("<invalid_utf8>")
}
}