Skip to main content

snapcast_proto/
types.rs

1//! Shared types used across the protocol.
2
3use std::io::{self, Read, Write};
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6
7/// Timestamp with second and microsecond components.
8///
9/// Matches the C++ `tv` struct used throughout the Snapcast protocol.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub struct Timeval {
12    /// Seconds component.
13    pub sec: i32,
14    /// Microseconds component.
15    pub usec: i32,
16}
17
18impl Timeval {
19    /// Create from microseconds since epoch.
20    pub fn from_usec(usec: i64) -> Self {
21        Self {
22            sec: (usec / 1_000_000) as i32,
23            usec: (usec % 1_000_000) as i32,
24        }
25    }
26
27    /// Convert to microseconds since epoch.
28    pub fn to_usec(self) -> i64 {
29        self.sec as i64 * 1_000_000 + self.usec as i64
30    }
31
32    /// Read a Timeval (8 bytes, little-endian) from a reader.
33    pub fn read_from<R: Read>(r: &mut R) -> io::Result<Self> {
34        Ok(Self {
35            sec: r.read_i32::<LittleEndian>()?,
36            usec: r.read_i32::<LittleEndian>()?,
37        })
38    }
39
40    /// Write a Timeval (8 bytes, little-endian) to a writer.
41    pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
42        w.write_i32::<LittleEndian>(self.sec)?;
43        w.write_i32::<LittleEndian>(self.usec)?;
44        Ok(())
45    }
46}
47
48impl std::ops::Add for Timeval {
49    type Output = Self;
50
51    fn add(self, rhs: Self) -> Self {
52        let mut sec = self.sec + rhs.sec;
53        let mut usec = self.usec + rhs.usec;
54        if usec >= 1_000_000 {
55            sec += usec / 1_000_000;
56            usec %= 1_000_000;
57        }
58        Self { sec, usec }
59    }
60}
61
62impl std::ops::Sub for Timeval {
63    type Output = Self;
64
65    fn sub(self, rhs: Self) -> Self {
66        let mut sec = self.sec - rhs.sec;
67        let mut usec = self.usec - rhs.usec;
68        while usec < 0 {
69            sec -= 1;
70            usec += 1_000_000;
71        }
72        Self { sec, usec }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn timeval_add() {
82        let a = Timeval {
83            sec: 1,
84            usec: 900_000,
85        };
86        let b = Timeval {
87            sec: 0,
88            usec: 200_000,
89        };
90        let result = a + b;
91        assert_eq!(
92            result,
93            Timeval {
94                sec: 2,
95                usec: 100_000
96            }
97        );
98    }
99
100    #[test]
101    fn timeval_sub() {
102        let a = Timeval {
103            sec: 2,
104            usec: 100_000,
105        };
106        let b = Timeval {
107            sec: 1,
108            usec: 900_000,
109        };
110        let result = a - b;
111        assert_eq!(
112            result,
113            Timeval {
114                sec: 0,
115                usec: 200_000
116            }
117        );
118    }
119
120    #[test]
121    fn timeval_round_trip() {
122        let tv = Timeval {
123            sec: 1000,
124            usec: 500_000,
125        };
126        let mut buf = Vec::new();
127        tv.write_to(&mut buf).unwrap();
128        assert_eq!(buf.len(), 8);
129        let mut cursor = io::Cursor::new(&buf);
130        let decoded = Timeval::read_from(&mut cursor).unwrap();
131        assert_eq!(tv, decoded);
132    }
133
134    #[test]
135    fn timeval_known_bytes() {
136        // sec=1000 (0x000003E8), usec=500000 (0x0007A120), little-endian
137        let expected: [u8; 8] = [0xE8, 0x03, 0x00, 0x00, 0x20, 0xA1, 0x07, 0x00];
138        let tv = Timeval {
139            sec: 1000,
140            usec: 500_000,
141        };
142        let mut buf = Vec::new();
143        tv.write_to(&mut buf).unwrap();
144        assert_eq!(buf.as_slice(), &expected);
145    }
146}