1use crate::bcodec::bencoder::BEncoder;
10use crate::bcodec::bvalue::BValue;
11use crate::bcodec::raw_finder::RawFinder;
12use crate::constants::{HASH_SIZE, PIECE_LENGTH};
13use crate::hashmap;
14use crate::Error;
15use crate::{BDecoder, DeepFinder};
16use sha1;
17use std::collections::HashMap;
18use std::convert::{TryFrom, TryInto};
19use std::fs;
20use std::path::{Path, PathBuf};
21
22#[derive(PartialEq, Clone, Debug)]
25pub struct Metainfo {
26 announce: String,
27 name: String,
28 piece_length: u64,
29 pieces: Vec<[u8; HASH_SIZE]>,
30 files: Vec<File>,
31 info_hash: [u8; HASH_SIZE],
32}
33
34#[derive(PartialEq, Clone, Debug)]
36pub struct File {
37 pub length: u64,
39 pub path: String,
41}
42
43pub struct PiecePos {
44 pub file_index: usize,
45 pub byte_index: usize,
46}
47
48impl Metainfo {
49 pub fn create_file(path: &Path, tracker_addr: &String) -> Result<(), Error> {
59 let metadata = match fs::metadata(path) {
60 Ok(metadata) => {
61 if metadata.is_dir() {
62 return Err(Error::FileNotFound);
63 }
64 metadata
65 }
66 Err(_) => return Err(Error::FileNotFound),
67 };
68
69 let name = match path.file_name() {
70 Some(name) => match name.to_str() {
71 Some(name) => name.as_bytes().to_vec(),
72 None => return Err(Error::FileNotFound),
73 },
74 None => return Err(Error::FileNotFound),
75 };
76
77 let data = match fs::read(path) {
78 Ok(data) => data,
79 Err(_) => return Err(Error::FileNotFound),
80 };
81
82 let pieces = data
83 .chunks(PIECE_LENGTH)
84 .flat_map(|chunk| {
85 let mut hasher = sha1::Sha1::new();
86 hasher.update(chunk);
87 hasher.digest().bytes().as_ref().to_vec()
88 })
89 .collect::<Vec<u8>>();
90
91 let info = hashmap![
92 b"name".to_vec() => BValue::ByteStr(name),
93 b"piece length".to_vec() => BValue::Int(PIECE_LENGTH as i64),
94 b"pieces".to_vec() => BValue::ByteStr(pieces),
95 b"length".to_vec() => BValue::Int(metadata.len() as i64)
96 ];
97
98 let torrent = hashmap![
99 b"announce".to_vec() => BValue::ByteStr(tracker_addr.to_owned().into_bytes()),
100 b"info".to_vec() => BValue::Dict(info)
101 ];
102
103 let torrent_file = match path.file_name() {
104 Some(file_name) => {
105 let mut torrent_file = file_name.to_os_string();
106 torrent_file.push(".torrent");
107 torrent_file
108 }
109 None => return Err(Error::FileNotFound),
110 };
111
112 match fs::write(torrent_file, BEncoder::new().add_dict(&torrent).encode()) {
113 Ok(()) => Ok(()),
114 Err(_) => Err(Error::FileCannotWrite),
115 }
116 }
117
118 pub fn from_file(path: &Path) -> Result<Metainfo, Error> {
129 match &fs::read(path) {
130 Ok(val) => Self::from_bencode(val),
131 Err(_) => Err(Error::MetaFileNotFound),
132 }
133 }
134
135 pub fn from_bencode(data: &[u8]) -> Result<Metainfo, Error> {
144 let bvalues = BDecoder::from_array(data)?;
145
146 if bvalues.is_empty() {
147 return Err(Error::MetaBEncodeMissing);
148 }
149
150 let mut err = Err(Error::MetaDataMissing);
151 for val in bvalues {
152 match val {
153 BValue::Dict(dict) => match Self::parse(data, &dict) {
154 Ok(torrent) => return Ok(torrent),
155 Err(e) => err = Err(e),
156 },
157 _ => (),
158 }
159 }
160
161 err
162 }
163
164 fn parse(data: &[u8], dict: &HashMap<Vec<u8>, BValue>) -> Result<Metainfo, Error> {
165 let length = Self::find_length(dict);
166 let multi_files = Self::find_files(dict);
167
168 if length.is_some() && multi_files.is_some() {
169 return Err(Error::MetaLenAndFilesConflict);
170 } else if length.is_none() && multi_files.is_none() {
171 return Err(Error::MetaLenOrFilesMissing);
172 }
173
174 let name = Self::find_name(dict)?;
175 let files = match length {
176 Some(length) => vec![File {
177 length,
178 path: name.clone(),
179 }],
180 None => match multi_files {
181 Some(multi_files) => multi_files,
182 None => vec![],
183 },
184 };
185
186 let metainfo = Metainfo {
187 announce: Self::find_announce(dict)?,
188 name,
189 piece_length: Self::find_piece_length(dict)?,
190 pieces: Self::find_pieces(dict)?,
191 files,
192 info_hash: Self::calculate_hash(data)?,
193 };
194
195 Ok(metainfo)
196 }
197
198 pub fn find_announce(dict: &HashMap<Vec<u8>, BValue>) -> Result<String, Error> {
200 match dict.get(&b"announce".to_vec()) {
201 Some(BValue::ByteStr(val)) => {
202 String::from_utf8(val.to_vec()).or(Err(Error::MetaInvalidUtf8("announce")))
203 }
204 _ => Err(Error::MetaIncorrectOrMissing("announce")),
205 }
206 }
207
208 pub fn find_name(dict: &HashMap<Vec<u8>, BValue>) -> Result<String, Error> {
210 match dict.get(&b"info".to_vec()) {
211 Some(BValue::Dict(info)) => match info.get(&b"name".to_vec()) {
212 Some(BValue::ByteStr(val)) => {
213 String::from_utf8(val.to_vec()).or(Err(Error::MetaInvalidUtf8("name")))
214 }
215 _ => Err(Error::MetaIncorrectOrMissing("name")),
216 },
217 _ => Err(Error::MetaIncorrectOrMissing("info".into())),
218 }
219 }
220
221 pub fn find_piece_length(dict: &HashMap<Vec<u8>, BValue>) -> Result<u64, Error> {
223 match dict.get(&b"info".to_vec()) {
224 Some(BValue::Dict(info)) => match info.get(&b"piece length".to_vec()) {
225 Some(BValue::Int(length)) => {
226 u64::try_from(*length).or(Err(Error::MetaInvalidU64("piece length")))
227 }
228 _ => Err(Error::MetaIncorrectOrMissing("piece length")),
229 },
230 _ => Err(Error::MetaIncorrectOrMissing("info".into())),
231 }
232 }
233
234 pub fn find_pieces(dict: &HashMap<Vec<u8>, BValue>) -> Result<Vec<[u8; HASH_SIZE]>, Error> {
236 match dict.get(&b"info".to_vec()) {
237 Some(BValue::Dict(info)) => match info.get(&b"pieces".to_vec()) {
238 Some(BValue::ByteStr(pieces)) => {
239 if pieces.len() % HASH_SIZE != 0 {
240 return Err(Error::MetaNotDivisible("pieces"));
241 }
242 Ok(pieces
243 .chunks(HASH_SIZE)
244 .map(|chunk| chunk.try_into().unwrap())
245 .collect())
246 }
247 _ => Err(Error::MetaIncorrectOrMissing("pieces")),
248 },
249 _ => Err(Error::MetaIncorrectOrMissing("info".into())),
250 }
251 }
252
253 pub fn find_length(dict: &HashMap<Vec<u8>, BValue>) -> Option<u64> {
255 match dict.get(&b"info".to_vec()) {
256 Some(BValue::Dict(info)) => match info.get(&b"length".to_vec()) {
257 Some(BValue::Int(length)) => u64::try_from(*length).ok(),
258 _ => None,
259 },
260 _ => None,
261 }
262 }
263
264 pub fn find_files(dict: &HashMap<Vec<u8>, BValue>) -> Option<Vec<File>> {
266 match dict.get(&b"info".to_vec()) {
267 Some(BValue::Dict(info)) => match info.get(&b"files".to_vec()) {
268 Some(BValue::List(list)) => Some(Self::file_list(list)),
269 _ => None,
270 },
271 _ => None,
272 }
273 }
274
275 fn file_list(list: &Vec<BValue>) -> Vec<File> {
276 list.iter()
277 .filter_map(|elem| match elem {
278 BValue::Dict(dict) => Some(dict),
279 _ => None,
280 })
281 .filter_map(
282 |dict| match (dict.get(&b"length".to_vec()), dict.get(&b"path".to_vec())) {
283 (Some(BValue::Int(length)), Some(BValue::ByteStr(path))) => {
284 Some((length, path))
285 }
286 _ => None,
287 },
288 )
289 .filter_map(|(length, path)| {
290 match (u64::try_from(*length), String::from_utf8(path.to_vec())) {
291 (Ok(l), Ok(p)) => Some(File { length: l, path: p }),
292 _ => None,
293 }
294 })
295 .collect()
296 }
297
298 fn calculate_hash(data: &[u8]) -> Result<[u8; HASH_SIZE], Error> {
299 if let Some(info) = DeepFinder::find_first("4:info", data) {
300 let mut hasher = sha1::Sha1::new();
301 hasher.update(info.as_ref());
302 return Ok(hasher.digest().bytes());
303 }
304
305 Err(Error::InfoMissing)
306 }
307
308 pub fn tracker_url(&self) -> &String {
310 &self.announce
311 }
312
313 pub fn piece(&self, piece_index: usize) -> &[u8; HASH_SIZE] {
315 &self.pieces[piece_index]
316 }
317
318 pub fn pieces_num(&self) -> usize {
320 self.pieces.len()
321 }
322
323 pub fn piece_length(&self, piece_index: usize) -> usize {
325 if piece_index < self.pieces.len() - 1 {
326 return self.piece_length as usize;
327 }
328
329 let last = self.total_length() as usize % self.piece_length as usize;
330 if last != 0 {
331 return last;
332 }
333
334 return self.piece_length as usize;
335 }
336
337 pub fn total_length(&self) -> u64 {
339 self.files.iter().map(|file| file.length).sum()
340 }
341
342 pub fn info_hash(&self) -> &[u8; HASH_SIZE] {
344 &self.info_hash
345 }
346
347 pub fn file_piece_ranges(&self) -> Vec<(PathBuf, PiecePos, PiecePos)> {
349 let dir = match self.files.len() > 1 {
350 true => PathBuf::from(&self.name),
351 false => PathBuf::new(),
352 };
353
354 let mut ranges: Vec<(PathBuf, PiecePos, PiecePos)> = vec![];
355 let mut pos: usize = 0;
356
357 for File { length, path } in self.files.iter() {
358 ranges.push((
359 dir.join(path),
360 self.piece_pos(pos),
361 self.piece_pos(pos + *length as usize),
362 ));
363
364 pos += *length as usize;
365 }
366
367 ranges
368 }
369
370 fn piece_pos(&self, pos: usize) -> PiecePos {
371 PiecePos {
372 file_index: pos / self.piece_length as usize,
373 byte_index: pos % self.piece_length as usize,
374 }
375 }
376}