The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
use strict;
use warnings;
use Test::More;
use Module::Build;
use Time::HiRes qw[];
use lib q[../../../../lib];
use Net::BitTorrent::Util qw[:all];
$|++;
my $test_builder       = Test::More->builder;
my $simple_dot_torrent = q[./t/900_data/950_torrents/953_miniswarm.torrent];
chdir q[../../../../] if not -f $simple_dot_torrent;
my $build    = Module::Build->current;
my $okay_tcp = $build->notes(q[okay_tcp]);
my $verbose  = $build->notes(q[verbose]);
$SIG{__WARN__} = (
    $verbose
    ? sub {
        diag(sprintf(q[%02.4f], Time::HiRes::time- $^T), q[ ], shift);
        }
    : sub { }
);
$|++;
plan tests => 88;
SKIP: {
    is(bencode(4),   q[i4e],   q[integer]);
    is(bencode(0),   q[i0e],   q[zero]);
    is(bencode(-0),  q[i0e],   q[zero w/ sign]);
    is(bencode(-10), q[i-10e], q[negative integer]);
    is(bencode(+10), q[i10e],  q[positive integer]);
    is(bencode(time x 50), q[i] . (time x 50) . q[e], q[large number]);
    is(bencode(q[Perl]), q[4:Perl], q[string]);
    is(bencode(q[]),     q[0:],     q[null string]);
    is(bencode(),        q[0:],     q[undef]);
    is(bencode(undef),   q[0:],     q[undef]);
    is(bencode(\undef),  q[],       q[ref to undef]);
    is(bencode(q[0:0:]), q[4:0:0:], q[odd string (malformed bencoded int)]);
    is(bencode([1, 2, 3]), q[li1ei2ei3ee], q[list (integers)]);
    is(bencode([q[one], q[two], q[three]]),
        q[l3:one3:two5:threee], q[list (strings)]);
    is(bencode([q[three], 1, 2, 3, q[one], q[two]]),
        q[l5:threei1ei2ei3e3:one3:twoe],
        q[list (mixed scalars)]);
    is(bencode([]), q[le], q[empty list]);
    is(bencode([[q[Alice], q[Bob]], [2, 3]]),
        q[ll5:Alice3:Bobeli2ei3eee], q[list^list]);
    is(bencode({date => {month => q[January], year => 2009}}),
        q[d4:dated5:month7:January4:yeari2009eee],
        q[dictionary]);
    is(bencode({}), q[de], q[dictionary from empty hash]);
    is(bencode({age => 25, eyes => q[blue]}),
        q[d3:agei25e4:eyes4:bluee], q[dictionary from anon hash]);
    is( length bencode({join(q[], map(chr($_), 0 .. 255)) =>
                            join(q[], map(chr($_), 0 .. 255))
                       }
        ),
        522,
        q[anon hash with long key/value pair]
    );
    bencode(
        {sub => sub { return my $brain; }
        }
    );
    my $str = q[d7:Integeri42e4:Listl6:item 1i2ei3ee6:String9:The Valuee];
    is(bdecode, undef, q[Empty string]);
    my ($hashref) = bdecode($str);
    ok defined $hashref, q[bdecode() returned something];
    is ref $hashref, q[HASH], q[bdecode() returned a valid hash ref];
    ok defined $hashref->{q[Integer]}, q[Integer key present];
    is $hashref->{q[Integer]}, 42, q[  and its the correct value];
    ok defined $hashref->{q[String]}, q[String key present];
    is ${$hashref;}{q[String]}, q[The Value], q[  and its the correct value];
    ok defined $hashref->{q[List]}, q[List key present];
    is @{$hashref->{q[List]}}, 3, q[  list has 3 elements];
    is ${$hashref->{q[List]}}[0], q[item 1], q[    first element correct];
    is ${$hashref->{q[List]}}[1], 2,         q[    second element correct];
    is ${$hashref->{q[List]}}[2], 3,         q[    third element correct];
    my ($encoded_string) = bencode($hashref);
    ok defined $encoded_string, q[bencode() returned something];
    is $encoded_string, $str, q[  and it appears to be the correct value];
    is(bdecode(q[i4e]),      4,      q[integer]);
    is(bdecode(q[i0e]),      0,      q[zero]);
    is(bdecode(q[i-0e]) + 0, 0,      q[zero w/ sign]);
    is(bdecode(q[i-10e]),    -10,    q[negative integer]);
    is(bdecode(q[0:]),       q[],    q[zero length string]);
    is(bdecode(q[3:abc]),    q[abc], q[string]);
    is_deeply(scalar bdecode(bencode([qw[this that and the other]])),
              [qw[this that and the other]],
              q[list in scalar context]);
    is_deeply(scalar bdecode(bencode({qw[this that the other]})),
              {qw[this that the other]}, q[dictionary in scalar context]);
    is(bdecode(q[10:1234567890]), q[1234567890], q[integer cast as string]);
    is(bdecode(q[02:xy]), undef, q[string with leading zero in length]);
    is(bdecode(q[i03e]),  3,     q[integer with leading zero]);
    is(bdecode(q[0:0:]), '',
        q[trailing junk at end of valid bencoded string]);
    is_deeply([bdecode(q[leanfdldjfh])],
              [[], q[anfdldjfh]],
              q[empty list with trailing garbage]);
    is_deeply([bdecode(q[i])],           [], q[aborted integer]);
    is_deeply([bdecode(q[i0])],          [], q[unterminated integer]);
    is_deeply([bdecode(q[ie])],          [], q[empty integer]);
    is_deeply([bdecode(q[i341foo382e])], [], q[malformed integer]);
    is_deeply([bdecode(q[i123])],        [], q[unterminated integer]);
    is_deeply([bdecode(q[1:])],          [], q[string longer than data]);
    is_deeply([bdecode(q[i6easd])], [6, q[asd]],
              q[string with trailing junk]);
    is_deeply([bdecode(q[35208734823ljdahflajhdf])],
              [], q[garbage looking vaguely like a string, with large count]);
    is_deeply([bdecode(q[2:abfdjslhfld])],
              [q[ab], q[fdjslhfld]],
              q[string with trailing garbage]
    );
    is_deeply([bdecode(q[02:xy])], [],
              q[string with extra leading zero in count]);
    is_deeply([bdecode(q[l])], [[], q[]], q[unclosed empty list]);
    is_deeply([bdecode(q[relwjhrlewjh])], [], q[complete garbage]);
    is_deeply([bdecode(q[d])], [{}, q[]], q[unclosed empty dict]);
    is_deeply([bdecode(q[defoobar])],
              [{}, q[foobar]],
              q[Catch invalid format (empty dictionary w/ trailing garbage)]);
    is_deeply([bdecode(q[d3:fooe])],
              [{foo => undef}, undef],
              q[Catch invalid format (dictionary w/ empty key)]);
    is_deeply([bdecode(q[d1:ei0e1:mde1:pi48536e1:v15:µTorrent 1.7.7e])],
              [{e => 0,
                m => {},
                p => 48536,
                v => "\xC2\xB5Torrent 1.7.7"
               },
               q[]
              ],
              q[Complex structure (empty dictionary, 'safe' hex chars)]
    );
    is(compact(qw[127.0.0.1:98]), qq[\x7F\0\0\1\0b],  q[localhost]);
    is(compact(qw[127.0.0.1:0]),  qq[\x7F\0\0\1\0\0], q[port number of zero]);
    is(compact(qw[127.0.0.1:5000]),
        qq[\x7F\0\0\1\23\x88], q[Large port number]);
    is(compact(qw[127.0.0.1:65535]),
        qq[\x7F\0\0\1\xFF\xFF], q[Large port number]);
    is(compact(qw[127.0.0.1:3265 255.25.21.32:0]),
        qq[\x7F\0\0\1\f\xC1\xFF\31\25 \0\0],
        q[short list of peers]);
    is(compact(qw[127.0.0.1:3265 127.0.0.1:3265]),
        qq[\x7F\0\0\1\f\xC1], q[Filter duplicates]);
    is(compact(qw[127.0.0.1:000065]),
        qq[\x7F\0\0\1\0A], q[Port with leading zeros]);
    is(compact(qw[270.0.0.1:0]), undef, q[Invalid IP address: 270.0.0.1:0]);
    is(compact(qw[127.0.0.1:5000000]),
        undef, q[Port number beyond ephemeral range: 127.0.0.1:5000000]);
    is(compact(qw[127.0.0.1:500:30]),
        undef, q[Invalid IP address: 127.0.0.1:500:30]);
    is(compact(qw[127.0.0.1.3:50030]),
        undef, q[Invalid IP address: 127.0.0.1.3:50030]);
    is(compact(q[127.0.0.1:ABC]), undef, q[Bad port number: 127.0.0.1:ABC]);
    is(compact(qw[127.0.0.1:3265 255.25.21.32:0:4554845]),
        qq[\x7F\0\0\1\f\xC1], q[Invalid peer in list]);
    is(compact(qw[]),    undef, q[Empty list]);
    is(compact(q[]),     undef, q[String]);
    is(compact(undef),   undef, q[undef]);
    is(compact(),        undef, q[undef]);
    is(uncompact(q[]),   undef, q[Empty string]);
    is(uncompact(undef), undef, q[undef string]);
    is(uncompact(),      undef, q[undef string]);
    is(uncompact(qq[\x7F\0\0\1\xFF\xFF]),
        qw[127.0.0.1:65535], q[Large port number]);
    my @peers = sort(uncompact(qq[\x7F\0\0\1\f\xC1\xFF\31\25 \0\0]));
    is(@peers,    2,                 q[Short list of peers...]);
    is($peers[0], q[127.0.0.1:3265], q[   ...First checks out.]);
    is($peers[1], q[255.25.21.32:0], q[   ...Second is okay too.]);

    # TODO: IPv6 faking IPv4 and invalid IPv4 (ex: 999.999.999.999)
    #is( uncompact(qw[127.0.0.1:98]), qq[\x7F\0\0\1\0b], q[localhost] );
    #is( uncompact(qw[127.0.0.1:0]),
    #    qq[\x7F\0\0\1\0\0], q[port number of zero] );
    #is( uncompact(qw[127.0.0.1:5000]),
    #    qq[\x7F\0\0\1\23\x88], q[large port number] );
}
__END__
Copyright (C) 2008-2009 by Sanko Robinson <sanko@cpan.org>

This program is free software; you can redistribute it and/or modify it
under the terms of The Artistic License 2.0.  See the LICENSE file
included with this distribution or
http://www.perlfoundation.org/artistic_license_2_0.  For
clarification, see http://www.perlfoundation.org/artistic_2_0_notes.

When separated from the distribution, all POD documentation is covered by
the Creative Commons Attribution-Share Alike 3.0 License.  See
http://creativecommons.org/licenses/by-sa/3.0/us/legalcode.  For
clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.

$Id: Util.t d3c97de 2009-09-12 04:31:46Z sanko@cpan.org $