Skip to main content

dvb_si/descriptors/ait/
dvb_j_application_location.rs

1//! DVB-J Application Location Descriptor — ETSI TS 102 727 §10.9.2, Table 85
2//! (AIT tag 0x04).
3//!
4//! Carried in the AIT per-application descriptor loop. Contains the base
5//! directory, classpath extension, and initial class name for a DVB-J
6//! application. The three fields are text strings, modelled as [`DvbText`] to
7//! match the sibling `simple_application_location` descriptor (AIT tag 0x15).
8
9use crate::descriptors::descriptor_body;
10use crate::error::{Error, Result};
11use crate::text::DvbText;
12use dvb_common::{Parse, Serialize};
13
14/// Descriptor tag for dvb_j_application_location_descriptor (AIT namespace).
15pub const TAG: u8 = 0x04;
16const HEADER_LEN: usize = 2;
17
18/// DVB-J Application Location Descriptor (AIT tag 0x04).
19#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct DvbJApplicationLocationDescriptor<'a> {
22    /// Base directory string (slash-delimited; "/" for root). Shall be non-empty.
23    pub base_directory: DvbText<'a>,
24    /// Classpath extension string (elements `;`-delimited, dirs `/`-delimited).
25    pub classpath_extension: DvbText<'a>,
26    /// Initial DVB-J class name — consumes the remainder of the descriptor.
27    pub initial_class: DvbText<'a>,
28}
29
30impl<'a> Parse<'a> for DvbJApplicationLocationDescriptor<'a> {
31    type Error = crate::error::Error;
32    fn parse(bytes: &'a [u8]) -> Result<Self> {
33        let body = descriptor_body(
34            bytes,
35            TAG,
36            "DvbJApplicationLocationDescriptor",
37            "unexpected tag for dvb_j_application_location_descriptor",
38        )?;
39        if body.is_empty() {
40            return Err(Error::InvalidDescriptor {
41                tag: TAG,
42                reason: "dvb_j_application_location_descriptor body is empty",
43            });
44        }
45        let base_dir_len = body[0] as usize;
46        let end = 1 + base_dir_len;
47        if body.len() < end {
48            return Err(Error::InvalidDescriptor {
49                tag: TAG,
50                reason: "dvb_j_application_location_descriptor base_directory overruns body",
51            });
52        }
53        let base_directory = &body[1..end];
54        let rest = &body[end..];
55        if rest.is_empty() {
56            return Err(Error::InvalidDescriptor {
57                tag: TAG,
58                reason: "dvb_j_application_location_descriptor missing classpath_extension",
59            });
60        }
61        let cp_len = rest[0] as usize;
62        let cp_end = 1 + cp_len;
63        if rest.len() < cp_end {
64            return Err(Error::InvalidDescriptor {
65                tag: TAG,
66                reason: "dvb_j_application_location_descriptor classpath_extension overruns body",
67            });
68        }
69        Ok(Self {
70            base_directory: DvbText::new(base_directory),
71            classpath_extension: DvbText::new(&rest[1..cp_end]),
72            initial_class: DvbText::new(&rest[cp_end..]),
73        })
74    }
75}
76
77impl Serialize for DvbJApplicationLocationDescriptor<'_> {
78    type Error = crate::error::Error;
79    fn serialized_len(&self) -> usize {
80        HEADER_LEN
81            + 1
82            + self.base_directory.len()
83            + 1
84            + self.classpath_extension.len()
85            + self.initial_class.len()
86    }
87
88    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
89        if self.base_directory.len() > u8::MAX as usize {
90            return Err(Error::InvalidDescriptor {
91                tag: TAG,
92                reason: "dvb_j_application_location_descriptor base_directory exceeds 255 bytes",
93            });
94        }
95        if self.classpath_extension.len() > u8::MAX as usize {
96            return Err(Error::InvalidDescriptor {
97                tag: TAG,
98                reason:
99                    "dvb_j_application_location_descriptor classpath_extension exceeds 255 bytes",
100            });
101        }
102        let body_len = self.serialized_len() - HEADER_LEN;
103        if body_len > u8::MAX as usize {
104            return Err(Error::InvalidDescriptor {
105                tag: TAG,
106                reason: "dvb_j_application_location_descriptor body exceeds 255 bytes",
107            });
108        }
109        let len = self.serialized_len();
110        if buf.len() < len {
111            return Err(Error::OutputBufferTooSmall {
112                need: len,
113                have: buf.len(),
114            });
115        }
116        buf[0] = TAG;
117        buf[1] = body_len as u8;
118        let mut pos = HEADER_LEN;
119        buf[pos] = self.base_directory.len() as u8;
120        pos += 1;
121        buf[pos..pos + self.base_directory.len()].copy_from_slice(self.base_directory.raw());
122        pos += self.base_directory.len();
123        buf[pos] = self.classpath_extension.len() as u8;
124        pos += 1;
125        buf[pos..pos + self.classpath_extension.len()]
126            .copy_from_slice(self.classpath_extension.raw());
127        pos += self.classpath_extension.len();
128        buf[pos..pos + self.initial_class.len()].copy_from_slice(self.initial_class.raw());
129        Ok(len)
130    }
131}
132
133impl<'a> crate::traits::DescriptorDef<'a> for DvbJApplicationLocationDescriptor<'a> {
134    const TAG: u8 = TAG;
135    const NAME: &'static str = "DVB_J_APPLICATION_LOCATION";
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn parse_full() {
144        let bytes = [
145            TAG, 10, // descriptor_length = 10
146            1, b'/', // base_directory_len=1, base_directory="/"
147            5, b'l', b'i', b'b', b'/', b';', // classpath_extension_len=5, "lib/;"
148            b'A', b'B', // initial_class = "AB" (no length prefix — consumes rest)
149        ];
150        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
151        assert_eq!(d.base_directory.raw(), b"/");
152        assert_eq!(d.classpath_extension.raw(), b"lib/;");
153        assert_eq!(d.initial_class.raw(), b"AB");
154    }
155
156    #[test]
157    fn parse_no_initial_class() {
158        // Per spec, initial_class is the REST — it can be empty
159        let bytes = [
160            TAG, 4, // descriptor_length = 4
161            1, b'/', // base_directory_len=1, "/"
162            1,
163            b';', // classpath_extension_len=1, ";"
164                  // initial_class = empty
165        ];
166        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
167        assert_eq!(d.base_directory.raw(), b"/");
168        assert_eq!(d.classpath_extension.raw(), b";");
169        assert!(d.initial_class.raw().is_empty());
170    }
171
172    #[test]
173    fn serialize_round_trip() {
174        let d = DvbJApplicationLocationDescriptor {
175            base_directory: DvbText::new(b"/apps"),
176            classpath_extension: DvbText::new(b"classes/"),
177            initial_class: DvbText::new(b"com.example.Main"),
178        };
179        let mut buf = vec![0u8; d.serialized_len()];
180        d.serialize_into(&mut buf).unwrap();
181        let re = DvbJApplicationLocationDescriptor::parse(&buf).unwrap();
182        assert_eq!(d, re);
183    }
184
185    #[test]
186    fn serialize_byte_identical() {
187        let bytes = [
188            TAG, 9, // descriptor_length = 9
189            1, b'/', // base_directory = "/"
190            4, b'a', b';', b'b', b';', // classpath_extension = "a;b;"
191            b'c', b'd', // initial_class = "cd" (no length prefix — consumes rest)
192        ];
193        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
194        let mut buf = vec![0u8; d.serialized_len()];
195        d.serialize_into(&mut buf).unwrap();
196        assert_eq!(buf.as_slice(), &bytes[..]);
197    }
198}