svg_converted: fix build (zlib2 split)
[mldonkey.git] / src / networks / bittorrent / bTGlobals.ml
blobce23fa0b8ea8f48c145449aa8c5651fd486bcddb
1 (* Copyright 2001, 2002 b52_simon :), b8_bavard, b8_fee_carabine, INRIA *)
2 (*
3 This file is part of mldonkey.
5 mldonkey is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 mldonkey is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with mldonkey; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 open Int64ops
21 open Printf2
22 open Md4
24 open CommonSwarming
25 open CommonInteractive
26 open CommonClient
27 open CommonTypes
28 open CommonOptions
29 open CommonFile
30 open CommonShared
31 open BasicSocket
32 open CommonGlobals
33 open Options
35 open BTRate
36 open BTTypes
37 open BTOptions
38 open BTProtocol
39 open CommonNetwork
40 open TcpMessages
43 let send_client c m = send_client c.client_sock m
45 let as_ft file = as_file file.ft_file
46 let ft_num file = file_num (as_ft file)
47 let ft_size file = file.ft_file.impl_file_size
48 let ft_state file = file_state (as_ft file)
50 let as_file file = as_file file.file_file
51 let file_size file = file.file_file.impl_file_size
52 let file_downloaded file = file_downloaded (as_file file)
53 let file_age file = file.file_file.impl_file_age
54 let file_fd file = file_fd (as_file file)
55 let file_disk_name file = file_disk_name (as_file file)
56 let file_state file = file_state (as_file file)
57 let file_num file = file_num (as_file file)
58 let file_must_update file = file_must_update (as_file file)
61 let set_file_state file state =
62 CommonFile.set_file_state (as_file file) state
64 let as_client c = as_client c.client_client
65 let client_type c = client_type (as_client c)
67 let set_client_state client state =
68 CommonClient.set_client_state (as_client client) state
70 let set_client_disconnected client =
71 CommonClient.set_client_disconnected (as_client client)
73 let client_num c = client_num (as_client c)
76 let network = new_network "BT" "BitTorrent"
78 NetworkHasMultinet;
79 NetworkHasUpload;
80 NetworkHasStats;
83 let connection_manager = network.network_connection_manager
85 let (shared_ops : file CommonShared.shared_ops) =
86 CommonShared.new_shared_ops network
88 let (server_ops : server CommonServer.server_ops) =
89 CommonServer.new_server_ops network
91 let (room_ops : server CommonRoom.room_ops) =
92 CommonRoom.new_room_ops network
94 let (user_ops : user CommonUser.user_ops) =
95 CommonUser.new_user_ops network
97 let (file_ops : file CommonFile.file_ops) =
98 CommonFile.new_file_ops network
100 let (ft_ops : ft CommonFile.file_ops) =
101 CommonFile.new_file_ops network
103 let (client_ops : client CommonClient.client_ops) =
104 CommonClient.new_client_ops network
106 let must_share_file file codedname has_old_impl =
107 match file.file_shared with
108 | Some _ -> ()
109 | None ->
110 begin
111 let impl = {
112 impl_shared_update = 1;
113 impl_shared_fullname = file_disk_name file;
114 impl_shared_codedname = codedname;
115 impl_shared_size = file_size file;
116 impl_shared_id = Md4.null;
117 impl_shared_num = 0;
118 impl_shared_uploaded = Int64.zero;
119 impl_shared_ops = shared_ops;
120 impl_shared_val = file;
121 impl_shared_requests = 0;
122 impl_shared_file = Some (as_file file);
123 impl_shared_servers = [];
124 } in
125 file.file_shared <- Some impl;
126 incr CommonGlobals.nshared_files;
127 CommonShared.shared_calculate_total_bytes ();
128 match has_old_impl with
129 None -> update_shared_num impl
130 | Some old_impl -> replace_shared old_impl impl
133 let must_share_file file = must_share_file file (file_best_name (as_file file)) None
135 let unshare_file file =
136 match file.file_shared with
137 None -> ()
138 | Some s ->
139 begin
140 file.file_shared <- None;
141 decr CommonGlobals.nshared_files;
142 CommonShared.shared_calculate_total_bytes ()
145 module DO = CommonOptions
147 let current_files = ref ([] : BTTypes.file list)
149 let listen_sock = ref (None : TcpServerSocket.t option)
151 let bt_dht = ref (None : BT_DHT.M.t option)
153 let files_by_uid = Hashtbl.create 13
155 let max_range_len = Int64.of_int (1 lsl 14)
156 let max_request_len = Int64.of_int (1 lsl 16)
158 let bt_download_counter = ref Int64.zero
159 let bt_upload_counter = ref Int64.zero
161 let log_prefix = "[BT]"
163 let lprintf_nl ?exn fmt =
164 lprintf_nl2 ?exn log_prefix fmt
166 let lprintf_n fmt =
167 lprintf2 log_prefix fmt
170 let check_if_interesting file c =
172 if not c.client_alrd_sent_notinterested then
173 let up = match c.client_uploader with
174 None -> assert false
175 | Some up -> up
177 let swarmer = CommonSwarming.uploader_swarmer up in
178 let must_send =
179 (* The client has nothing to propose to us *)
180 (not (CommonSwarming.is_interesting up )) &&
181 (* All the requested ranges are useless *)
182 (List.filter (fun (_,_,r) ->
183 let x,y = CommonSwarming.range_range r in
184 x < y) c.client_ranges_sent = []) &&
185 (match c.client_range_waiting with
186 None -> true
187 | Some (x,y,r) ->
188 let x,y = CommonSwarming.range_range r in
189 x < y) &&
190 (* The current blocks are also useless *)
191 (match c.client_chunk with
192 | None -> true
193 | Some (chunk, blocks) ->
194 List.for_all (fun b ->
195 let chunk_num = CommonSwarming.block_chunk_num swarmer b.up_block in
196 let bitmap = CommonSwarming.chunks_verified_bitmap swarmer in
197 VB.get bitmap chunk_num <> VB.State_verified) blocks)
199 if must_send then
200 begin
201 c.client_interesting <- false;
202 c.client_alrd_sent_notinterested <- true;
203 send_client c NotInterested
206 let add_torrent_infos file trackers =
207 file.file_trackers <- trackers @ file.file_trackers
209 let create_temp_file file_temp file_files file_state =
210 if !verbose then lprintf_nl "create_temp_file %s - %s" file_temp (string_of_state file_state);
211 let writable =
212 if file_state = FileShared then
213 false
214 else
215 true
217 let file_fd =
218 if file_files <> [] then
219 Unix32.create_multifile file_temp writable file_files
220 else
221 Unix32.create_diskfile file_temp writable
223 if Unix32.destroyed file_fd then
224 failwith
225 (Printf.sprintf
226 "create_temp_file: Unix32.create returned a destroyed FD for %s\n"
227 file_temp);
228 file_fd
230 let make_tracker_url url =
231 if String2.check_prefix (String.lowercase url) "http://" then
232 `Http url (* do not change the case of the url *)
233 else
234 try Scanf.sscanf (String.lowercase url) "udp://%s@:%d" (fun host port -> `Udp (host,port))
235 with _ -> `Other url
237 (** invariant: [make_tracker_url (show_tracker_url url) = url] *)
238 let show_tracker_url : tracker_url -> string = function
239 | `Http url | `Other url -> url
240 | `Udp (host,port) -> Printf.sprintf "udp://%s:%d" host port
242 let can_handle_tracker = function
243 | `Http _
244 | `Udp _ -> true
245 | `Other _ -> false
247 let set_trackers file file_trackers =
248 List.iter (fun url ->
249 let url = make_tracker_url url in
250 if not (List.exists (fun tracker -> tracker.tracker_url = url) file.file_trackers) then
251 let t = {
252 tracker_url = url;
253 tracker_interval = 600;
254 tracker_min_interval = 600;
255 tracker_last_conn = 0;
256 tracker_last_clients_num = 0;
257 tracker_torrent_downloaded = 0;
258 tracker_torrent_complete = 0;
259 tracker_torrent_incomplete = 0;
260 tracker_torrent_total_clients_count = 0;
261 tracker_torrent_last_dl_req = 0;
262 tracker_id = "";
263 tracker_key = "";
264 tracker_status = if can_handle_tracker url then Enabled
265 else Disabled_mld (intern "Tracker type not supported")
266 } in
267 file.file_trackers <- t :: file.file_trackers)
268 file_trackers
270 let new_file ?(metadata=false) file_id t torrent_diskname file_temp file_state user group =
272 Hashtbl.find files_by_uid file_id
273 with Not_found ->
274 let file_fd = create_temp_file file_temp t.torrent_files file_state in
275 let rec file = {
276 file_tracker_connected = false;
277 file_file = file_impl;
278 file_piece_size = t.torrent_piece_size;
279 file_id = file_id;
280 file_name = t.torrent_name;
281 file_comment = t.torrent_comment;
282 file_created_by = t.torrent_created_by;
283 file_creation_date = t.torrent_creation_date;
284 file_modified_by = t.torrent_modified_by;
285 file_encoding = t.torrent_encoding;
286 file_clients_num = 0;
287 file_clients = Hashtbl.create 113;
288 file_swarmer = None;
289 file_trackers = [];
290 file_chunks = t.torrent_pieces;
291 file_files = (List.map (fun (file,size) -> (file,size,None)) t.torrent_files);
292 file_blocks_downloaded = [];
293 file_uploaded = Int64.zero;
294 file_torrent_diskname = torrent_diskname;
295 file_completed_hook = (fun _ -> ());
296 file_shared = None;
297 file_session_uploaded = Int64.zero;
298 file_session_downloaded = Int64.zero;
299 file_last_dht_announce = 0;
300 file_metadata_size = 0L;
301 file_metadata_piece = 0L;
302 file_metadata_downloading = metadata;
303 file_metadata_chunks = Array.make 20 "";
304 file_private = t.torrent_private;
305 } and file_impl = {
306 (dummy_file_impl ()) with
307 impl_file_owner = user;
308 impl_file_group = group;
309 impl_file_fd = Some file_fd;
310 impl_file_size = t.torrent_length;
311 impl_file_downloaded = Int64.zero;
312 impl_file_val = file;
313 impl_file_ops = file_ops;
314 impl_file_age = last_time ();
315 impl_file_best_name = t.torrent_name;
318 if t.torrent_announce_list <> [] then
319 set_trackers file t.torrent_announce_list
320 else
321 set_trackers file [t.torrent_announce];
322 if file_state <> FileShared then begin
323 let kernel = CommonSwarming.create_swarmer file_temp (file_size file) in
324 let swarmer = CommonSwarming.create kernel (as_file file)
325 file.file_piece_size in
326 file.file_swarmer <- Some swarmer;
327 CommonSwarming.set_verified swarmer (fun _ num ->
328 file.file_blocks_downloaded <- (num) ::
329 file.file_blocks_downloaded;
330 file_must_update file;
331 (*Automatically send Have to ALL clients once a piece is verified
332 NB : will probably have to check if client can be interested*)
333 Hashtbl.iter (fun _ c ->
335 if c.client_registered_bitfield then
336 begin
337 match c.client_bitmap with
338 None -> ()
339 | Some bitmap ->
340 if not (Bitv.get bitmap num) then
341 send_client c (Have (Int64.of_int num));
342 check_if_interesting file c
344 ) file.file_clients
347 CommonSwarming.set_verifier swarmer (Verification
348 (Array.map (fun sha1 -> Sha1 sha1) file.file_chunks));
349 end;
350 current_files := file :: !current_files;
351 Hashtbl.add files_by_uid file_id file;
352 file_add file_impl file_state;
353 must_share_file file;
354 file
356 let new_download ?(metadata=false) file_id t torrent_diskname user =
357 let file_temp = Filename.concat !!DO.temp_directory
358 (Printf.sprintf "BT-%s" (Sha1.to_string file_id)) in
359 new_file ~metadata file_id t torrent_diskname file_temp FileDownloading user
361 let ft_by_num = Hashtbl.create 13
362 let ft_counter = ref 0
364 let new_ft file_name user =
365 incr ft_counter;
366 let rec ft = {
367 ft_file = file_impl;
368 ft_id = !ft_counter;
369 ft_filename = file_name;
370 ft_retry = (fun _ -> ());
371 } and file_impl = {
372 (dummy_file_impl ()) with
373 impl_file_owner = user;
374 impl_file_group = user.user_default_group;
375 impl_file_fd = None;
376 impl_file_size = zero;
377 impl_file_downloaded = Int64.zero;
378 impl_file_val = ft;
379 impl_file_ops = ft_ops;
380 impl_file_age = last_time ();
381 impl_file_best_name = file_name;
384 Hashtbl.add ft_by_num !ft_counter ft;
385 file_add file_impl FileDownloading;
388 let _dot_string s h =
389 let len = String.length s in
390 let char2hex c =
391 let ic = int_of_char c in
392 if ic >= 65 && ic <= 70 then
393 string_of_int (ic - 55)
394 else begin
395 if ic >= 97 && ic <= 102 then
396 string_of_int (ic - 87)
397 else
398 Printf.sprintf "%c" c
401 let rec iter i b =
402 if i < len then begin
403 if h then Buffer.add_string b (char2hex s.[i])
404 else Buffer.add_char b s.[i];
405 if i < len-1 then Buffer.add_char b '.';
406 iter (i+1) b;
407 end else b;
409 Buffer.contents (iter 0 (Buffer.create (len*2)))
411 let dot_string s =
412 _dot_string s false
414 let dot_string_h s =
415 _dot_string s true
417 let dot_string_of_list s l =
418 let buf = Buffer.create (List.length l) in
419 List.iter (fun i -> Buffer.add_char buf s.[i]) l;
420 dot_string (Buffer.contents buf)
422 let dot_string_of_string s =
423 let buf = Buffer.create 20 in
424 let found_non_int = ref false in
425 String.iter (fun s ->
426 match s with
427 | '0' .. '9' ->
428 if !found_non_int then Buffer.add_char buf '.';
429 found_non_int := false;
430 Buffer.add_char buf s
431 | _ -> found_non_int := true
432 ) s;
433 Buffer.contents buf
435 (* check string s for char c (dec) at position l (list) *)
436 let check_all s c l =
437 let ch = char_of_int c in
438 List.for_all (fun i -> s.[i] = ch) l
440 let check_int s p =
442 ignore (int_of_string (String.sub s p 1));
443 true
444 with _ -> false
446 let strip_leading_zeroes s =
447 let l = String.length s in
448 let rec aux i =
449 if i = l then "0"
450 else if s.[i] <> '0' then String.sub s i (l - i)
451 else aux (i + 1) in
452 aux 0
454 (* from azureus/gpl *)
455 let decode_az_style s =
456 if check_all s 45 [0;7] then begin
457 let s_id = (String.sub s 1 2) in
458 let brand =
459 match s_id with
460 | "AR" -> Brand_arctic
461 | "AZ" -> Brand_azureus
462 | "BB" -> Brand_bitbuddy
463 | "BC" -> Brand_bitcomet
464 | "BR" -> Brand_bitrocket
465 | "BS" -> Brand_btslave
466 | "BX" -> Brand_bittorrentx
467 | "CT" (* ctorrent *)
468 | "CD" -> Brand_ctorrent
469 | "lt" (* libtorrent *)
470 | "LT" -> Brand_libtorrent
471 | "MT" -> Brand_moonlighttorrent
472 | "SB" -> Brand_swiftbit
473 | "SN" -> Brand_sharenet
474 | "SS" -> Brand_swarmscope
475 | "SZ" (* shareaza *)
476 | "S~" -> Brand_shareaza
477 | "TN" -> Brand_torrentdotnet
478 | "TS" -> Brand_torrentstorm
479 | "XT" -> Brand_xantorrent
480 | "ZT" -> Brand_ziptorrent
481 | "bk" -> Brand_bitkitten
482 | "MP" -> Brand_moopolice
483 | "UM" -> Brand_utorrent_mac
484 | "UT" -> Brand_utorrent
485 | "KT" -> Brand_ktorrent
486 | "LP" -> Brand_lphant
487 | "TR" -> Brand_transmission
488 | "HN" -> Brand_hydranode
489 | "RT" -> Brand_retriever
490 | "PC" -> Brand_cachelogic
491 | "ES" -> Brand_electricsheep
492 | "qB" -> Brand_qbittorrent
493 | "QT" -> Brand_qt4
494 | "UL" -> Brand_uleecher
495 | "XX" -> Brand_xtorrent
496 | "AG" (* ares *)
497 | "A~" -> Brand_ares
498 | "AX" -> Brand_bitpump
499 | "DE" -> Brand_deluge
500 | "TT" -> Brand_tuotu
501 | "SD" (* Thunder (aka XùnLéi) *)
502 | "XL" -> Brand_xunlei
503 | "FT" -> Brand_foxtorrent
504 | "BF" -> Brand_bitflu
505 | "OS" -> Brand_oneswarm
506 | "LW" -> Brand_limewire
507 | "HL" -> Brand_halite
508 | "MR" -> Brand_miro
509 | "PD" -> Brand_pando
510 | _ -> Brand_unknown
512 if brand = Brand_unknown then None else
513 let version =
514 match brand with
515 (* 4.56 *)
516 | Brand_bitpump
517 | Brand_bitcomet -> (String.sub s 4 1) ^ "." ^ (String.sub s 5 2)
518 (* 3.4.5 *)
519 | Brand_tuotu
520 | Brand_utorrent_mac
521 | Brand_oneswarm
522 | Brand_utorrent -> (String.sub s 3 1) ^ "." ^ (String.sub s 4 1) ^ "." ^ (String.sub s 5 1)
523 (* 3.45 *)
524 | Brand_transmission -> (String.sub s 3 1) ^ "." ^ (String.sub s 4 2)
525 (* 34.56 *)
526 | Brand_ctorrent -> (strip_leading_zeroes (String.sub s 3 2)) ^ "." ^ (strip_leading_zeroes(String.sub s 5 2))
527 (* 3.4.5->[R=RC.6|D=Dev|''] *)
528 | Brand_ktorrent ->
529 let x = match s.[5] with
530 | 'R' -> " RC" ^ (String.sub s 6 1)
531 | 'D' -> " Dev"
532 | _ -> ""
534 (String.sub s 3 1) ^ "." ^ (String.sub s 4 1) ^ x
535 (* 3.4(56) *)
536 | Brand_bitrocket -> (String.sub s 3 1) ^ "." ^ (String.sub s 4 1) ^ "(" ^ (String.sub s 5 2) ^ ")"
537 (* v 3456 *)
538 | Brand_xtorrent -> "v" ^ (strip_leading_zeroes (String.sub s 3 4))
539 (* BitFlu is too complicated YMDD (Y+M -> HEX) eg. 7224 is 2007.02.24 *)
540 | Brand_bitflu -> ""
541 (* 3.4.5.6 *)
542 | _ -> (dot_string (String.sub s 3 4))
544 Some (brand, version)
545 end else
546 None
548 let decode_tornado_style s =
549 if s.[5] = '-' then begin
550 let check_brand c =
551 match c with
552 | 'T' -> Brand_bittornado
553 | 'S' -> Brand_shadow
554 | 'A' -> Brand_abc
555 | 'U' -> Brand_upnp
556 | 'O' -> Brand_osprey
557 | 'R' -> Brand_tribler
558 | _ -> Brand_unknown
560 let bv = ref None in
562 if s.[5] ='-' && s.[6] ='-' && s.[7] ='-' then begin
563 let brand = check_brand s.[0] in
564 if not (brand = Brand_unknown) then
565 bv := Some (brand, (dot_string_h (String.sub s 1 3)));
567 else if s.[6] = (char_of_int 48) then begin
568 let brand = check_brand s.[0] in
569 if not (brand = Brand_unknown) then
570 bv := Some (brand, ("LM " ^ dot_string_h (String.sub s 1 3)));
571 end;
573 end else
574 None
576 let decode_mainline_style s =
577 if check_all s 45 [2;7] && check_int s 1 then begin
578 let c_id = s.[0] in
579 let brand =
580 match c_id with
581 | 'M' -> Brand_mainline
582 | _ -> Brand_unknown
584 if brand = Brand_unknown then None
585 else Some (brand, (dot_string_of_string (String.sub s 1 6)))
586 end else
587 None
589 let decode_simple_style s =
590 let simple_list = ref
591 [ (0, "martini", Brand_martiniman, "");
592 (0, "oernu", Brand_btugaxp, "");
593 (0, "BTDWV-", Brand_deadmanwalking, "");
594 (0, "PRC.P---", Brand_btplus, "II");
595 (0, "P87.P---", Brand_btplus, "");
596 (0, "S587Plus", Brand_btplus, "");
597 (5, "Azureus", Brand_azureus, "2.0.3.2");
598 (0, "-G3", Brand_g3torrent, "");
599 (0, "-AR", Brand_arctic, "");
600 (4, "btfans", Brand_simplebt, "");
601 (0, "btuga", Brand_btugaxp, "");
602 (0, "BTuga", Brand_btugaxp, "");
603 (0, "DansClient", Brand_xantorrent, "");
604 (0, "Deadman Walking-", Brand_deadmanwalking, "");
605 (0, "346-", Brand_torrenttopia, "");
606 (0, "271-", Brand_greedbt, "2.7.1");
607 (10, "BG", Brand_btgetit, "");
608 (0, "a00---0", Brand_swarmy, "");
609 (0, "a02---0", Brand_swarmy, "");
610 (0, "10-------", Brand_jvtorrent, "");
611 (0, "T00---0", Brand_teeweety, "");
612 (0, "LIME", Brand_limewire, "");
613 (0, "AZ2500BT", Brand_btyrant, "");
614 (0, "Mbrst", Brand_burst, (dot_string_of_list s [5;7;9]));
615 (0, "Plus", Brand_plus, (dot_string_of_list s [4;5;6]));
616 (0, "OP", Brand_opera, (dot_string(String.sub s 2 4)));
617 (0, "eX", Brand_exeem, (String.sub s 2 18));
618 (0, "turbobt", Brand_turbobt, (String.sub s 7 5));
619 (0, "btpd", Brand_btpd, (dot_string(String.sub s 5 3)));
620 (0, "XBT", Brand_xbt, (dot_string(String.sub s 3 3)));
621 (0, "-FG", Brand_flashget, (dot_string(String.sub s 4 3)));
622 (0, "-SP", Brand_bitspirit, (dot_string(String.sub s 3 3)));
625 let len = List.length !simple_list in
626 let rec check pos =
627 if pos >= len then None
628 else
629 let (x,y,z,v) = List.nth !simple_list pos in
630 if (String.sub s x (String.length y)) = y then Some (z,v)
631 else check (pos+1);
633 check 0
635 let decode_rufus s =
636 let release s =
637 let minor = Char.code s.[1] in
638 Printf.sprintf "%d.%d.%d" (Char.code s.[0]) (minor / 10) (minor mod 10) in
639 if "RS" = String.sub s 2 2 then
640 Some (Brand_rufus, release s)
641 else None
643 let decode_bow s =
644 if "BOW" = String.sub s 0 3 ||
645 (check_all s 45 [0;7] && "BOW" = String.sub s 1 3) then
646 Some (Brand_bitsonwheels, (String.sub s 4 3))
647 else None
649 let decode_btuga s =
650 if ("BTM" = String.sub s 0 3) && ("BTuga" = String.sub s 5 5) then
651 Some (Brand_btuga, dot_string(String.sub s 3 2))
652 else None
654 let decode_shadow s =
655 if "S" = String.sub s 0 1 then begin
656 let bv = ref None in
657 if check_all s 45 [6;7;8] then begin
658 let i1 = int_of_string ("0x" ^ String.sub s 1 1) in
659 let i2 = int_of_string ("0x" ^ String.sub s 2 1) in
660 let i3 = int_of_string ("0x" ^ String.sub s 3 1) in
661 bv := Some (Brand_shadow, (Printf.sprintf "%d.%d.%d" i1 i2 i3))
662 end;
664 if s.[8] = (char_of_int 0) then begin
665 let i1 = int_of_char s.[1] in
666 let i2 = int_of_char s.[2] in
667 let i3 = int_of_char s.[3] in
668 bv := Some (Brand_shadow, (Printf.sprintf "%d.%d.%d" i1 i2 i3))
669 end;
671 end else
672 None
674 let decode_bitspirit s =
675 if "BS" = String.sub s 2 2 then begin
676 let bv = ref None in
677 if s.[1] = (char_of_int 0) then bv := Some (Brand_bitspirit, "v1");
678 if s.[1] = (char_of_int 2) then bv := Some (Brand_bitspirit, "v2");
679 if s.[1] = (char_of_int 3) then bv := Some (Brand_bitspirit, "v3");
681 end else
682 None
684 let decode_upnp s =
685 if 'U' = s.[0] && s.[8] = '-' then
686 Some (Brand_upnp, (dot_string (String.sub s 1 3)))
687 else None
689 let decode_old_bitcomet s =
690 let bitcomet = String.sub s 0 4 in
691 if "exbc" = bitcomet || "FUTB" = bitcomet || "xUTB" = bitcomet then
692 let brand = if "LORD" = String.sub s 6 4 then
693 Brand_bitlord else Brand_bitcomet
695 let versionMajorNumber = int_of_char s.[4] in
696 let versionMinorNubmer =
697 match versionMajorNumber with
698 0 -> (int_of_char s.[5])
699 | _ -> ((int_of_char s.[5]) mod 10)
701 let version = Printf.sprintf "%d.%d"
702 versionMajorNumber versionMinorNubmer in
703 Some (brand, version)
704 else None
706 let decode_shareaza s =
707 let rec not_zeros pos =
708 if pos > 15 then true else
709 if s.[pos] = (char_of_int 0) then false else not_zeros (pos+1)
711 let rec weird_crap pos =
712 if pos > 19 then true else
713 let i1 = (int_of_char s.[pos]) in
714 let i2 = (int_of_char s.[(pos mod 16)]) in
715 let i3 = (int_of_char s.[(15 - (pos mod 16))]) in
716 if not (i1 = (i2 lxor i3)) then false else weird_crap (pos+1)
718 if (not_zeros 0) && (weird_crap 16) then Some (Brand_shareaza, "") else None
720 let decode_non_zero s =
721 let max_pos = ((String.length s) - 1) in
722 let zero = char_of_int 0 in
723 let rec find_non_zero pos =
724 if pos > max_pos then max_pos else
725 if not (s.[pos] = zero) then pos else
726 find_non_zero (pos+1)
728 let bv = ref None in
729 (match find_non_zero 0 with
730 8 -> (if "UDP0" = String.sub s 16 4 then
731 bv := Some (Brand_bitcomet, "UDP");
732 if "HTTPBT" = String.sub s 14 6 then
733 bv := Some (Brand_bitcomet, "HTTP"));
734 | 9 -> if check_all s 3 [9;10;11] then
735 bv := Some (Brand_snark, "");
736 | 12 -> if check_all s 97 [12;13] then
737 bv := Some (Brand_experimental, "3.2.1b2")
738 else begin
739 if check_all s 0 [12;13] then
740 bv := Some (Brand_experimental, "3.1")
741 else
742 bv := Some (Brand_mainline, "")
743 end;
744 | _ -> ()
748 (* format is : "-ML" ^ version ( of unknown length) ^ "-" ^ random bytes ( of unknown length) *)
749 let decode_mldonkey_style s =
750 if '-' = s.[0] then begin
751 let s_id = String.sub s 1 2 in
752 let brand =
753 match s_id with
754 | "ML" -> Brand_mldonkey
755 | _ -> Brand_unknown
757 if brand = Brand_unknown then None else
758 let len =
759 (try String.index_from s 3 '-'
760 with _ -> 8) - 3
762 let version = String.sub s 3 len in
763 Some (brand, version)
764 end else None
767 let decoder_list = [
768 decode_az_style;
769 decode_tornado_style;
770 decode_mainline_style;
771 decode_simple_style;
772 decode_bow;
773 decode_shadow;
774 decode_bitspirit;
775 decode_upnp;
776 decode_old_bitcomet;
777 decode_shareaza;
778 decode_non_zero;
779 decode_mldonkey_style;
780 decode_rufus;
781 decode_btuga;
784 let parse_software s =
785 let default = (Brand_unknown, "") in
786 let rec iter = function
787 | [] ->
788 if !verbose_msg_clienttags then lprintf_nl "BTUC: %S" s;
789 default
790 | d :: t ->
791 match (d s) with
792 | None -> iter t
793 | Some (brand, version as bv) ->
794 if !verbose_msg_clienttags then
795 lprintf_nl "BTKC: %S; ID: %S; version: %S" s (brand_to_string brand) version;
798 if Sha1.direct_of_string s = Sha1.null then
799 default
800 else
801 try iter decoder_list
802 with _ -> default
804 let check_client_country_code c =
805 if Geoip.active () then
806 match c.client_country_code with
807 | None ->
808 c.client_country_code <-
809 Geoip.get_country_code_option (fst c.client_host)
810 | _ -> ()
812 let new_client file peer_id kind cc =
814 let c = Hashtbl.find file.file_clients kind in
815 let old_ip = fst c.client_host in
816 c.client_host <- kind;
817 if old_ip <> Ip.null && old_ip <> fst c.client_host then
818 begin
819 c.client_country_code <- None;
820 check_client_country_code c
821 end;
823 with _ ->
824 let brand, release = parse_software (Sha1.direct_to_string peer_id) in
825 let rec c = {
826 client_client = impl;
827 client_sock = NoConnection;
828 client_upload_requests = [];
829 client_connection_control = new_connection_control (());
830 client_file = file;
831 client_host = kind;
832 client_country_code = cc;
833 client_choked = true;
834 client_received_peer_id = false;
835 client_sent_choke = false;
836 client_interested = false;
837 client_uploader = None;
838 client_chunks = [];
839 client_ranges_sent = [];
840 client_range_waiting = None;
841 client_chunk = None;
842 client_uid = peer_id;
843 client_brand = brand;
844 client_release = release;
845 client_bitmap = None;
846 client_allowed_to_write = zero;
847 client_total_uploaded = zero;
848 client_total_downloaded = zero;
849 client_session_uploaded = zero;
850 client_session_downloaded = zero;
851 client_upload_rate = Rate.new_rate ();
852 client_downloaded_rate = Rate.new_rate ();
853 client_connect_time = last_time ();
854 client_blocks_sent = [];
855 client_new_chunks = [];
856 client_good = false;
857 client_num_try = 0;
858 client_alrd_sent_interested = false;
859 client_alrd_sent_notinterested = false;
860 client_interesting = false;
861 client_incoming = false;
862 client_registered_bitfield = false;
863 client_last_optimist = 0;
864 client_dht = false;
865 client_cache_extension = false;
866 client_fast_extension = false;
867 client_utorrent_extension = false;
868 client_ut_metadata_msg = -1L;
869 client_azureus_messaging_protocol = false;
870 } and impl = {
871 dummy_client_impl with
872 impl_client_val = c;
873 impl_client_ops = client_ops;
874 impl_client_upload = None;
875 } in
876 c.client_connection_control.control_min_reask <- 120;
877 check_client_country_code c;
878 new_client impl;
879 Hashtbl.add file.file_clients kind c;
880 file.file_clients_num <- file.file_clients_num + 1;
881 file_add_source (as_file file) (as_client c);
884 let remove_file file =
885 Hashtbl.remove files_by_uid file.file_id;
886 current_files := List2.removeq file !current_files
888 let remove_client c =
889 Hashtbl.remove c.client_file.file_clients c.client_host ;
890 c.client_file.file_clients_num <- c.client_file.file_clients_num - 1;
891 file_remove_source (as_file c.client_file) (as_client c)
893 let remove_tracker url file =
894 if !verbose_msg_servers then
895 List.iter (fun tracker ->
896 lprintf_nl "Old tracker list: %s" (show_tracker_url tracker.tracker_url)
897 ) file.file_trackers;
898 List.iter (fun bad_tracker ->
899 if bad_tracker.tracker_url = url then
900 file.file_trackers <- List2.remove_first bad_tracker file.file_trackers;
901 ) file.file_trackers;
902 if !verbose_msg_servers then
903 List.iter (fun tracker ->
904 lprintf_nl "New tracker list: %s" (show_tracker_url tracker.tracker_url)
905 ) file.file_trackers
907 let tracker_is_enabled t =
908 match t.tracker_status with
909 | Enabled -> true
910 | Disabled_failure (i,_) ->
911 if !!tracker_retries = 0 || i < !!tracker_retries then true else false
912 | _ -> false
914 let torrents_directory = "torrents"
915 let new_torrents_directory = Filename.concat torrents_directory "incoming"
916 let downloads_directory = Filename.concat torrents_directory "downloads"
917 let tracked_directory = Filename.concat torrents_directory "tracked"
918 let seeded_directory = Filename.concat torrents_directory "seeded"
919 let old_directory = Filename.concat torrents_directory "old"
921 (*************************************************************
923 Define a function to be called when the "mem_stats" command
924 is used to display information on structure footprint.
926 **************************************************************)
928 let () =
929 Heap.add_memstat "BittorrentGlobals" (fun level buf ->
930 Printf.bprintf buf "Number of old files: %d\n" (List.length !!old_files);
931 let downloads = ref 0 in
932 let tracked = ref 0 in
933 let seeded = ref 0 in
934 Unix2.iter_directory (fun file -> incr downloads ) downloads_directory;
935 Unix2.iter_directory (fun file -> incr tracked ) tracked_directory;
936 Unix2.iter_directory (fun file -> incr seeded ) seeded_directory;
937 Printf.bprintf buf "Files in downloads directory: %d\n" ! downloads;
938 Printf.bprintf buf "Files in tracked directory: %d\n" ! tracked;
939 Printf.bprintf buf "Files in seeded directory: %d\n" ! seeded;
940 Printf.bprintf buf "files_by_uid: %d\n" (Hashtbl.length files_by_uid);
941 Printf.bprintf buf "ft_by_num: %d\n" (Hashtbl.length ft_by_num);
944 open BT_DHT
946 let () =
947 Heap.add_memstat "BittorrentDHT" (fun _level buf ->
948 match !bt_dht with
949 | None -> ()
950 | Some dht ->
951 let (buckets,nodes,keys,peers) = stat dht in
952 Printf.bprintf buf "Routing : %d nodes in %d buckets\n" nodes buckets;
953 Printf.bprintf buf "Storage : %d keys with %d peers\n" keys peers;
954 List.iter (fun s -> Printf.bprintf buf "%s\n" s) (rpc_stats dht);
955 let queries = ["PING",`Ping;"FIND_NODE",`FindNode;"GET_PEERS",`GetPeers;"ANNOUNCE",`Announce] in
956 Printf.bprintf buf "Outgoing queries : ok/error/timeout\n";
957 List.iter begin fun (name,qt) ->
958 let get k = try Hashtbl.find dht.M.stats (qt,`Out k) with Not_found -> 0 in
959 Printf.bprintf buf "%s: %d/%d/%d\n" name (get `Answer) (get `Error) (get `Timeout);
960 end queries;
961 Printf.bprintf buf "Incoming queries\n";
962 List.iter begin fun (name,qt) ->
963 let get () = try Hashtbl.find dht.M.stats (qt,`In) with Not_found -> 0 in
964 Printf.bprintf buf "%s: %d\n" name (get ())
965 end queries