build fix: add missing files
This commit is contained in:
parent
e9a5e4e34a
commit
29c80c6f0e
109
src/error.rs
Normal file
109
src/error.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{ConnectionContext, RtspMessageContext};
|
||||
use thiserror::Error;
|
||||
|
||||
/// An opaque `std::error::Error + Send + Sync + 'static` implementation.
|
||||
///
|
||||
/// Currently the focus is on providing detailed human-readable error messages.
|
||||
/// In most cases they have enough information to find the offending packet
|
||||
/// in Wireshark.
|
||||
///
|
||||
/// If you wish to inspect Retina errors programmatically, or if you need
|
||||
/// errors formatted in a different way, please file an issue on the `retina`
|
||||
/// repository.
|
||||
pub struct Error(pub(crate) Box<ErrorInt>);
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum ErrorInt {
|
||||
/// The method's caller provided an invalid argument.
|
||||
#[error("Invalid argument: {0}")]
|
||||
InvalidArgument(String),
|
||||
|
||||
/// Unparseable or unexpected RTSP message.
|
||||
#[error("[{conn_ctx}, {msg_ctx}] RTSP framing error: {description}")]
|
||||
RtspFramingError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
description: String,
|
||||
},
|
||||
|
||||
#[error("[{conn_ctx}, {msg_ctx}] {status} response to {} CSeq={cseq}: \
|
||||
{description}", Into::<&str>::into(.method))]
|
||||
RtspResponseError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
method: rtsp_types::Method,
|
||||
cseq: u32,
|
||||
status: rtsp_types::StatusCode,
|
||||
description: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"[{conn_ctx}, {msg_ctx}] Received interleaved data on unassigned channel {channel_id}"
|
||||
)]
|
||||
RtspUnassignedChannelError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
channel_id: u8,
|
||||
},
|
||||
|
||||
#[error("[{conn_ctx}, {msg_ctx} channel {channel_id} stream {stream_id}] RTSP data message: {description}")]
|
||||
RtspDataMessageError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
channel_id: u8,
|
||||
stream_id: usize,
|
||||
description: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"[{conn_ctx}, {msg_ctx}, channel={channel_id}, stream={stream_id}, ssrc={ssrc:08x}] \
|
||||
{description}"
|
||||
)]
|
||||
RtpPacketError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
channel_id: u8,
|
||||
stream_id: usize,
|
||||
ssrc: u32,
|
||||
sequence_number: u16,
|
||||
description: String,
|
||||
},
|
||||
|
||||
#[error("Unable to connect to RTSP server: {0}")]
|
||||
ConnectError(#[source] std::io::Error),
|
||||
|
||||
#[error("[{conn_ctx}, {msg_ctx}] Error reading from RTSP peer: {source}")]
|
||||
ReadError {
|
||||
conn_ctx: ConnectionContext,
|
||||
msg_ctx: RtspMessageContext,
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("[{conn_ctx}] Error writing to RTSP peer: {source}")]
|
||||
WriteError {
|
||||
conn_ctx: ConnectionContext,
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("Failed precondition: {0}")]
|
||||
FailedPrecondition(String),
|
||||
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
291
src/tokio.rs
Normal file
291
src/tokio.rs
Normal file
@ -0,0 +1,291 @@
|
||||
//! tokio-based [`Connection`].
|
||||
//!
|
||||
//! In theory there could be a similar async-std-based implementation.
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use pretty_hex::PrettyHex;
|
||||
use rtsp_types::{Data, Message};
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Instant;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::Framed;
|
||||
use url::Host;
|
||||
|
||||
use crate::{Error, ErrorInt, RtspMessageContext};
|
||||
|
||||
use super::{ConnectionContext, ReceivedMessage, WallTime};
|
||||
|
||||
/// A RTSP connection which implements `Stream`, `Sink`, and `Unpin`.
|
||||
pub(crate) struct Connection(Framed<TcpStream, Codec>);
|
||||
|
||||
impl Connection {
|
||||
pub(crate) async fn connect(host: Host<&str>, port: u16) -> Result<Self, std::io::Error> {
|
||||
let stream = match host {
|
||||
Host::Domain(h) => TcpStream::connect((h, port)).await,
|
||||
Host::Ipv4(h) => TcpStream::connect((h, port)).await,
|
||||
Host::Ipv6(h) => TcpStream::connect((h, port)).await,
|
||||
}?;
|
||||
let established_wall = WallTime::now();
|
||||
let established = Instant::now();
|
||||
let local_addr = stream.local_addr()?;
|
||||
let peer_addr = stream.peer_addr()?;
|
||||
Ok(Self(Framed::new(
|
||||
stream,
|
||||
Codec {
|
||||
ctx: ConnectionContext {
|
||||
local_addr,
|
||||
peer_addr,
|
||||
established_wall,
|
||||
established,
|
||||
},
|
||||
read_pos: 0,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn ctx(&self) -> &ConnectionContext {
|
||||
&self.0.codec().ctx
|
||||
}
|
||||
|
||||
pub(crate) fn eof_ctx(&self) -> RtspMessageContext {
|
||||
RtspMessageContext {
|
||||
pos: self.0.codec().read_pos
|
||||
+ u64::try_from(self.0.read_buffer().remaining()).expect("usize fits in u64"),
|
||||
received_wall: WallTime::now(),
|
||||
received: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_write_err(&self, e: CodecError) -> ErrorInt {
|
||||
match e {
|
||||
CodecError::IoError(source) => ErrorInt::WriteError {
|
||||
conn_ctx: *self.ctx(),
|
||||
source,
|
||||
},
|
||||
CodecError::ParseError { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Connection {
|
||||
type Item = Result<ReceivedMessage, Error>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
self.0.poll_next_unpin(cx).map_err(|e| {
|
||||
wrap!(match e {
|
||||
CodecError::IoError(error) => ErrorInt::ReadError {
|
||||
conn_ctx: *self.ctx(),
|
||||
msg_ctx: self.eof_ctx(),
|
||||
source: error,
|
||||
},
|
||||
CodecError::ParseError { description, pos } => ErrorInt::RtspFramingError {
|
||||
conn_ctx: *self.ctx(),
|
||||
msg_ctx: RtspMessageContext {
|
||||
pos,
|
||||
received_wall: WallTime::now(),
|
||||
received: Instant::now(),
|
||||
},
|
||||
description,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<Message<Bytes>> for Connection {
|
||||
type Error = ErrorInt;
|
||||
|
||||
fn poll_ready(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
self.0
|
||||
.poll_ready_unpin(cx)
|
||||
.map_err(|e| self.wrap_write_err(e))
|
||||
}
|
||||
|
||||
fn start_send(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
item: Message<Bytes>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.0
|
||||
.start_send_unpin(item)
|
||||
.map_err(|e| self.wrap_write_err(e))
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
self.0
|
||||
.poll_flush_unpin(cx)
|
||||
.map_err(|e| self.wrap_write_err(e))
|
||||
}
|
||||
|
||||
fn poll_close(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
self.0
|
||||
.poll_close_unpin(cx)
|
||||
.map_err(|e| self.wrap_write_err(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes and decodes RTSP messages.
|
||||
struct Codec {
|
||||
ctx: ConnectionContext,
|
||||
|
||||
/// Number of bytes read and processed (drained from the input buffer).
|
||||
read_pos: u64,
|
||||
}
|
||||
|
||||
/// An intermediate error type that exists because [`Framed`] expects the
|
||||
/// codec's error type to implement `From<std::io::Error>`, and [`Error`]
|
||||
/// takes additional context.
|
||||
enum CodecError {
|
||||
IoError(std::io::Error),
|
||||
ParseError { description: String, pos: u64 },
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for CodecError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
CodecError::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl Codec {
|
||||
fn parse_msg(&self, src: &mut BytesMut) -> Result<Option<(usize, Message<Bytes>)>, CodecError> {
|
||||
if !src.is_empty() && src[0] == b'$' {
|
||||
// Fast path for interleaved data, avoiding MessageRef -> Message<&[u8]> ->
|
||||
// Message<Bytes> conversion. This speeds things up quite a bit in practice,
|
||||
// avoiding a bunch of memmove calls.
|
||||
if src.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
let channel_id = src[1];
|
||||
let len = 4 + usize::from(u16::from_be_bytes([src[2], src[3]]));
|
||||
if src.len() < len {
|
||||
src.reserve(len - src.len());
|
||||
return Ok(None);
|
||||
}
|
||||
let mut msg = src.split_to(len);
|
||||
msg.advance(4);
|
||||
return Ok(Some((
|
||||
len,
|
||||
Message::Data(Data::new(channel_id, msg.freeze())),
|
||||
)));
|
||||
}
|
||||
|
||||
let (msg, len): (Message<&[u8]>, _) = match Message::parse(src) {
|
||||
Ok((m, l)) => (m, l),
|
||||
Err(rtsp_types::ParseError::Error) => {
|
||||
const MAX_DUMP_BYTES: usize = 128;
|
||||
let conf = pretty_hex::HexConfig {
|
||||
title: false,
|
||||
..Default::default()
|
||||
};
|
||||
if src.len() > MAX_DUMP_BYTES {
|
||||
return Err(CodecError::ParseError {
|
||||
description: format!(
|
||||
"Invalid RTSP message; next {} of {} buffered bytes are:\n{:#?}",
|
||||
MAX_DUMP_BYTES,
|
||||
src.len(),
|
||||
(&src[0..MAX_DUMP_BYTES]).hex_conf(conf)
|
||||
),
|
||||
pos: self.read_pos,
|
||||
});
|
||||
}
|
||||
return Err(CodecError::ParseError {
|
||||
description: format!(
|
||||
"Invalid RTSP message; next {} buffered bytes are:\n{:#?}",
|
||||
MAX_DUMP_BYTES,
|
||||
src.hex_conf(conf)
|
||||
),
|
||||
pos: self.read_pos,
|
||||
});
|
||||
}
|
||||
Err(rtsp_types::ParseError::Incomplete) => return Ok(None),
|
||||
};
|
||||
|
||||
// Map msg's body to a Bytes representation and advance `src`. Awkward:
|
||||
// 1. lifetime concerns require mapping twice: first so the message
|
||||
// doesn't depend on the BytesMut, which needs to be split/advanced;
|
||||
// then to get the proper Bytes body in place post-split.
|
||||
// 2. rtsp_types messages must be AsRef<[u8]>, so we can't use the
|
||||
// range as an intermediate body.
|
||||
// 3. within a match because the rtsp_types::Message enum itself
|
||||
// doesn't have body/replace_body/map_body methods.
|
||||
let msg = match msg {
|
||||
Message::Request(msg) => {
|
||||
let body_range = crate::as_range(src, msg.body());
|
||||
let msg = msg.replace_body(rtsp_types::Empty);
|
||||
if let Some(r) = body_range {
|
||||
let mut raw_msg = src.split_to(len);
|
||||
raw_msg.advance(r.start);
|
||||
raw_msg.truncate(r.len());
|
||||
Message::Request(msg.replace_body(raw_msg.freeze()))
|
||||
} else {
|
||||
src.advance(len);
|
||||
Message::Request(msg.replace_body(Bytes::new()))
|
||||
}
|
||||
}
|
||||
Message::Response(msg) => {
|
||||
let body_range = crate::as_range(src, msg.body());
|
||||
let msg = msg.replace_body(rtsp_types::Empty);
|
||||
if let Some(r) = body_range {
|
||||
let mut raw_msg = src.split_to(len);
|
||||
raw_msg.advance(r.start);
|
||||
raw_msg.truncate(r.len());
|
||||
Message::Response(msg.replace_body(raw_msg.freeze()))
|
||||
} else {
|
||||
src.advance(len);
|
||||
Message::Response(msg.replace_body(Bytes::new()))
|
||||
}
|
||||
}
|
||||
Message::Data(_) => unreachable!(),
|
||||
};
|
||||
Ok(Some((len, msg)))
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_util::codec::Decoder for Codec {
|
||||
type Item = ReceivedMessage;
|
||||
type Error = CodecError;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
let (len, msg) = match self.parse_msg(src) {
|
||||
Err(e) => return Err(e),
|
||||
Ok(None) => return Ok(None),
|
||||
Ok(Some((len, msg))) => (len, msg),
|
||||
};
|
||||
let msg = ReceivedMessage {
|
||||
msg,
|
||||
ctx: RtspMessageContext {
|
||||
pos: self.read_pos,
|
||||
received_wall: WallTime::now(),
|
||||
received: Instant::now(),
|
||||
},
|
||||
};
|
||||
self.read_pos += u64::try_from(len).expect("usize fits in u64");
|
||||
Ok(Some(msg))
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_util::codec::Encoder<rtsp_types::Message<Bytes>> for Codec {
|
||||
type Error = CodecError;
|
||||
|
||||
fn encode(
|
||||
&mut self,
|
||||
item: rtsp_types::Message<Bytes>,
|
||||
mut dst: &mut BytesMut,
|
||||
) -> Result<(), Self::Error> {
|
||||
item.write(&mut (&mut dst).writer())
|
||||
.expect("BufMut Writer is infallible");
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user