1use std::time::Duration;
2
3use seedlink_rs_protocol::{PayloadFormat, PayloadSubformat, RawFrame, SequenceNumber};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum ClientState {
10 Disconnected,
12 Connected,
14 Configured,
16 Streaming,
18}
19
20impl ClientState {
21 pub fn as_str(&self) -> &'static str {
23 match self {
24 Self::Disconnected => "Disconnected",
25 Self::Connected => "Connected",
26 Self::Configured => "Configured",
27 Self::Streaming => "Streaming",
28 }
29 }
30}
31
32pub struct ClientConfig {
34 pub connect_timeout: Duration,
36 pub read_timeout: Duration,
38 pub prefer_v4: bool,
40}
41
42impl Default for ClientConfig {
43 fn default() -> Self {
44 Self {
45 connect_timeout: Duration::from_secs(10),
46 read_timeout: Duration::from_secs(30),
47 prefer_v4: true,
48 }
49 }
50}
51
52#[derive(Clone, Debug)]
54pub struct ServerInfo {
55 pub software: String,
57 pub version: String,
59 pub organization: String,
61 pub capabilities: Vec<String>,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Hash)]
67pub struct StationKey {
68 pub network: String,
70 pub station: String,
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
76pub enum OwnedFrame {
77 V3 {
79 sequence: SequenceNumber,
81 payload: Vec<u8>,
83 },
84 V4 {
86 format: PayloadFormat,
88 subformat: PayloadSubformat,
90 sequence: SequenceNumber,
92 station_id: String,
94 payload: Vec<u8>,
96 },
97}
98
99impl OwnedFrame {
100 pub fn sequence(&self) -> SequenceNumber {
102 match self {
103 Self::V3 { sequence, .. } | Self::V4 { sequence, .. } => *sequence,
104 }
105 }
106
107 pub fn payload(&self) -> &[u8] {
109 match self {
110 Self::V3 { payload, .. } | Self::V4 { payload, .. } => payload,
111 }
112 }
113
114 pub fn station_key(&self) -> Option<StationKey> {
121 match self {
122 Self::V3 { payload, .. } => {
123 if payload.len() >= 20 {
124 let station = std::str::from_utf8(&payload[8..13]).ok()?.trim().to_owned();
125 let network = std::str::from_utf8(&payload[18..20])
126 .ok()?
127 .trim()
128 .to_owned();
129 if !station.is_empty() && !network.is_empty() {
130 Some(StationKey { network, station })
131 } else {
132 None
133 }
134 } else {
135 None
136 }
137 }
138 Self::V4 { station_id, .. } => {
139 station_id
140 .split_once('_')
141 .map(|(network, station)| StationKey {
142 network: network.to_owned(),
143 station: station.to_owned(),
144 })
145 }
146 }
147 }
148
149 pub fn decode(&self) -> seedlink_rs_protocol::Result<seedlink_rs_protocol::DataFrame> {
153 self.as_raw_frame().decode()
154 }
155
156 fn as_raw_frame(&self) -> RawFrame<'_> {
157 match self {
158 Self::V3 { sequence, payload } => RawFrame::V3 {
159 sequence: *sequence,
160 payload,
161 },
162 Self::V4 {
163 format,
164 subformat,
165 sequence,
166 station_id,
167 payload,
168 } => RawFrame::V4 {
169 format: *format,
170 subformat: *subformat,
171 sequence: *sequence,
172 station_id,
173 payload,
174 },
175 }
176 }
177}
178
179impl<'a> From<RawFrame<'a>> for OwnedFrame {
180 fn from(raw: RawFrame<'a>) -> Self {
181 match raw {
182 RawFrame::V3 { sequence, payload } => Self::V3 {
183 sequence,
184 payload: payload.to_vec(),
185 },
186 RawFrame::V4 {
187 format,
188 subformat,
189 sequence,
190 station_id,
191 payload,
192 } => Self::V4 {
193 format,
194 subformat,
195 sequence,
196 station_id: station_id.to_owned(),
197 payload: payload.to_vec(),
198 },
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn decode_zeroed_payload_returns_err() {
209 let frame = OwnedFrame::V3 {
210 sequence: SequenceNumber::new(1),
211 payload: vec![0u8; 512],
212 };
213 assert!(frame.decode().is_err());
214 }
215
216 #[test]
217 fn as_raw_frame_roundtrip() {
218 let frame = OwnedFrame::V3 {
219 sequence: SequenceNumber::new(42),
220 payload: vec![0xAA; 512],
221 };
222 let raw = frame.as_raw_frame();
223 assert_eq!(raw.sequence(), SequenceNumber::new(42));
224 assert_eq!(raw.payload().len(), 512);
225 }
226}