1use derive_more::Display;
2use std::collections::BTreeMap;
3use std::error::Error;
4use std::io::Write;
5use std::num::ParseIntError;
6use std::{fmt, io, str};
7use thiserror::Error;
8
9#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)]
11pub enum Element {
12 Integer(i64),
13 ByteString(Vec<u8>),
14 List(Vec<Element>),
15 Dictionary(BTreeMap<Element, Element>),
16}
17
18impl Element {
19 pub fn from_bytes(src: &[u8]) -> Result<Element, ParseError> {
21 match read_element(src) {
22 Ok((entity, _)) => Ok(entity),
23 Err(e) => Err(e),
24 }
25 }
26
27 pub fn from_bytes_with_len(src: &[u8]) -> Result<(Element, usize), ParseError> {
29 let (entity, rest) = read_element(src)?;
30 Ok((entity, src.len() - rest.len()))
31 }
32
33 pub fn encode(&self) -> Vec<u8> {
35 let mut dest = Vec::<u8>::new();
36 write_element(self, &mut dest);
37 dest
38 }
39}
40
41impl From<&str> for Element {
42 fn from(text: &str) -> Self {
43 Element::ByteString(Vec::<u8>::from(text))
44 }
45}
46
47impl From<String> for Element {
48 fn from(value: String) -> Self {
49 Element::ByteString(value.into_bytes())
50 }
51}
52
53impl From<i64> for Element {
54 fn from(number: i64) -> Self {
55 Element::Integer(number)
56 }
57}
58
59impl fmt::Display for Element {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 const INDENT_INCREMENT: usize = 3;
62 fn write_entity(
63 entity: &Element,
64 f: &mut fmt::Formatter<'_>,
65 last_indent: usize,
66 ignore_first_indent: bool,
67 ) -> fmt::Result {
68 let first_indent = if ignore_first_indent { 0 } else { last_indent };
69 match entity {
70 Element::ByteString(data) => match std::str::from_utf8(data) {
71 Ok(text) => write!(f, "{:first_indent$}\"{}\"", ' ', text)?,
72 Err(_) => write!(f, "{:first_indent$}<{} raw bytes>", ' ', data.len())?,
73 },
74 Element::Integer(number) => write!(f, "{:first_indent$}{}", ' ', number)?,
75 Element::List(elements) => {
76 writeln!(f, "{:first_indent$}[", ' ')?;
77 for e in elements {
78 write_entity(e, f, last_indent + INDENT_INCREMENT, false)?;
79 writeln!(f, ",")?;
80 }
81 write!(f, "{:last_indent$}]", ' ')?;
82 }
83 Element::Dictionary(map) => {
84 writeln!(f, "{:first_indent$}{{", ' ')?;
85 for (key, val) in map {
86 write_entity(key, f, last_indent + INDENT_INCREMENT, false)?;
87 write!(f, ":")?;
88 write_entity(val, f, last_indent + INDENT_INCREMENT, true)?;
89 writeln!(f, ",")?;
90 }
91 write!(f, "{:last_indent$}}}", ' ')?;
92 }
93 }
94 Ok(())
95 }
96 write_entity(self, f, INDENT_INCREMENT, false)
97 }
98}
99
100#[derive(Debug, Error, Display)]
102pub enum ParseError {
103 EmptySource,
104 #[display("invalid prefix ({_0})")]
105 InvalidPrefix(u8),
106 NoIntegerPrefix,
107 NoIntegerEnd,
108 NoStringDelimeter,
109 #[display("invalid string length ({_0})")]
110 InvalidStringLength(usize),
111 NoListPrefix,
112 NoDictionaryPrefix,
113 #[display("{_0}")]
114 ExternalError(#[source] Box<dyn Error + Send + Sync>),
115}
116
117impl From<ParseIntError> for ParseError {
118 fn from(e: ParseIntError) -> Self {
119 ParseError::ExternalError(Box::new(e))
120 }
121}
122
123impl From<str::Utf8Error> for ParseError {
124 fn from(e: str::Utf8Error) -> Self {
125 ParseError::ExternalError(Box::new(e))
126 }
127}
128
129impl From<ParseError> for io::Error {
130 fn from(e: ParseError) -> Self {
131 io::Error::other(e)
132 }
133}
134
135pub fn convert_dictionary(src: BTreeMap<Element, Element>) -> BTreeMap<String, Element> {
137 fn to_string_key((key, value): (Element, Element)) -> Option<(String, Element)> {
138 if let Element::ByteString(data) = key
139 && let Ok(text) = String::from_utf8(data)
140 {
141 Some((text, value))
142 } else {
143 None
144 }
145 }
146 src.into_iter().filter_map(to_string_key).collect()
147}
148
149const DELIMITER_STRING: u8 = b':';
150const PREFIX_INTEGER: u8 = b'i';
151const PREFIX_LIST: u8 = b'l';
152const PREFIX_DICTIONARY: u8 = b'd';
153const SUFFIX_COMMON: u8 = b'e';
154
155fn write_element(e: &Element, dest: &mut Vec<u8>) {
156 match e {
157 Element::Integer(number) => {
158 let _ = write!(dest, "{}{}{}", PREFIX_INTEGER as char, number, SUFFIX_COMMON as char);
159 }
160 Element::ByteString(data) => {
161 let _ = write!(dest, "{}{}", data.len(), DELIMITER_STRING as char);
162 dest.extend_from_slice(data);
163 }
164 Element::List(list) => {
165 dest.push(PREFIX_LIST);
166 for e in list {
167 write_element(e, dest);
168 }
169 dest.push(SUFFIX_COMMON);
170 }
171 Element::Dictionary(map) => {
172 dest.push(PREFIX_DICTIONARY);
173 for (key, value) in map {
174 write_element(key, dest);
175 write_element(value, dest);
176 }
177 dest.push(SUFFIX_COMMON);
178 }
179 };
180}
181
182fn read_element(src: &[u8]) -> Result<(Element, &[u8]), ParseError> {
183 let first_byte = src.first().ok_or(ParseError::EmptySource)?;
184 match *first_byte {
185 b'0'..=b'9' => read_string(src),
186 PREFIX_INTEGER => read_integer(src),
187 PREFIX_LIST => read_list(src),
188 PREFIX_DICTIONARY => read_dictionary(src),
189 prefix => Err(ParseError::InvalidPrefix(prefix)),
190 }
191}
192
193fn split_once(src: &[u8], delimeter: u8) -> Option<(&[u8], &[u8])> {
194 let opt_index = src.iter().position(|b| *b == delimeter);
195 match opt_index {
196 Some(index) => {
197 let lhs = unsafe { src.get_unchecked(..index) };
198 let rhs = unsafe { src.get_unchecked(index + 1..) };
199 Some((lhs, rhs))
200 }
201 None => None,
202 }
203}
204
205fn read_integer(src: &[u8]) -> Result<(Element, &[u8]), ParseError> {
206 let rest = src.strip_prefix(&[PREFIX_INTEGER]).ok_or(ParseError::NoIntegerPrefix)?;
207
208 let (number_data, rest) = split_once(rest, SUFFIX_COMMON).ok_or(ParseError::NoIntegerEnd)?;
209 let number_text = str::from_utf8(number_data)?;
210 let number = number_text.parse::<i64>()?;
211
212 Ok((Element::Integer(number), rest))
213}
214
215fn read_string(src: &[u8]) -> Result<(Element, &[u8]), ParseError> {
216 let (size_data, rest) =
217 split_once(src, DELIMITER_STRING).ok_or(ParseError::NoStringDelimeter)?;
218 let size_text = str::from_utf8(size_data)?;
219 let size = size_text.parse::<usize>()?;
220
221 let data = rest.get(..size).ok_or(ParseError::InvalidStringLength(size))?;
222 let rest = unsafe { rest.get_unchecked(size..) };
223
224 Ok((Element::ByteString(Vec::from(data)), rest))
225}
226
227fn read_list(src: &[u8]) -> Result<(Element, &[u8]), ParseError> {
228 let mut rest = src.strip_prefix(&[PREFIX_LIST]).ok_or(ParseError::NoListPrefix)?;
229
230 let mut list = Vec::new();
231 while !rest.starts_with(&[SUFFIX_COMMON]) {
232 let (entity, new_rest) = read_element(rest)?;
233 list.push(entity);
234 rest = new_rest;
235 }
236 Ok((Element::List(list), unsafe { rest.get_unchecked(1..) }))
237}
238
239fn read_dictionary(src: &[u8]) -> Result<(Element, &[u8]), ParseError> {
240 let mut rest = src.strip_prefix(&[PREFIX_DICTIONARY]).ok_or(ParseError::NoDictionaryPrefix)?;
241
242 let mut map = BTreeMap::new();
243 while !rest.starts_with(&[SUFFIX_COMMON]) {
244 let (key, new_rest) = read_string(rest)?;
245 rest = new_rest;
246
247 let (value, new_rest) = read_element(rest)?;
248 rest = new_rest;
249
250 map.insert(key, value);
251 }
252 Ok((Element::Dictionary(map), unsafe { rest.get_unchecked(1..) }))
253}
254
255#[allow(clippy::get_first)]
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_decode_and_encode_negative_integer() {
262 let input = b"i-42e";
263
264 let parsed = read_integer(input);
265 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
266
267 let (entity, rest) = parsed.unwrap();
268 assert_eq!(Element::Integer(-42), entity);
269 assert!(rest.is_empty());
270
271 assert_eq!(input, entity.encode().as_slice());
272 }
273
274 #[test]
275 fn test_decode_invalid_integer() {
276 let input = b"i0_0e";
277
278 let parsed = read_integer(input);
279 assert!(parsed.is_err());
280
281 if let ParseError::ExternalError(msg) = parsed.err().unwrap() {
282 assert_eq!("invalid digit found in string", format!("{msg}"));
283 } else {
284 panic!("Wrong error type");
285 }
286 }
287
288 #[test]
289 fn test_decode_non_utf8_integer() {
290 let input = [PREFIX_INTEGER, 200, 201, 202, SUFFIX_COMMON];
291
292 let parsed = read_integer(&input);
293 assert!(parsed.is_err());
294
295 if let ParseError::ExternalError(msg) = parsed.err().unwrap() {
296 assert_eq!("invalid utf-8 sequence of 1 bytes from index 0", format!("{msg}"));
297 } else {
298 panic!("Wrong error type");
299 }
300 }
301
302 #[test]
303 fn test_decode_and_encode_simple_string() {
304 let input = b"15:A simple string";
305
306 let parsed = read_string(input);
307 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
308
309 let (entity, rest) = parsed.unwrap();
310 assert_eq!(Element::ByteString(Vec::from(b"A simple string".as_slice())), entity);
311 assert!(rest.is_empty());
312
313 assert_eq!(input, entity.encode().as_slice());
314 }
315
316 #[test]
317 fn test_decode_and_encode_non_ascii_string() {
318 let input = "22:Добрый день!";
319
320 let parsed = read_string(input.as_bytes());
321 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
322
323 let (entity, rest) = parsed.unwrap();
324 assert_eq!(Element::ByteString(Vec::from("Добрый день!".as_bytes())), entity);
325 assert!(rest.is_empty());
326
327 assert_eq!(input.as_bytes(), entity.encode().as_slice());
328 }
329
330 #[test]
331 fn test_decode_and_encode_binary_string() {
332 let input = [b'4', b':', 0xf1, 0xf2, 0xf3, 0xf4];
333
334 let parsed = read_string(&input);
335 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
336
337 let (entity, rest) = parsed.unwrap();
338 assert_eq!(Element::ByteString(Vec::from([0xf1, 0xf2, 0xf3, 0xf4].as_slice())), entity);
339 assert!(rest.is_empty());
340
341 assert_eq!(&input, entity.encode().as_slice());
342 }
343
344 #[test]
345 fn test_decode_string_with_invalid_length() {
346 let input = [b'9', b'2', b':', 0xf1, 0xf2, 0xf3, 0xf4];
347
348 let parsed = read_string(&input);
349 assert!(parsed.is_err());
350 assert!(matches!(parsed.err().unwrap(), ParseError::InvalidStringLength(_)));
351 }
352
353 #[test]
354 fn test_decode_and_encode_simple_list() {
355 let input = "li-42ei42e15:A simple stringe";
356
357 let parsed = read_list(input.as_bytes());
358 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
359
360 let (entity, rest) = parsed.unwrap();
361 assert!(rest.is_empty());
362
363 if let Element::List(ref list) = entity {
364 assert_eq!(3, list.len());
365 assert_eq!(Element::Integer(-42), *list.get(0).unwrap());
366 assert_eq!(Element::Integer(42), *list.get(1).unwrap());
367 assert_eq!(
368 Element::ByteString(Vec::from("A simple string".as_bytes())),
369 *list.get(2).unwrap()
370 );
371 } else {
372 panic!("Not a list");
373 }
374
375 assert_eq!(input.as_bytes(), entity.encode().as_slice());
376 }
377
378 #[test]
379 fn test_decode_list_without_end_postfix() {
380 let input = "li-42ei42e15:A simple string";
381
382 let parsed = read_list(input.as_bytes());
383 assert!(parsed.is_err());
384 assert!(matches!(parsed.err().unwrap(), ParseError::EmptySource));
385 }
386
387 #[test]
388 fn test_decode_and_encode_list_with_binary_string() {
389 let input = {
390 let start = b"li42e";
391 let end = [b'4', b':', 0xf1, 0xf2, 0xf3, 0xf4, b'e'];
392 let mut buf = Vec::<u8>::new();
393 buf.write_all(start).unwrap();
394 buf.write_all(&end).unwrap();
395 buf
396 };
397
398 let parsed = read_list(&input);
399 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
400
401 let (entity, rest) = parsed.unwrap();
402 assert!(rest.is_empty());
403
404 if let Element::List(ref list) = entity {
405 assert_eq!(2, list.len());
406 assert_eq!(Element::Integer(42), *list.get(0).unwrap());
407 assert_eq!(
408 Element::ByteString(Vec::from([0xf1, 0xf2, 0xf3, 0xf4].as_slice())),
409 *list.get(1).unwrap()
410 );
411 } else {
412 panic!("Not a list");
413 }
414
415 assert_eq!(&input, entity.encode().as_slice());
416 }
417
418 #[test]
419 fn test_decode_and_encode_simple_dictionary() {
420 let input = "d8:announce41:http://tracker.trackerfix.com:80/announcee";
421
422 let parsed = read_dictionary(input.as_bytes());
423 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
424
425 let (entity, rest) = parsed.unwrap();
426 assert!(rest.is_empty());
427 if let Element::Dictionary(ref map) = entity {
428 assert_eq!(1, map.len());
429 let (key, value) = map.iter().next().unwrap();
430 assert_eq!(Element::ByteString(Vec::from("announce".as_bytes())), *key);
431 assert_eq!(
432 Element::ByteString(Vec::from(
433 "http://tracker.trackerfix.com:80/announce".as_bytes()
434 )),
435 *value
436 );
437 } else {
438 panic!("Not a dictionary");
439 }
440
441 assert_eq!(input.as_bytes(), entity.encode().as_slice());
442 }
443
444 #[test]
445 fn test_decode_dictionary_with_appended_data() {
446 let input = "d8:msg_typei1e5:piecei0e10:total_sizei34256eexxxxxxxx";
447
448 let parsed = Element::from_bytes(input.as_bytes()).unwrap();
449
450 if let Element::Dictionary(map) = parsed {
451 let mut it = map.into_iter();
452
453 let (key, value) = it.next().unwrap();
454 assert_eq!(Element::from("msg_type"), key);
455 assert_eq!(Element::Integer(1), value);
456
457 let (key, value) = it.next().unwrap();
458 assert_eq!(Element::from("piece"), key);
459 assert_eq!(Element::Integer(0), value);
460
461 let (key, value) = it.next().unwrap();
462 assert_eq!(Element::from("total_size"), key);
463 assert_eq!(Element::Integer(34256), value);
464
465 assert!(it.next().is_none());
466 } else {
467 panic!("Not a dictionary");
468 }
469 }
470
471 #[test]
472 fn test_decode_and_encode_real_dictionary() {
473 let input = "d8:announce41:http://tracker.trackerfix.com:80/announce13:announce-listll41:http://tracker.trackerfix.com:80/announceel30:udp://9.rarbg.me:2720/announceel30:udp://9.rarbg.to:2740/announceel42:udp://tracker.fatkhoala.org:13780/announceel44:udp://tracker.tallpenguin.org:15760/announceee7:comment40:Torrent downloaded from https://rarbg.to10:created by5:RARBG13:creation datei1629718368e4:infod5:filesld6:lengthi30e4:pathl9:RARBG.txteed6:lengthi99e4:pathl23:RARBG_DO_NOT_MIRROR.exeeed6:lengthi66667e4:pathl4:Subs13:10_French.srteed6:lengthi67729e4:pathl4:Subs13:11_German.srteed6:lengthi98430e4:pathl4:Subs12:12_Greek.srteed6:lengthi89001e4:pathl4:Subs13:13_Hebrew.srteed6:lengthi66729e4:pathl4:Subs10:14_hrv.srteed6:lengthi69251e4:pathl4:Subs16:15_Hungarian.srteed6:lengthi67897e4:pathl4:Subs17:16_Indonesian.srteed6:lengthi67035e4:pathl4:Subs14:17_Italian.srteed6:lengthi68310e4:pathl4:Subs15:18_Japanese.srteed6:lengthi79479e4:pathl4:Subs13:19_Korean.srteed6:lengthi67367e4:pathl4:Subs10:20_may.srteed6:lengthi63337e4:pathl4:Subs13:21_Bokmal.srteed6:lengthi68715e4:pathl4:Subs13:22_Polish.srteed6:lengthi67838e4:pathl4:Subs17:23_Portuguese.srteed6:lengthi69077e4:pathl4:Subs17:24_Portuguese.srteed6:lengthi70967e4:pathl4:Subs15:25_Romanian.srteed6:lengthi90311e4:pathl4:Subs14:26_Russian.srteed6:lengthi67143e4:pathl4:Subs14:27_Spanish.srteed6:lengthi67068e4:pathl4:Subs14:28_Spanish.srteed6:lengthi63229e4:pathl4:Subs14:29_Swedish.srteed6:lengthi97509e4:pathl4:Subs12:2_Arabic.srteed6:lengthi126859e4:pathl4:Subs11:30_Thai.srteed6:lengthi69519e4:pathl4:Subs14:31_Turkish.srteed6:lengthi87216e4:pathl4:Subs10:32_ukr.srteed6:lengthi86745e4:pathl4:Subs17:33_Vietnamese.srteed6:lengthi71908e4:pathl4:Subs13:3_Chinese.srteed6:lengthi71949e4:pathl4:Subs13:4_Chinese.srteed6:lengthi69054e4:pathl4:Subs11:5_Czech.srteed6:lengthi64987e4:pathl4:Subs12:6_Danish.srteed6:lengthi60512e4:pathl4:Subs11:7_Dutch.srteed6:lengthi81102e4:pathl4:Subs13:8_English.srteed6:lengthi62658e4:pathl4:Subs13:9_Finnish.srteed6:lengthi1397575464e4:pathl66:The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG.mp4eee4:name62:The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG12:piece lengthi2097152eee";
474
475 let parsed = read_dictionary(input.as_bytes());
476 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
477
478 let entity = parsed.unwrap();
479 match entity.0 {
480 Element::Dictionary(_) => (),
481 _ => panic!(),
482 };
483
484 assert_eq!(input.as_bytes(), entity.0.encode().as_slice());
485 }
486
487 #[test]
488 fn test_format_bencode() {
489 #[rustfmt::skip]
490 let expected =
491r#" {
492 "announce": "http://tracker.trackerfix.com:80/announce",
493 "announce-list": [
494 [
495 "http://tracker.trackerfix.com:80/announce",
496 ],
497 [
498 "udp://9.rarbg.me:2720/announce",
499 ],
500 [
501 "udp://9.rarbg.to:2740/announce",
502 ],
503 [
504 "udp://tracker.fatkhoala.org:13780/announce",
505 ],
506 [
507 "udp://tracker.tallpenguin.org:15760/announce",
508 ],
509 ],
510 "comment": "Torrent downloaded from https://rarbg.to",
511 "created by": "RARBG",
512 "creation date": 1629718368,
513 "info": {
514 "files": [
515 {
516 "length": 30,
517 "path": [
518 "RARBG.txt",
519 ],
520 },
521 {
522 "length": 99,
523 "path": [
524 "RARBG_DO_NOT_MIRROR.exe",
525 ],
526 },
527 {
528 "length": 66667,
529 "path": [
530 "Subs",
531 "10_French.srt",
532 ],
533 },
534 {
535 "length": 67729,
536 "path": [
537 "Subs",
538 "11_German.srt",
539 ],
540 },
541 {
542 "length": 98430,
543 "path": [
544 "Subs",
545 "12_Greek.srt",
546 ],
547 },
548 {
549 "length": 89001,
550 "path": [
551 "Subs",
552 "13_Hebrew.srt",
553 ],
554 },
555 {
556 "length": 66729,
557 "path": [
558 "Subs",
559 "14_hrv.srt",
560 ],
561 },
562 {
563 "length": 69251,
564 "path": [
565 "Subs",
566 "15_Hungarian.srt",
567 ],
568 },
569 {
570 "length": 67897,
571 "path": [
572 "Subs",
573 "16_Indonesian.srt",
574 ],
575 },
576 {
577 "length": 67035,
578 "path": [
579 "Subs",
580 "17_Italian.srt",
581 ],
582 },
583 {
584 "length": 68310,
585 "path": [
586 "Subs",
587 "18_Japanese.srt",
588 ],
589 },
590 {
591 "length": 79479,
592 "path": [
593 "Subs",
594 "19_Korean.srt",
595 ],
596 },
597 {
598 "length": 67367,
599 "path": [
600 "Subs",
601 "20_may.srt",
602 ],
603 },
604 {
605 "length": 63337,
606 "path": [
607 "Subs",
608 "21_Bokmal.srt",
609 ],
610 },
611 {
612 "length": 68715,
613 "path": [
614 "Subs",
615 "22_Polish.srt",
616 ],
617 },
618 {
619 "length": 67838,
620 "path": [
621 "Subs",
622 "23_Portuguese.srt",
623 ],
624 },
625 {
626 "length": 69077,
627 "path": [
628 "Subs",
629 "24_Portuguese.srt",
630 ],
631 },
632 {
633 "length": 70967,
634 "path": [
635 "Subs",
636 "25_Romanian.srt",
637 ],
638 },
639 {
640 "length": 90311,
641 "path": [
642 "Subs",
643 "26_Russian.srt",
644 ],
645 },
646 {
647 "length": 67143,
648 "path": [
649 "Subs",
650 "27_Spanish.srt",
651 ],
652 },
653 {
654 "length": 67068,
655 "path": [
656 "Subs",
657 "28_Spanish.srt",
658 ],
659 },
660 {
661 "length": 63229,
662 "path": [
663 "Subs",
664 "29_Swedish.srt",
665 ],
666 },
667 {
668 "length": 97509,
669 "path": [
670 "Subs",
671 "2_Arabic.srt",
672 ],
673 },
674 {
675 "length": 126859,
676 "path": [
677 "Subs",
678 "30_Thai.srt",
679 ],
680 },
681 {
682 "length": 69519,
683 "path": [
684 "Subs",
685 "31_Turkish.srt",
686 ],
687 },
688 {
689 "length": 87216,
690 "path": [
691 "Subs",
692 "32_ukr.srt",
693 ],
694 },
695 {
696 "length": 86745,
697 "path": [
698 "Subs",
699 "33_Vietnamese.srt",
700 ],
701 },
702 {
703 "length": 71908,
704 "path": [
705 "Subs",
706 "3_Chinese.srt",
707 ],
708 },
709 {
710 "length": 71949,
711 "path": [
712 "Subs",
713 "4_Chinese.srt",
714 ],
715 },
716 {
717 "length": 69054,
718 "path": [
719 "Subs",
720 "5_Czech.srt",
721 ],
722 },
723 {
724 "length": 64987,
725 "path": [
726 "Subs",
727 "6_Danish.srt",
728 ],
729 },
730 {
731 "length": 60512,
732 "path": [
733 "Subs",
734 "7_Dutch.srt",
735 ],
736 },
737 {
738 "length": 81102,
739 "path": [
740 "Subs",
741 "8_English.srt",
742 ],
743 },
744 {
745 "length": 62658,
746 "path": [
747 "Subs",
748 "9_Finnish.srt",
749 ],
750 },
751 {
752 "length": 1397575464,
753 "path": [
754 "The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG.mp4",
755 ],
756 },
757 ],
758 "name": "The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG",
759 "piece length": 2097152,
760 },
761 }"#;
762
763 let input = "d8:announce41:http://tracker.trackerfix.com:80/announce13:announce-listll41:http://tracker.trackerfix.com:80/announceel30:udp://9.rarbg.me:2720/announceel30:udp://9.rarbg.to:2740/announceel42:udp://tracker.fatkhoala.org:13780/announceel44:udp://tracker.tallpenguin.org:15760/announceee7:comment40:Torrent downloaded from https://rarbg.to10:created by5:RARBG13:creation datei1629718368e4:infod5:filesld6:lengthi30e4:pathl9:RARBG.txteed6:lengthi99e4:pathl23:RARBG_DO_NOT_MIRROR.exeeed6:lengthi66667e4:pathl4:Subs13:10_French.srteed6:lengthi67729e4:pathl4:Subs13:11_German.srteed6:lengthi98430e4:pathl4:Subs12:12_Greek.srteed6:lengthi89001e4:pathl4:Subs13:13_Hebrew.srteed6:lengthi66729e4:pathl4:Subs10:14_hrv.srteed6:lengthi69251e4:pathl4:Subs16:15_Hungarian.srteed6:lengthi67897e4:pathl4:Subs17:16_Indonesian.srteed6:lengthi67035e4:pathl4:Subs14:17_Italian.srteed6:lengthi68310e4:pathl4:Subs15:18_Japanese.srteed6:lengthi79479e4:pathl4:Subs13:19_Korean.srteed6:lengthi67367e4:pathl4:Subs10:20_may.srteed6:lengthi63337e4:pathl4:Subs13:21_Bokmal.srteed6:lengthi68715e4:pathl4:Subs13:22_Polish.srteed6:lengthi67838e4:pathl4:Subs17:23_Portuguese.srteed6:lengthi69077e4:pathl4:Subs17:24_Portuguese.srteed6:lengthi70967e4:pathl4:Subs15:25_Romanian.srteed6:lengthi90311e4:pathl4:Subs14:26_Russian.srteed6:lengthi67143e4:pathl4:Subs14:27_Spanish.srteed6:lengthi67068e4:pathl4:Subs14:28_Spanish.srteed6:lengthi63229e4:pathl4:Subs14:29_Swedish.srteed6:lengthi97509e4:pathl4:Subs12:2_Arabic.srteed6:lengthi126859e4:pathl4:Subs11:30_Thai.srteed6:lengthi69519e4:pathl4:Subs14:31_Turkish.srteed6:lengthi87216e4:pathl4:Subs10:32_ukr.srteed6:lengthi86745e4:pathl4:Subs17:33_Vietnamese.srteed6:lengthi71908e4:pathl4:Subs13:3_Chinese.srteed6:lengthi71949e4:pathl4:Subs13:4_Chinese.srteed6:lengthi69054e4:pathl4:Subs11:5_Czech.srteed6:lengthi64987e4:pathl4:Subs12:6_Danish.srteed6:lengthi60512e4:pathl4:Subs11:7_Dutch.srteed6:lengthi81102e4:pathl4:Subs13:8_English.srteed6:lengthi62658e4:pathl4:Subs13:9_Finnish.srteed6:lengthi1397575464e4:pathl66:The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG.mp4eee4:name62:The.Witcher.Nightmare.of.the.Wolf.2021.1080p.WEBRip.x265-RARBG12:piece lengthi2097152eee";
764
765 let parsed = read_dictionary(input.as_bytes());
766 assert!(parsed.is_ok(), "Error: {:?}", parsed.err());
767
768 let entity = parsed.unwrap().0;
769 assert_eq!(expected, format!("{entity}"), "\n{entity}\n");
770 }
771}