Skip to main content

oaat_core/
capability.rs

1use crate::error::OaatError;
2use serde::{Deserialize, Serialize};
3
4/// Parsed capability string from mDNS TXT records.
5/// Format: `pcm:<max_rate_khz>/<max_bits>[,dsd:<max_multiplier>][,flac][,opus]`
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct Capabilities {
8    pub pcm_max_rate_khz: u32,
9    pub pcm_max_bits: u8,
10    pub dsd_max_multiplier: Option<u16>,
11    pub flac: bool,
12    pub opus: bool,
13}
14
15impl Capabilities {
16    pub fn parse(s: &str) -> Result<Self, OaatError> {
17        let mut pcm_max_rate_khz = 0u32;
18        let mut pcm_max_bits = 0u8;
19        let mut dsd_max_multiplier = None;
20        let mut flac = false;
21        let mut opus = false;
22
23        for part in s.split(',') {
24            let part = part.trim();
25            if let Some(pcm) = part.strip_prefix("pcm:") {
26                let (rate_s, bits_s) = pcm
27                    .split_once('/')
28                    .ok_or_else(|| OaatError::InvalidCapabilityString(s.to_owned()))?;
29                pcm_max_rate_khz = rate_s
30                    .parse()
31                    .map_err(|_| OaatError::InvalidCapabilityString(s.to_owned()))?;
32                pcm_max_bits = bits_s
33                    .parse()
34                    .map_err(|_| OaatError::InvalidCapabilityString(s.to_owned()))?;
35            } else if let Some(dsd) = part.strip_prefix("dsd:") {
36                dsd_max_multiplier = Some(
37                    dsd.parse()
38                        .map_err(|_| OaatError::InvalidCapabilityString(s.to_owned()))?,
39                );
40            } else if part == "flac" {
41                flac = true;
42            } else if part == "opus" {
43                opus = true;
44            }
45        }
46
47        if pcm_max_rate_khz == 0 {
48            return Err(OaatError::InvalidCapabilityString(s.to_owned()));
49        }
50
51        Ok(Self {
52            pcm_max_rate_khz,
53            pcm_max_bits,
54            dsd_max_multiplier,
55            flac,
56            opus,
57        })
58    }
59
60    pub fn pcm_max_rate_hz(&self) -> u32 {
61        self.pcm_max_rate_khz * 1000
62    }
63}
64
65impl std::fmt::Display for Capabilities {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "pcm:{}/{}", self.pcm_max_rate_khz, self.pcm_max_bits)?;
68        if let Some(dsd) = self.dsd_max_multiplier {
69            write!(f, ",dsd:{dsd}")?;
70        }
71        if self.flac {
72            write!(f, ",flac")?;
73        }
74        if self.opus {
75            write!(f, ",opus")?;
76        }
77        Ok(())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn parse_pcm_only() {
87        let caps = Capabilities::parse("pcm:192/24").unwrap();
88        assert_eq!(caps.pcm_max_rate_khz, 192);
89        assert_eq!(caps.pcm_max_bits, 24);
90        assert_eq!(caps.dsd_max_multiplier, None);
91        assert!(!caps.flac);
92    }
93
94    #[test]
95    fn parse_full() {
96        let caps = Capabilities::parse("pcm:768/32,dsd:256,flac,opus").unwrap();
97        assert_eq!(caps.pcm_max_rate_khz, 768);
98        assert_eq!(caps.pcm_max_bits, 32);
99        assert_eq!(caps.dsd_max_multiplier, Some(256));
100        assert!(caps.flac);
101        assert!(caps.opus);
102    }
103
104    #[test]
105    fn roundtrip() {
106        let original = "pcm:768/32,dsd:256,flac";
107        let caps = Capabilities::parse(original).unwrap();
108        assert_eq!(caps.to_string(), original);
109    }
110
111    #[test]
112    fn invalid() {
113        assert!(Capabilities::parse("dsd:256").is_err());
114        assert!(Capabilities::parse("garbage").is_err());
115    }
116}