3 # This software was written by Philipp Wuensche <cryx-otsync@h3q.com>
4 # It is considered beerware.
8 #use Convert::Bencode_XS qw(:all);
9 use Convert
::Bencode
qw(:all);
14 # enable verbose output
17 # tracker from where we get our sync data
18 my @trackers = ('127.0.0.1:8989');
19 # tracker to upload merged data
20 my @client_tracker = ('127.0.0.1:8989');
22 # time to wait between syncs
23 my $sleeptime = '300';
26 my $ssl_cert = 'cert.pem';
27 my $ssl_key = 'key.pem';
30 print "Syncing from: $_\n";
32 foreach(@client_tracker) {
33 print "Syncing to: $_\n";
39 # global hash for storing the merged syncs
44 my @bencoded_sync_data;
46 # fetch the sync from every tracker and put it into an array in bencoded form
47 foreach my $tracker (@trackers) {
48 my $bencoded_sync = fetch_sync
($tracker);
49 # my $bencoded_sync = fetch_sync_from_file($file);
50 if($bencoded_sync ne 0 && $bencoded_sync =~ /^d4\:sync/) {
51 push(@bencoded_sync_data,$bencoded_sync);
55 # bdecode every sync and throw it into the merged-sync
56 foreach my $bencoded_sync (@bencoded_sync_data) {
57 print "Doing merge...\n";
58 merge_sync
(bdecode
($bencoded_sync));
59 my $num_torrents = keys(%merged_syncs);
61 print "number of torrents: $num_torrents\n";
64 # number of max. peers in one changeset
66 # max number of changesets per commit
67 my $max_changesets = 10;
71 my @escaped_changesets;
73 # run until all hashes are put into changesets and commited to the trackers
74 while(keys(%merged_syncs) != 0) {
76 foreach my $hash (keys(%merged_syncs)) {
77 print "Starting new changeset\n" if($peer_count == 0 && $debug);
78 my $num_peers = keys(%{$merged_syncs{$hash}});
80 print "\t$peer_count peers for $hash_count hashes in changeset\n" if($debug);
82 my $pack_hash = pack('H*',$hash);
84 # as long as the peer_limit is not reached, add new hashes with peers to the changeset hash-table
85 if($peer_count < $peer_limit) {
86 print "\t\tAdd $num_peers peers for $hash changeset\n" if($debug);
87 $peer_count = $peer_count + $num_peers;
88 foreach my $peer_socket (keys(%{$merged_syncs{$hash}})) {
90 my $flags = $merged_syncs{$hash}{$peer_socket};
92 print "\t\t\tAdd $peer_socket $flags\n" if($debug);
94 my $pack_peer = packme
($peer_socket,$flags);
96 $changeset->{'sync'}->{$pack_hash} = $changeset->{'sync'}->{$pack_hash}.$pack_peer;
99 # hash is stored in the changeset, delete it from the hash-table
100 delete $merged_syncs{$hash};
103 # the peer_limit is reached or we are out of torrents, so start preparing a changeset
104 if($peer_count >= $peer_limit || keys(%merged_syncs) == 0) {
106 print "Commit changeset for $hash_count hashes with $peer_count peers total\n" if($debug);
108 # bencode the changeset
109 my $enc_changeset = bencode
($changeset);
111 # URL-escape the changeset and put into an array of changesets
112 my $foobar = uri_escape
($enc_changeset);
113 push(@escaped_changesets,$foobar);
115 # the changeset is ready and stored, so delete it from the changeset hash-table
116 delete $changeset->{'sync'};
120 print "\n\n\n" if($debug);
123 # if enought changesets are prepared or we are out of torrents for more changesets,
124 # sync the changesets to the trackers
125 if($#escaped_changesets == $max_changesets || keys(%merged_syncs) == 0) {
127 sync_to_tracker
(\
@escaped_changesets);
128 undef @escaped_changesets;
134 print "Sleeping for $sleeptime seconds\n";
138 sub connect_tracker
{
139 # connect a tracker via HTTPS, returns the body of the response
142 $ENV{HTTPS_DEBUG
} = 0;
143 $ENV{HTTPS_CERT_FILE
} = $ssl_cert;
144 $ENV{HTTPS_KEY_FILE
} = $ssl_key;
146 my $ua = new LWP
::UserAgent
;
147 my $req = new HTTP
::Request
('GET', $url);
148 my $res = $ua->request($req);
150 my $content = $res->content;
152 if($res->is_success()) {
155 print $res->code."|".$res->status_line."\n";
160 sub sync_to_tracker
{
161 # commit changesets to a tracker
162 my @changesets = @
{(shift)};
164 # prepare the URI with URL-encoded changesets concatenated by a &
166 foreach my $set (@changesets) {
167 $uri .= 'changeset='.$set.'&';
169 my $uri_length = length($uri);
171 # commit the collection of changesets to the tracker via HTTPS
172 foreach my $tracker (@client_tracker) {
173 print "\t\tTracker: $tracker (URI: $uri_length)\n";
174 my $url = "https://$tracker/".$uri;
175 connect_tracker
($url);
181 # returns ipaddr, port and flags in packed format
182 my $peer_socket = shift;
185 my($a,$b,$c,$d,$port) = split(/[\.,\:]/,$peer_socket);
186 my $pack_peer = pack('C4 n1 b16',$a,$b,$c,$d,$port,$flags);
193 # returns ipaddr. in quad-form with port (a.b.c.d:port) and flags as bitfield
195 # - 4 byte ipaddr. (unsigned char value)
196 # - 2 byte port (unsigned short in "network" (big-endian) order)
197 # - 2 byte flags (bit string (ascending bit order inside each byte))
200 my($a, $b, $c, $d, $port, $flags) = unpack('C4 n1 b16',$data);
201 my $peer_socket = "$a\.$b\.$c\.$d\:$port";
203 return($peer_socket,$flags);
207 # fetch sync from a tracker
209 my $url = "https://$tracker/sync";
211 print "Fetching from $url\n";
213 my $body = connect_tracker
($url);
215 if($body && $body =~ /^d4\:sync/) {
222 sub fetch_sync_from_file
{
223 # fetch sync from a file
226 print "Fetching from file $file\n";
236 # This builds a hash table with the torrenthash as keys. The value is a hash table again with the peer-socket as keys
237 # and flags in the value
239 # 60dd2beb4197f71677c0f5ba92b956f7d04651e5 =>
240 # 192.168.23.23:2323 => 0000000000000000
241 # 23.23.23.23:2342 => 0000000100000000
242 # b220b4d7136e84a88abc090db88bec8604a808f3 =>
243 # 42.23.42.23:55555 => 0000000000000000
247 my $nonuniq_hash_counter = 0;
248 my $nonuniq_peer_counter = 0;
249 my $hash_counter = 0;
250 my $peer_counter = 0;
252 foreach my $key (keys(%{$hashref->{'sync'}})) {
253 # start merge for every sha1-hash in the sync
254 my $hash = unpack('H*',$key);
257 $nonuniq_hash_counter++ if exists $merged_syncs{$hash};
259 while(${$hashref->{'sync'}}{$key} ne "")
261 # split the value into 8-byte and unpack it for getting peer-socket and flags
262 my($peer_socket,$flags) = unpackme
(substr(${$hashref->{'sync'}}{$key},0,8,''));
265 $nonuniq_peer_counter++ if exists $merged_syncs{$hash}{$peer_socket};
267 # Create a hash table with sha1-hash as key and a hash table as value.
268 # The hash table in the value has the peer-socket as key and flags as value
269 # If the entry already exists, the flags are ORed together, if not it is ORed with 0
270 $merged_syncs{$hash}{$peer_socket} = $flags | $merged_syncs{$hash}{$peer_socket};
273 print "$hash_counter hashes $nonuniq_hash_counter non-uniq, $peer_counter peers $nonuniq_peer_counter non-uniq.\n";
280 print "CHANGESET DEBUG OUTPUT\n";
282 print Dumper
$hashref;
283 foreach my $key (keys(%{$hashref->{'sync'}})) {
284 my $hash = unpack('H*',$key);
286 print "Changeset for $hash\n";
287 while(${$hashref->{'sync'}}{$key} ne "")
290 my($peer_socket,$flags) = unpackme
(substr(${$hashref->{'sync'}}{$key},0,8,''));
291 print "\tSocket: $peer_socket Flags: $flags\n";