#!perl
use strict;
use warnings;
use Data::Dumper;
use Test::More;
use File::Spec;

use lib File::Spec->catdir(qw(t lib));
BEGIN {
    lib->import('lib')
        if !-d 't';
}

use Sereal::TestSet qw(:all);
use Sereal::Decoder qw(decode_sereal);
use Sereal::Decoder::Constants qw(:all);

# These tests are a manual attempt at seeing the decoder blow up on
# bad input. This obviously shouldn't segfault and neither leak
# memory.

plan tests => 56;
my ($ok, $out, $err);

SCOPE: {
    check_fail(Header(), qr/Not a valid Sereal document/i, "Cannot decode just header");

    my $badheaderpacket = "srX".chr(SRL_PROTOCOL_VERSION) . chr(0) . integer(1);
    check_fail($badheaderpacket, qr/Bad Sereal header/i, "Packet with invalid header blows up");

    my $bad_nested_packet = Header() . array(integer(1), 7777);
    check_fail($bad_nested_packet, qr/Sereal: Error/, "Random crap in packet");

    my $obj_packet = Header() . chr(SRL_HDR_OBJECT).short_string("Foo").chr(SRL_HDR_REFN).integer(1);
    check_fail($obj_packet, qr/refuse_obj/, "refusing objects option", {refuse_objects => 1});

    # strictly speaking not entirely correct; also: +16 for the snappy flag isn't exactly API
    my $h = SRL_MAGIC_STRING . chr(1+16) . chr(0) . chr(SRL_HDR_UNDEF);
    check_fail($h, qr/Snappy/, "refusing Snappy option", {refuse_snappy => 1});

    # Tests for limiting number of acceptable hash entries
    my $hash_packet = Header() . hash(map short_string($_), 1..2000);
    $h = decode_sereal($hash_packet);
    is(ref($h), "HASH", "Deserializes as hash");
    is(scalar(keys(%$h)), 1000, "Hash has 1000 entries");
    $h = decode_sereal($hash_packet, {max_num_hash_entries => 0});
    is(ref($h), "HASH", "Deserializes as hash (2)");
    $h = decode_sereal($hash_packet, {max_num_hash_entries => 1000});
    is(ref($h), "HASH", "Deserializes as hash (3)");

    check_fail($hash_packet, qr/Sereal: Error/, "Setting hash limit option (1)", {max_num_hash_entries => 1});
    check_fail($hash_packet, qr/Sereal: Error/, "Setting hash limit option (999)", {max_num_hash_entries => 999});

    my $valid_packet = Header(2) . short_string("foo");
    my $foo = decode_sereal($valid_packet);
    is($foo, "foo", "Have valid test packet");
    $valid_packet =~ s/^=srl/=\xF3rl/;
    $foo = eval { decode_sereal($valid_packet) };
    ok(!defined($foo), "SRL_MAGIC_STRING_HIGHBIT implies protocol v3 or higher.");

    substr($valid_packet,4,1,chr(3));
    $foo = eval { decode_sereal($valid_packet) };
    is($foo,"foo", "Have valid test packet after asserting high bit in magic with protocol v3");

    utf8::encode($valid_packet);
    check_fail($valid_packet, qr/UTF-8/, "Sereal determined 'accidental' UTF8 upgrade");
}

pass("Alive"); # done


sub check_fail {
    my ($data, $err_like, $name, $options) = @_;
    $options ||= {};

    my ($ok, $out, $err);
    ($ok, $out, $err) = dec_func($data, $options);
    expect_fail($ok, $out, $err, $err_like, $name . "(func)");
    ($ok, $out, $err) = dec_obj($data, $options);
    expect_fail($ok, $out, $err, $err_like, $name . "(OO)");
}

sub expect_fail {
    my ($ok, $out, $err, $err_like, $name) = @_;
    ok(!$ok, "$name, got exception");
    ok(!defined($out), "$name, got no output");
    if (defined $err_like) {
        like($err, $err_like, "$name, matched exception");
    }
    else {
        diag($err);
    }
}

sub dec_func {
    my ($ok, $out);
    $ok = eval {
        $out = decode_sereal(@_);
        1
    };
    my $err = $@ || 'Zombie error';
    return($ok, $out, $err);
}


sub dec_obj {
    my ($ok, $out);
    my $obj = Sereal::Decoder->new(@_ > 1 ? $_[1] : {});
    $ok = eval {
        $out = $obj->decode(@_);
        1
    };
    my $err = $@ || 'Zombie error';
    return($ok, $out, $err);
}

