diff options
Diffstat (limited to 'rust/vendor/bendy/examples')
-rw-r--r-- | rust/vendor/bendy/examples/decode_torrent.rs | 212 | ||||
-rw-r--r-- | rust/vendor/bendy/examples/encode_torrent.rs | 115 | ||||
-rw-r--r-- | rust/vendor/bendy/examples/torrent_files/debian-9.4.0-amd64-netinst.iso.torrent | bin | 0 -> 23821 bytes | |||
-rw-r--r-- | rust/vendor/bendy/examples/torrent_files/pieces.iso | bin | 0 -> 23280 bytes |
4 files changed, 327 insertions, 0 deletions
diff --git a/rust/vendor/bendy/examples/decode_torrent.rs b/rust/vendor/bendy/examples/decode_torrent.rs new file mode 100644 index 0000000..3960a89 --- /dev/null +++ b/rust/vendor/bendy/examples/decode_torrent.rs @@ -0,0 +1,212 @@ +//! A decoder for torrent files. +//! +//! This example will ... +//! +//! - read a torrent file, +//! - deserialize the bencode formatted information +//! - and print the result into stdout. +//! +//! *Attention*: Please consider to pipe the output into a file of your choice. +//! +//! # Run the Example +//! +//! ``` +//! cargo run --example decode_torrent > parsing_output.txt +//! ``` + +use bendy::{ + decoding::{Error, FromBencode, Object, ResultExt}, + encoding::AsString, +}; + +static EXAMPLE_TORRENT: &[u8] = + include_bytes!("torrent_files/debian-9.4.0-amd64-netinst.iso.torrent"); + +/// Main struct containing all required information. +/// +/// Based on: [http://fileformats.wikia.com/wiki/Torrent_file]. +/// +/// # Design Decision +/// +/// To keep the example simple we won't parse the integers fields +/// into a concrete number type as the bencode integer definition +/// is actually a `BigNum` and the content may not fit. +#[derive(Debug)] +struct MetaInfo { + pub announce: String, + pub info: Info, + pub comment: Option<String>, // not official element + pub creation_date: Option<u64>, // not official element + pub http_seeds: Option<Vec<String>>, // not official element +} + +/// File related information (Single-file format) +#[derive(Debug)] +struct Info { + pub piece_length: String, + pub pieces: Vec<u8>, + pub name: String, + pub file_length: String, +} + +impl FromBencode for MetaInfo { + // Try to parse with a `max_depth` of two. + // + // The required max depth of a data structure is calculated as follows: + // + // - Every potential nesting level encoded as bencode dictionary or list count as +1, + // - everything else is ignored. + // + // This typically means that we only need to count the amount of nested structs and container + // types. (Potentially ignoring lists of bytes as they are normally encoded as strings.) + // + // struct MetaInfo { // encoded as dictionary (+1) + // announce: String, + // info: Info { // encoded as dictionary (+1) + // piece_length: String, + // pieces: Vec<u8>, // encoded as string and therefore ignored + // name: String, + // file_length: String, + // }, + // comment: Option<String>, + // creation_date: Option<u64>, + // http_seeds: Option<Vec<String>> // if available encoded as list but even then doesn't + // increase the limit over the deepest chain including + // info + // } + const EXPECTED_RECURSION_DEPTH: usize = Info::EXPECTED_RECURSION_DEPTH + 1; + + /// Entry point for decoding a torrent. The dictionary is parsed for all + /// non-optional and optional fields. Missing optional fields are ignored + /// but any other missing fields result in stopping the decoding and in + /// spawning [`DecodingError::MissingField`]. + fn decode_bencode_object(object: Object) -> Result<Self, Error> + where + Self: Sized, + { + let mut announce = None; + let mut comment = None; + let mut creation_date = None; + let mut http_seeds = None; + let mut info = None; + + let mut dict_dec = object.try_into_dictionary()?; + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"announce", value) => { + announce = String::decode_bencode_object(value) + .context("announce") + .map(Some)?; + }, + (b"comment", value) => { + comment = String::decode_bencode_object(value) + .context("comment") + .map(Some)?; + }, + (b"creation date", value) => { + creation_date = u64::decode_bencode_object(value) + .context("creation_date") + .map(Some)?; + }, + (b"httpseeds", value) => { + http_seeds = Vec::decode_bencode_object(value) + .context("http_seeds") + .map(Some)?; + }, + (b"info", value) => { + info = Info::decode_bencode_object(value) + .context("info") + .map(Some)?; + }, + (unknown_field, _) => { + return Err(Error::unexpected_field(String::from_utf8_lossy( + unknown_field, + ))); + }, + } + } + + let announce = announce.ok_or_else(|| Error::missing_field("announce"))?; + let info = info.ok_or_else(|| Error::missing_field("info"))?; + + Ok(MetaInfo { + announce, + info, + comment, + creation_date, + http_seeds, + }) + } +} + +impl FromBencode for Info { + const EXPECTED_RECURSION_DEPTH: usize = 1; + + /// Treats object as dictionary containing all fields for the info struct. + /// On success the dictionary is parsed for the fields of info which are + /// necessary for torrent. Any missing field will result in a missing field + /// error which will stop the decoding. + fn decode_bencode_object(object: Object) -> Result<Self, Error> + where + Self: Sized, + { + let mut file_length = None; + let mut name = None; + let mut piece_length = None; + let mut pieces = None; + + let mut dict_dec = object.try_into_dictionary()?; + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"length", value) => { + file_length = value + .try_into_integer() + .context("file.length") + .map(ToString::to_string) + .map(Some)?; + }, + (b"name", value) => { + name = String::decode_bencode_object(value) + .context("name") + .map(Some)?; + }, + (b"piece length", value) => { + piece_length = value + .try_into_integer() + .context("length") + .map(ToString::to_string) + .map(Some)?; + }, + (b"pieces", value) => { + pieces = AsString::decode_bencode_object(value) + .context("pieces") + .map(|bytes| Some(bytes.0))?; + }, + (unknown_field, _) => { + return Err(Error::unexpected_field(String::from_utf8_lossy( + unknown_field, + ))); + }, + } + } + + let file_length = file_length.ok_or_else(|| Error::missing_field("file_length"))?; + let name = name.ok_or_else(|| Error::missing_field("name"))?; + let piece_length = piece_length.ok_or_else(|| Error::missing_field("piece_length"))?; + let pieces = pieces.ok_or_else(|| Error::missing_field("pieces"))?; + + // Check that we discovered all necessary fields + Ok(Info { + file_length, + name, + piece_length, + pieces, + }) + } +} + +fn main() -> Result<(), Error> { + let torrent = MetaInfo::from_bencode(EXAMPLE_TORRENT)?; + println!("{:#?}", torrent); + Ok(()) +} diff --git a/rust/vendor/bendy/examples/encode_torrent.rs b/rust/vendor/bendy/examples/encode_torrent.rs new file mode 100644 index 0000000..1e9b8c2 --- /dev/null +++ b/rust/vendor/bendy/examples/encode_torrent.rs @@ -0,0 +1,115 @@ +//! An encoder for torrent files +//! +//! This example will ... +//! +//! - serialize a torrent file representing object in bencode format +//! - and print the result into stdout. +//! +//! *Attention*: Please consider to pipe the output into a file of your choice. +//! +//! # Run the Example +//! +//! ``` +//! cargo run --example encode_torrent > example.torrent +//! ``` + +use std::io::Write; + +use bendy::encoding::{AsString, Error as EncodingError, SingleItemEncoder, ToBencode}; +use failure::Error; + +/// Main struct containing all required information. +/// +/// Based on: [http://fileformats.wikia.com/wiki/Torrent_file]. +/// +/// # Design Decision +/// +/// To keep the example simple we won't parse the integers fields +/// into a concrete number type as the bencode integer definition +/// is actually a `BigNum` and the content may not fit. +#[derive(Debug)] +struct MetaInfo { + pub announce: String, + pub info: Info, + pub comment: Option<String>, // not official element + pub creation_date: Option<String>, // not official element + pub http_seeds: Option<Vec<String>>, // not official element +} + +/// File related information (Single-file format) +#[derive(Debug)] +struct Info { + pub piece_length: String, + pub pieces: Vec<u8>, + pub name: String, + pub file_length: String, +} + +impl ToBencode for MetaInfo { + // Adds an additional recursion level -- itself formatted as dictionary -- + // around the info struct. + const MAX_DEPTH: usize = Info::MAX_DEPTH + 1; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), EncodingError> { + encoder.emit_dict(|mut e| { + e.emit_pair(b"announce", &self.announce)?; + + if let Some(comment) = &self.comment { + e.emit_pair(b"comment", comment)?; + } + + if let Some(creation_date) = &self.creation_date { + e.emit_pair(b"creation date", creation_date)?; + } + + if let Some(seeds) = &self.http_seeds { + // List is a simple iterable wrapper that allows to encode + // any list like container as bencode list object. + e.emit_pair(b"httpseeds", seeds)?; + } + + e.emit_pair(b"info", &self.info) + })?; + + Ok(()) + } +} + +impl ToBencode for Info { + // The struct is encoded as dictionary and all of it internals are encoded + // as flat values, i.e. strings or integers. + const MAX_DEPTH: usize = 1; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), EncodingError> { + encoder.emit_dict(|mut e| { + e.emit_pair(b"length", &self.file_length)?; + e.emit_pair(b"name", &self.name)?; + e.emit_pair(b"piece length", &self.piece_length)?; + e.emit_pair(b"pieces", AsString(&self.pieces)) + })?; + Ok(()) + } +} + +fn main() -> Result<(), Error> { + let torrent = MetaInfo { + announce: "http://bttracker.debian.org:6969/announce".to_owned(), + comment: Some("\"Debian CD from cdimage.debian.org\"".to_owned()), + creation_date: Some(1_520_682_848.to_string()), + http_seeds: Some(vec![ + "https://cdimage.debian.org/cdimage/release/9.4.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-9.4.0-amd64-netinst.iso".to_owned(), + "https://cdimage.debian.org/cdimage/archive/9.4.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-9.4.0-amd64-netinst.iso".to_owned(), + ]), + info: Info { + piece_length: 262_144.to_string(), + pieces: include_bytes!("torrent_files/pieces.iso").to_vec(), + name: "debian-9.4.0-amd64-netinst.iso".to_owned(), + file_length: 305_135_616.to_string(), + }, + }; + + let data = torrent.to_bencode()?; + std::io::stdout().write_all(&data)?; + + Ok(()) +} diff --git a/rust/vendor/bendy/examples/torrent_files/debian-9.4.0-amd64-netinst.iso.torrent b/rust/vendor/bendy/examples/torrent_files/debian-9.4.0-amd64-netinst.iso.torrent Binary files differnew file mode 100644 index 0000000..5c8652e --- /dev/null +++ b/rust/vendor/bendy/examples/torrent_files/debian-9.4.0-amd64-netinst.iso.torrent diff --git a/rust/vendor/bendy/examples/torrent_files/pieces.iso b/rust/vendor/bendy/examples/torrent_files/pieces.iso Binary files differnew file mode 100644 index 0000000..1b35971 --- /dev/null +++ b/rust/vendor/bendy/examples/torrent_files/pieces.iso |