use async_trait::async_trait;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum StreamType {
Audio = 0x20,
Video = 0x21,
Screen = 0x22,
RtcpFeedback = 0x23,
Data = 0x24,
}
impl StreamType {
#[must_use]
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn try_from_u8(val: u8) -> Option<Self> {
match val {
0x20 => Some(StreamType::Audio),
0x21 => Some(StreamType::Video),
0x22 => Some(StreamType::Screen),
0x23 => Some(StreamType::RtcpFeedback),
0x24 => Some(StreamType::Data),
_ => None,
}
}
}
#[derive(Error, Debug, Clone)]
pub enum LinkTransportError {
#[error("Connection not established")]
NotConnected,
#[error("Peer not found: {0}")]
PeerNotFound(String),
#[error("Send failed: {0}")]
SendError(String),
#[error("Receive failed: {0}")]
ReceiveError(String),
#[error("Invalid stream type: {0}")]
InvalidStreamType(u8),
#[error("IO error: {0}")]
IoError(String),
}
#[derive(Debug, Clone)]
pub struct PeerConnection {
pub peer_id: String,
pub remote_addr: SocketAddr,
}
#[async_trait]
pub trait LinkTransport: Send + Sync {
async fn start(&mut self) -> Result<(), LinkTransportError>;
async fn stop(&mut self) -> Result<(), LinkTransportError>;
#[must_use]
async fn is_running(&self) -> bool;
async fn local_addr(&self) -> Result<SocketAddr, LinkTransportError>;
async fn connect(&mut self, addr: SocketAddr) -> Result<PeerConnection, LinkTransportError>;
async fn accept(&mut self) -> Result<Option<PeerConnection>, LinkTransportError>;
async fn send(
&self,
peer: &PeerConnection,
stream_type: StreamType,
data: &[u8],
) -> Result<(), LinkTransportError>;
async fn receive(&self) -> Result<(PeerConnection, StreamType, Vec<u8>), LinkTransportError>;
async fn send_default(
&self,
stream_type: StreamType,
data: &[u8],
) -> Result<(), LinkTransportError> {
self.send(&self.default_peer()?, stream_type, data).await
}
fn default_peer(&self) -> Result<PeerConnection, LinkTransportError> {
Err(LinkTransportError::NotConnected)
}
fn set_default_peer(&mut self, peer: PeerConnection) -> Result<(), LinkTransportError> {
let _ = peer;
Err(LinkTransportError::NotConnected)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_type_conversions() {
assert_eq!(StreamType::Audio.as_u8(), 0x20);
assert_eq!(StreamType::Video.as_u8(), 0x21);
assert_eq!(StreamType::Screen.as_u8(), 0x22);
assert_eq!(StreamType::RtcpFeedback.as_u8(), 0x23);
assert_eq!(StreamType::Data.as_u8(), 0x24);
}
#[test]
fn test_stream_type_from_u8() {
assert_eq!(StreamType::try_from_u8(0x20), Some(StreamType::Audio));
assert_eq!(StreamType::try_from_u8(0x21), Some(StreamType::Video));
assert_eq!(StreamType::try_from_u8(0x22), Some(StreamType::Screen));
assert_eq!(
StreamType::try_from_u8(0x23),
Some(StreamType::RtcpFeedback)
);
assert_eq!(StreamType::try_from_u8(0x24), Some(StreamType::Data));
assert_eq!(StreamType::try_from_u8(0x25), None);
assert_eq!(StreamType::try_from_u8(0xFF), None);
}
#[test]
fn test_stream_type_roundtrip() {
let types = vec![
StreamType::Audio,
StreamType::Video,
StreamType::Screen,
StreamType::RtcpFeedback,
StreamType::Data,
];
for original in types {
let byte = original.as_u8();
let recovered = StreamType::try_from_u8(byte);
assert_eq!(recovered, Some(original));
}
}
}