btpc_core/
metadata.rs

1#[derive(Clone, Debug, Eq, PartialEq)]
2pub(crate) struct TrackerUrl(Vec<u8>);
3
4impl TrackerUrl {
5    pub(crate) fn new(value: Vec<u8>) -> Self {
6        Self(value)
7    }
8
9    pub(crate) fn as_bytes(&self) -> &[u8] {
10        &self.0
11    }
12
13    pub(crate) fn into_bytes(self) -> Vec<u8> {
14        self.0
15    }
16}
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub(crate) struct TrackerTier(Vec<TrackerUrl>);
20
21impl TrackerTier {
22    pub(crate) fn new(values: Vec<Vec<u8>>) -> Self {
23        Self(values.into_iter().map(TrackerUrl::new).collect())
24    }
25
26    pub(crate) fn urls(&self) -> &[TrackerUrl] {
27        &self.0
28    }
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub(crate) struct WebSeed(Vec<u8>);
33
34impl WebSeed {
35    pub(crate) fn new(value: Vec<u8>) -> Self {
36        Self(value)
37    }
38
39    pub(crate) fn into_bytes(self) -> Vec<u8> {
40        self.0
41    }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub(crate) struct NodeHost(Vec<u8>);
46
47impl NodeHost {
48    pub(crate) fn new(value: Vec<u8>) -> Self {
49        Self(value)
50    }
51
52    pub(crate) fn as_bytes(&self) -> &[u8] {
53        &self.0
54    }
55}
56
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub(crate) struct MetadataText(Vec<u8>);
59
60impl MetadataText {
61    pub(crate) fn new(value: Vec<u8>) -> Self {
62        Self(value)
63    }
64
65    pub(crate) fn as_bytes(&self) -> &[u8] {
66        &self.0
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::{MetadataText, NodeHost, TrackerTier, WebSeed};
73
74    #[test]
75    fn field_types_preserve_non_utf8_bytes_without_interchange() {
76        let tier = TrackerTier::new(vec![vec![0xff, b't']]);
77        let seed = WebSeed::new(vec![0xfe, b'w']);
78        let host = NodeHost::new(vec![0xfd, b'n']);
79        let text = MetadataText::new(vec![0xfc, b'x']);
80        assert_eq!(tier.urls()[0].as_bytes(), &[0xff, b't']);
81        assert_eq!(seed.into_bytes(), vec![0xfe, b'w']);
82        assert_eq!(host.as_bytes(), &[0xfd, b'n']);
83        assert_eq!(text.as_bytes(), &[0xfc, b'x']);
84    }
85}
86use crate::{Error, Result};
87
88/// One validated DHT bootstrap node.
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct DhtNode {
91    host: Vec<u8>,
92    port: u16,
93}
94
95impl DhtNode {
96    pub(crate) fn new(host: Vec<u8>, port: u16) -> Self {
97        Self { host, port }
98    }
99
100    /// Returns the raw host bytes.
101    #[must_use]
102    pub fn host(&self) -> &[u8] {
103        &self.host
104    }
105
106    /// Returns the non-zero port.
107    #[must_use]
108    pub const fn port(&self) -> u16 {
109        self.port
110    }
111}
112
113/// Validated optional metainfo fields shared by parsing and inspection surfaces.
114#[derive(Clone, Debug, Default, Eq, PartialEq)]
115pub struct OptionalMetadata {
116    trackers: Vec<Vec<Vec<u8>>>,
117    web_seeds: Vec<Vec<u8>>,
118    nodes: Vec<DhtNode>,
119    source: Option<Vec<u8>>,
120    comment: Option<Vec<u8>>,
121    created_by: Option<Vec<u8>>,
122    creation_date: Option<i64>,
123}
124
125impl OptionalMetadata {
126    pub(crate) fn new(
127        trackers: Vec<Vec<Vec<u8>>>,
128        web_seeds: Vec<Vec<u8>>,
129        nodes: Vec<DhtNode>,
130        source: Option<Vec<u8>>,
131        comment: Option<Vec<u8>>,
132        created_by: Option<Vec<u8>>,
133        creation_date: Option<i64>,
134    ) -> Self {
135        Self {
136            trackers,
137            web_seeds,
138            nodes,
139            source,
140            comment,
141            created_by,
142            creation_date,
143        }
144    }
145
146    /// Returns tracker tiers as raw byte strings.
147    #[must_use]
148    pub fn trackers(&self) -> &[Vec<Vec<u8>>] {
149        &self.trackers
150    }
151
152    /// Returns web seed URLs as raw byte strings.
153    #[must_use]
154    pub fn web_seeds(&self) -> &[Vec<u8>] {
155        &self.web_seeds
156    }
157
158    /// Returns validated DHT bootstrap nodes.
159    #[must_use]
160    pub fn nodes(&self) -> &[DhtNode] {
161        &self.nodes
162    }
163
164    /// Returns raw source bytes.
165    #[must_use]
166    pub fn source(&self) -> Option<&[u8]> {
167        self.source.as_deref()
168    }
169
170    /// Returns raw comment bytes.
171    #[must_use]
172    pub fn comment(&self) -> Option<&[u8]> {
173        self.comment.as_deref()
174    }
175
176    /// Returns raw creator bytes.
177    #[must_use]
178    pub fn created_by(&self) -> Option<&[u8]> {
179        self.created_by.as_deref()
180    }
181
182    /// Returns the non-negative Unix timestamp.
183    #[must_use]
184    pub const fn creation_date(&self) -> Option<i64> {
185        self.creation_date
186    }
187}
188
189pub(crate) fn validate_tracker_tiers(trackers: &[Vec<Vec<u8>>]) -> Result<()> {
190    if trackers.iter().any(Vec::is_empty) {
191        return Err(Error::metainfo_field(
192            "announce-list",
193            "tracker tier is empty",
194        ));
195    }
196    if trackers.iter().flatten().any(Vec::is_empty) {
197        return Err(Error::metainfo_field("announce", "tracker URL is empty"));
198    }
199    Ok(())
200}
201
202pub(crate) fn validate_web_seeds(web_seeds: &[Vec<u8>]) -> Result<()> {
203    if web_seeds.iter().any(Vec::is_empty) {
204        return Err(Error::metainfo_field("url-list", "web seed URL is empty"));
205    }
206    Ok(())
207}
208
209pub(crate) fn validate_nodes(nodes: &[(Vec<u8>, u16)]) -> Result<()> {
210    if nodes.iter().any(|(host, _)| host.is_empty()) {
211        return Err(Error::metainfo_field("nodes", "node host is empty"));
212    }
213    if nodes.iter().any(|(_, port)| *port == 0) {
214        return Err(Error::metainfo_field(
215            "nodes",
216            "node port must be 1..=65535",
217        ));
218    }
219    Ok(())
220}
221
222pub(crate) fn validate_creation_date(creation_date: Option<i64>) -> Result<()> {
223    if creation_date.is_some_and(|value| value < 0) {
224        return Err(Error::metainfo_field(
225            "creation date",
226            "must be a non-negative Unix timestamp",
227        ));
228    }
229    Ok(())
230}