Skip to main content

rd_rds/
header.rs

1use crate::{ByteCursor, Error};
2
3/// The serialized RDS stream header.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct Header {
6    pub format_version: u32,
7    pub writer_version: RVersion,
8    pub minimum_reader_version: RVersion,
9    pub native_encoding: Option<String>,
10}
11
12impl Header {
13    pub fn parse(cursor: &mut ByteCursor<'_>) -> Result<Self, Error> {
14        let marker_offset = cursor.position();
15        let marker = cursor.read_exact(2)?;
16        if marker != b"X\n" {
17            return Err(Error::UnsupportedMarker {
18                marker: [marker[0], marker[1]],
19                offset: marker_offset,
20            });
21        }
22
23        let version_offset = cursor.position();
24        let format_version = cursor.read_be_u32()?;
25        if format_version != 2 && format_version != 3 {
26            return Err(Error::UnsupportedVersion {
27                version: format_version,
28                offset: version_offset,
29            });
30        }
31
32        let writer_version = RVersion::from_raw(cursor.read_be_u32()?);
33        let minimum_reader_version = RVersion::from_raw(cursor.read_be_u32()?);
34
35        let native_encoding = if format_version == 3 {
36            let byte_len = cursor.read_be_u32()? as usize;
37            let encoding_offset = cursor.position();
38            let encoding = cursor.read_exact(byte_len)?;
39            Some(
40                String::from_utf8(encoding.to_vec()).map_err(|_| Error::InvalidUtf8 {
41                    offset: encoding_offset,
42                })?,
43            )
44        } else {
45            None
46        };
47
48        Ok(Self {
49            format_version,
50            writer_version,
51            minimum_reader_version,
52            native_encoding,
53        })
54    }
55}
56
57/// An R version encoded in the serializer's packed `major * 65536 + minor * 256 + patch` form.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct RVersion {
60    raw: u32,
61}
62
63impl RVersion {
64    pub fn from_raw(raw: u32) -> Self {
65        Self { raw }
66    }
67
68    pub fn raw(self) -> u32 {
69        self.raw
70    }
71
72    pub fn components(self) -> (u32, u32, u32) {
73        (self.raw >> 16, (self.raw >> 8) & 0xff, self.raw & 0xff)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn truncated_input_returns_offset() {
83        let mut cursor = ByteCursor::new(b"X\n");
84        let err = Header::parse(&mut cursor).unwrap_err();
85        assert!(matches!(
86            err,
87            Error::UnexpectedEof {
88                offset: 2,
89                needed: 4,
90                remaining: 0
91            }
92        ));
93    }
94
95    #[test]
96    fn wrong_marker_is_reported() {
97        let mut cursor = ByteCursor::new(b"A\n\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00");
98        let err = Header::parse(&mut cursor).unwrap_err();
99        assert!(matches!(
100            err,
101            Error::UnsupportedMarker {
102                marker: [b'A', b'\n'],
103                offset: 0
104            }
105        ));
106    }
107
108    #[test]
109    fn unsupported_version_is_reported() {
110        let mut cursor = ByteCursor::new(b"X\n\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00");
111        let err = Header::parse(&mut cursor).unwrap_err();
112        assert!(matches!(
113            err,
114            Error::UnsupportedVersion {
115                version: 4,
116                offset: 2
117            }
118        ));
119    }
120}