use super::subscription_wire::{FillOutcome, fill_buffer, protocol_error};
use super::{
Frame, ProtocolError, SUBSCRIPTION_STREAM_ID, SchemaId, SdkError, TcpStream, ToString, Vec,
WRITE_TIMEOUT, format, write_frame,
};
use crate::remote::SETUP_TIMEOUT;
use liminal::protocol::{DELIVER_MESSAGE_ID_VERSION, ProtocolVersion, decode};
use std::time::Instant;
const CLIENT_MIN_VERSION: ProtocolVersion = ProtocolVersion::new(1, 0);
pub(super) const CLIENT_MAX_VERSION: ProtocolVersion = DELIVER_MESSAGE_ID_VERSION;
const SUBSCRIBE_MAX_IN_FLIGHT: u32 = 1024;
pub(super) fn connect_socket(address: &str) -> Result<TcpStream, SdkError> {
let stream = TcpStream::connect(address).map_err(|source| SdkError::Connection {
description: format!("failed to connect subscription client to {address}: {source}"),
})?;
stream
.set_nodelay(true)
.map_err(|source| SdkError::Connection {
description: format!("failed to disable Nagle for {address}: {source}"),
})?;
stream
.set_read_timeout(Some(SETUP_TIMEOUT))
.map_err(|source| SdkError::Connection {
description: format!(
"failed to set the subscription setup deadline for {address}: {source}"
),
})?;
stream
.set_write_timeout(Some(WRITE_TIMEOUT))
.map_err(|source| SdkError::Connection {
description: format!(
"failed to set subscription write timeout for {address}: {source}"
),
})?;
Ok(stream)
}
pub(super) fn handshake(
stream: &mut TcpStream,
buffer: &mut Vec<u8>,
auth_token: &[u8],
) -> Result<(), SdkError> {
let connect = Frame::Connect {
flags: 0,
min_version: CLIENT_MIN_VERSION,
max_version: CLIENT_MAX_VERSION,
auth_token: auth_token.to_vec(),
};
write_frame(stream, &connect)?;
match read_one_frame(stream, buffer)? {
Frame::ConnectAck { .. } => Ok(()),
Frame::ConnectError {
reason_code,
message,
..
} => Err(SdkError::Connection {
description: format!(
"server rejected subscription connection (reason {reason_code}): {}",
message.unwrap_or_else(|| "no detail".to_string())
),
}),
other => Err(SdkError::Protocol {
description: format!(
"expected ConnectAck during subscription handshake, received {:?}",
other.frame_type()
),
}),
}
}
pub(super) fn subscribe(
stream: &mut TcpStream,
buffer: &mut Vec<u8>,
channel: &str,
accepted_schemas: Vec<SchemaId>,
) -> Result<u64, SdkError> {
let frame = Frame::Subscribe {
flags: 0,
stream_id: SUBSCRIPTION_STREAM_ID,
channel: channel.to_string(),
accepted_schemas,
max_in_flight: SUBSCRIBE_MAX_IN_FLIGHT,
};
write_frame(stream, &frame)?;
match read_one_frame(stream, buffer)? {
Frame::SubscribeAck {
subscription_id, ..
} => Ok(subscription_id),
Frame::SubscribeError {
reason_code,
message,
..
} => Err(SdkError::Protocol {
description: format!(
"server rejected subscribe (reason {reason_code}): {}",
message.unwrap_or_else(|| "no detail".to_string())
),
}),
other => Err(SdkError::Protocol {
description: format!(
"expected SubscribeAck during subscribe, received {:?}",
other.frame_type()
),
}),
}
}
fn read_one_frame(stream: &mut TcpStream, buffer: &mut Vec<u8>) -> Result<Frame, SdkError> {
let deadline = Instant::now() + SETUP_TIMEOUT;
loop {
match decode(buffer) {
Ok((frame, consumed)) => {
buffer.drain(..consumed);
return Ok(frame);
}
Err(
ProtocolError::IncompleteHeader { .. } | ProtocolError::TruncatedPayload { .. },
) => match fill_buffer(stream, buffer)? {
FillOutcome::Read => {}
FillOutcome::TimedOut => {
if Instant::now() >= deadline {
return Err(SdkError::Connection {
description:
"subscription connection timed out waiting for a control-frame reply"
.to_string(),
});
}
}
},
Err(error) => return Err(protocol_error(&error)),
}
}
}