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 broadcast_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: "dvb_j_application_location_descriptor classpath_extension exceeds 255 bytes",
99            });
100        }
101        let body_len = self.serialized_len() - HEADER_LEN;
102        if body_len > u8::MAX as usize {
103            return Err(Error::InvalidDescriptor {
104                tag: TAG,
105                reason: "dvb_j_application_location_descriptor body exceeds 255 bytes",
106            });
107        }
108        let len = self.serialized_len();
109        if buf.len() < len {
110            return Err(Error::OutputBufferTooSmall {
111                need: len,
112                have: buf.len(),
113            });
114        }
115        buf[0] = TAG;
116        buf[1] = body_len as u8;
117        let mut pos = HEADER_LEN;
118        buf[pos] = self.base_directory.len() as u8;
119        pos += 1;
120        buf[pos..pos + self.base_directory.len()].copy_from_slice(self.base_directory.raw());
121        pos += self.base_directory.len();
122        buf[pos] = self.classpath_extension.len() as u8;
123        pos += 1;
124        buf[pos..pos + self.classpath_extension.len()]
125            .copy_from_slice(self.classpath_extension.raw());
126        pos += self.classpath_extension.len();
127        buf[pos..pos + self.initial_class.len()].copy_from_slice(self.initial_class.raw());
128        Ok(len)
129    }
130}
131
132impl<'a> crate::traits::DescriptorDef<'a> for DvbJApplicationLocationDescriptor<'a> {
133    const TAG: u8 = TAG;
134    const NAME: &'static str = "DVB_J_APPLICATION_LOCATION";
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn parse_full() {
143        let bytes = [
144            TAG, 10, // descriptor_length = 10
145            1, b'/', // base_directory_len=1, base_directory="/"
146            5, b'l', b'i', b'b', b'/', b';', // classpath_extension_len=5, "lib/;"
147            b'A', b'B', // initial_class = "AB" (no length prefix — consumes rest)
148        ];
149        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
150        assert_eq!(d.base_directory.raw(), b"/");
151        assert_eq!(d.classpath_extension.raw(), b"lib/;");
152        assert_eq!(d.initial_class.raw(), b"AB");
153    }
154
155    #[test]
156    fn parse_no_initial_class() {
157        // Per spec, initial_class is the REST — it can be empty
158        let bytes = [
159            TAG, 4, // descriptor_length = 4
160            1, b'/', // base_directory_len=1, "/"
161            1,
162            b';', // classpath_extension_len=1, ";"
163                  // initial_class = empty
164        ];
165        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
166        assert_eq!(d.base_directory.raw(), b"/");
167        assert_eq!(d.classpath_extension.raw(), b";");
168        assert!(d.initial_class.raw().is_empty());
169    }
170
171    #[test]
172    fn serialize_round_trip() {
173        let d = DvbJApplicationLocationDescriptor {
174            base_directory: DvbText::new(b"/apps"),
175            classpath_extension: DvbText::new(b"classes/"),
176            initial_class: DvbText::new(b"com.example.Main"),
177        };
178        let mut buf = vec![0u8; d.serialized_len()];
179        d.serialize_into(&mut buf).unwrap();
180        let re = DvbJApplicationLocationDescriptor::parse(&buf).unwrap();
181        assert_eq!(d, re);
182    }
183
184    #[test]
185    fn serialize_byte_identical() {
186        let bytes = [
187            TAG, 9, // descriptor_length = 9
188            1, b'/', // base_directory = "/"
189            4, b'a', b';', b'b', b';', // classpath_extension = "a;b;"
190            b'c', b'd', // initial_class = "cd" (no length prefix — consumes rest)
191        ];
192        let d = DvbJApplicationLocationDescriptor::parse(&bytes).unwrap();
193        let mut buf = vec![0u8; d.serialized_len()];
194        d.serialize_into(&mut buf).unwrap();
195        assert_eq!(buf.as_slice(), &bytes[..]);
196    }
197}