use crate::config::LimitsConfig;
use liminal::protocol::{Frame, FrameHeader, ProtocolError, codec};
use std::io::{Read, Write};
use std::net::TcpStream;
pub mod choice;
mod relay;
pub use relay::RelayCarrier;
#[cfg(test)]
#[path = "link_carrier/ended_tests.rs"]
mod ended_tests;
mod reader;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Carrier {
Wire,
Direct,
Relay,
}
#[derive(PartialEq, Eq)]
pub enum CarrierReceive {
Sealed { subject: String, sealed: Vec<u8> },
Acknowledged {
message_id: u64,
durable_position: Option<u64>,
},
Refused {
reason_code: u16,
reason: Option<String>,
},
End { reason: String },
}
impl std::fmt::Debug for CarrierReceive {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sealed { subject, sealed } => out
.debug_struct("Sealed")
.field("subject", subject)
.field("bytes", &sealed.len())
.finish(),
Self::Acknowledged {
message_id,
durable_position,
} => out
.debug_struct("Acknowledged")
.field("message_id", message_id)
.field("durable_position", durable_position)
.finish(),
Self::Refused {
reason_code,
reason,
} => out
.debug_struct("Refused")
.field("reason_code", reason_code)
.field("reason_bytes", &reason.as_ref().map(String::len))
.finish(),
Self::End { reason } => out
.debug_struct("End")
.field("reason_bytes", &reason.len())
.finish(),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CarrierError {
#[error("wire link lifecycle failed: {0}")]
Lifecycle(String),
#[error("wire I/O failed: {0}")]
Io(#[from] std::io::Error),
#[error("wire framing refused: {0}")]
Protocol(#[from] ProtocolError),
#[error(transparent)]
Subject(#[from] crate::auth_lys::SubjectBindingError),
#[error("wire frame of {bytes} bytes exceeds bound {bound}")]
TooLarge { bytes: usize, bound: usize },
#[error("unexpected frame on wire link")]
UnexpectedFrame,
#[error("wire carrier has ended")]
Ended,
}
pub trait LinkCarrier: Send + std::fmt::Debug {
fn carrier(&self) -> Carrier;
fn refuse_unsolicited(&self) -> Result<(), CarrierError> {
Ok(())
}
fn send(&mut self, subject: &str, sealed: &[u8]) -> Result<(), CarrierError>;
fn receive(&mut self) -> Result<CarrierReceive, CarrierError>;
fn ended(&self) -> Option<String> {
None
}
}
#[derive(Debug)]
pub struct WireCarrier {
socket: TcpStream,
stream_id: u32,
ended: Option<String>,
failure: std::sync::Arc<std::sync::OnceLock<String>>,
life: Option<crate::link::LinkLife>,
reader: Option<reader::Reader>,
}
impl WireCarrier {
pub(crate) fn refuse_unsolicited(&self) -> Result<(), CarrierError> {
if let Some(reader) = &self.reader
&& let Some(answer) = reader.try_receive()?
{
let error = match answer {
Ok(Some(_)) => CarrierError::UnexpectedFrame,
Ok(None) => CarrierError::Ended,
Err(error) => error,
};
self.failed(&error);
return Err(error);
}
Ok(())
}
pub(crate) fn install_life(&mut self, life: crate::link::LinkLife) -> Result<(), CarrierError> {
if self.life.is_some() {
return Err(CarrierError::Lifecycle(
"lifecycle already installed".to_owned(),
));
}
self.life = Some(life.clone());
match reader::Reader::start(
&self.socket,
self.stream_id,
life,
std::sync::Arc::clone(&self.failure),
) {
Ok(reader) => self.reader = Some(reader),
Err(error) => {
self.end_life(crate::link::LinkEnd::Refused(error.to_string()));
return Err(error);
}
}
Ok(())
}
pub(crate) const fn life(&self) -> Option<&crate::link::LinkLife> {
self.life.as_ref()
}
fn end_life(&self, reason: crate::link::LinkEnd) {
if let Some(life) = &self.life
&& let Err(error) = life.end(reason)
{
tracing::error!(%error, "wire link end observation failed");
}
}
fn failed(&self, error: &CarrierError) {
self.failure.get_or_init(|| error.to_string());
self.end_life(
if matches!(error, CarrierError::Io(_) | CarrierError::Ended) {
crate::link::LinkEnd::PeerGone
} else {
crate::link::LinkEnd::Refused(error.to_string())
},
);
}
pub fn interrupt_handle(&self) -> Result<TcpStream, CarrierError> {
Ok(self.socket.try_clone()?)
}
pub fn new(socket: TcpStream, stream_id: u32) -> Result<Self, CarrierError> {
liminal::protocol::validate_stream(liminal::protocol::FrameType::LinkMessage, stream_id)?;
Ok(Self {
socket,
stream_id,
ended: None,
failure: std::sync::Arc::new(std::sync::OnceLock::new()),
life: None,
reader: None,
})
}
}
impl LinkCarrier for WireCarrier {
fn refuse_unsolicited(&self) -> Result<(), CarrierError> {
Self::refuse_unsolicited(self)
}
fn carrier(&self) -> Carrier {
Carrier::Wire
}
fn ended(&self) -> Option<String> {
self.ended
.clone()
.or_else(|| self.failure.get().cloned())
.or_else(|| {
self.life
.as_ref()
.and_then(|life| life.reason().ok().flatten())
})
}
fn send(&mut self, subject: &str, sealed: &[u8]) -> Result<(), CarrierError> {
if self.ended.is_some() {
return Err(CarrierError::Ended);
}
if let Some(life) = &self.life
&& life.reason().map_err(CarrierError::Lifecycle)?.is_some()
{
return Err(CarrierError::Ended);
}
crate::auth_lys::SubjectBinding::new(
"wire".to_owned(),
subject.to_owned(),
subject.to_owned(),
)?;
let result = write_frame(
&mut self.socket,
&Frame::LinkMessage {
flags: 0,
stream_id: self.stream_id,
subject: subject.to_owned(),
sealed: sealed.to_vec(),
},
);
if let Err(error) = &result {
self.failed(error);
}
result
}
fn receive(&mut self) -> Result<CarrierReceive, CarrierError> {
if let Some(reason) = &self.ended {
return Ok(CarrierReceive::End {
reason: reason.clone(),
});
}
let answer = if let Some(reader) = &self.reader {
reader.receive()
} else {
read_frame(&mut self.socket)
};
let frame = answer.inspect_err(|error| self.failed(error))?;
let reason = match frame {
Some(Frame::PublishAck {
stream_id,
message_id,
durable_position,
..
}) if stream_id == self.stream_id => {
return Ok(CarrierReceive::Acknowledged {
message_id,
durable_position,
});
}
Some(Frame::PublishError {
stream_id,
reason_code,
message,
..
}) if stream_id == self.stream_id => {
return Ok(CarrierReceive::Refused {
reason_code,
reason: message,
});
}
Some(Frame::LinkMessage {
stream_id,
subject,
sealed,
..
}) if stream_id == self.stream_id => {
crate::auth_lys::SubjectBinding::new(
"wire".to_owned(),
subject.clone(),
subject.clone(),
)?;
return Ok(CarrierReceive::Sealed { subject, sealed });
}
Some(Frame::DisconnectReason { reason, .. }) => reason.name().to_owned(),
Some(Frame::Disconnect { .. }) => "peer_disconnect".to_owned(),
None => "wire_eof".to_owned(),
Some(_) => {
self.failed(&CarrierError::UnexpectedFrame);
return Err(CarrierError::UnexpectedFrame);
}
};
self.end_life(
if reason == "grant_revoked" || reason == "revocation_log_reset" {
crate::link::LinkEnd::Revoked
} else {
crate::link::LinkEnd::PeerGone
},
);
self.ended = Some(reason.clone());
Ok(CarrierReceive::End { reason })
}
}
impl Drop for WireCarrier {
fn drop(&mut self) {
self.end_life(crate::link::LinkEnd::Shutdown);
if let Some(reader) = self.reader.take() {
reader.close(&self.socket);
}
}
}
pub(crate) const WIRE_FRAME_BOUND: usize = LimitsConfig::DEFAULT_MAX_CONNECTION_INBOX_BYTES;
pub(crate) fn write_frame(socket: &mut TcpStream, frame: &Frame) -> Result<(), CarrierError> {
let bytes = codec::encoded_len(frame)?;
if bytes > WIRE_FRAME_BOUND {
return Err(CarrierError::TooLarge {
bytes,
bound: WIRE_FRAME_BOUND,
});
}
let mut encoded = vec![0; bytes];
let length = codec::encode(frame, &mut encoded)?;
socket.write_all(&encoded[..length])?;
Ok(())
}
pub(crate) fn read_frame(socket: &mut TcpStream) -> Result<Option<Frame>, CarrierError> {
let mut header = [0; FrameHeader::WIRE_LEN];
loop {
match socket.read(&mut header[..1]) {
Ok(0) => return Ok(None),
Ok(_) => break,
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
Err(error) => return Err(error.into()),
}
}
socket.read_exact(&mut header[1..])?;
let payload = u32::from_be_bytes([header[6], header[7], header[8], header[9]]);
let bytes = usize::try_from(payload)
.ok()
.and_then(|length| length.checked_add(header.len()))
.ok_or(CarrierError::TooLarge {
bytes: usize::MAX,
bound: WIRE_FRAME_BOUND,
})?;
if bytes > WIRE_FRAME_BOUND {
return Err(CarrierError::TooLarge {
bytes,
bound: WIRE_FRAME_BOUND,
});
}
let mut encoded = vec![0; bytes];
encoded[..header.len()].copy_from_slice(&header);
socket.read_exact(&mut encoded[header.len()..])?;
let (frame, _) = codec::decode(&encoded)?;
Ok(Some(frame))
}