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#[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 #[must_use]
102 pub fn host(&self) -> &[u8] {
103 &self.host
104 }
105
106 #[must_use]
108 pub const fn port(&self) -> u16 {
109 self.port
110 }
111}
112
113#[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 #[must_use]
148 pub fn trackers(&self) -> &[Vec<Vec<u8>>] {
149 &self.trackers
150 }
151
152 #[must_use]
154 pub fn web_seeds(&self) -> &[Vec<u8>] {
155 &self.web_seeds
156 }
157
158 #[must_use]
160 pub fn nodes(&self) -> &[DhtNode] {
161 &self.nodes
162 }
163
164 #[must_use]
166 pub fn source(&self) -> Option<&[u8]> {
167 self.source.as_deref()
168 }
169
170 #[must_use]
172 pub fn comment(&self) -> Option<&[u8]> {
173 self.comment.as_deref()
174 }
175
176 #[must_use]
178 pub fn created_by(&self) -> Option<&[u8]> {
179 self.created_by.as_deref()
180 }
181
182 #[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}