use std::time::Duration;
use seedlink_rs_protocol::{PayloadFormat, PayloadSubformat, RawFrame, SequenceNumber};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClientState {
Disconnected,
Connected,
Configured,
Streaming,
}
impl ClientState {
pub fn as_str(&self) -> &'static str {
match self {
Self::Disconnected => "Disconnected",
Self::Connected => "Connected",
Self::Configured => "Configured",
Self::Streaming => "Streaming",
}
}
}
pub struct ClientConfig {
pub connect_timeout: Duration,
pub read_timeout: Duration,
pub prefer_v4: bool,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
connect_timeout: Duration::from_secs(10),
read_timeout: Duration::from_secs(30),
prefer_v4: true,
}
}
}
#[derive(Clone, Debug)]
pub struct ServerInfo {
pub software: String,
pub version: String,
pub organization: String,
pub capabilities: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StationKey {
pub network: String,
pub station: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OwnedFrame {
V3 {
sequence: SequenceNumber,
payload: Vec<u8>,
},
V4 {
format: PayloadFormat,
subformat: PayloadSubformat,
sequence: SequenceNumber,
station_id: String,
payload: Vec<u8>,
},
}
impl OwnedFrame {
pub fn sequence(&self) -> SequenceNumber {
match self {
Self::V3 { sequence, .. } | Self::V4 { sequence, .. } => *sequence,
}
}
pub fn payload(&self) -> &[u8] {
match self {
Self::V3 { payload, .. } | Self::V4 { payload, .. } => payload,
}
}
pub fn station_key(&self) -> Option<StationKey> {
match self {
Self::V3 { payload, .. } => {
if payload.len() >= 20 {
let station = std::str::from_utf8(&payload[8..13]).ok()?.trim().to_owned();
let network = std::str::from_utf8(&payload[18..20])
.ok()?
.trim()
.to_owned();
if !station.is_empty() && !network.is_empty() {
Some(StationKey { network, station })
} else {
None
}
} else {
None
}
}
Self::V4 { station_id, .. } => {
station_id
.split_once('_')
.map(|(network, station)| StationKey {
network: network.to_owned(),
station: station.to_owned(),
})
}
}
}
pub fn decode(&self) -> seedlink_rs_protocol::Result<seedlink_rs_protocol::DataFrame> {
self.as_raw_frame().decode()
}
fn as_raw_frame(&self) -> RawFrame<'_> {
match self {
Self::V3 { sequence, payload } => RawFrame::V3 {
sequence: *sequence,
payload,
},
Self::V4 {
format,
subformat,
sequence,
station_id,
payload,
} => RawFrame::V4 {
format: *format,
subformat: *subformat,
sequence: *sequence,
station_id,
payload,
},
}
}
}
impl<'a> From<RawFrame<'a>> for OwnedFrame {
fn from(raw: RawFrame<'a>) -> Self {
match raw {
RawFrame::V3 { sequence, payload } => Self::V3 {
sequence,
payload: payload.to_vec(),
},
RawFrame::V4 {
format,
subformat,
sequence,
station_id,
payload,
} => Self::V4 {
format,
subformat,
sequence,
station_id: station_id.to_owned(),
payload: payload.to_vec(),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_zeroed_payload_returns_err() {
let frame = OwnedFrame::V3 {
sequence: SequenceNumber::new(1),
payload: vec![0u8; 512],
};
assert!(frame.decode().is_err());
}
#[test]
fn as_raw_frame_roundtrip() {
let frame = OwnedFrame::V3 {
sequence: SequenceNumber::new(42),
payload: vec![0xAA; 512],
};
let raw = frame.as_raw_frame();
assert_eq!(raw.sequence(), SequenceNumber::new(42));
assert_eq!(raw.payload().len(), 512);
}
}