The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use Test::More tests => 17;
use Test::Deep;
use Net::SPDY::Compressor;

use strict;
use warnings;

# Read chunk of size same as prototype from an IO::Handle and
# compare it against the prototype.
sub readcmp
{
	my $handle = shift;
	my $proto = shift;
	my $msg = shift;

	my $buf;
	my $toread = length $proto;
	while ($toread) {
		my $read = $handle->read ($buf, $toread, (length $proto) - $toread);
		die $! unless defined $read;
		die 'Short read' unless $read;
		$toread -= $read;
	}

	is (join (' ', map { sprintf '%02x', $_ } unpack 'C*', $buf),
		join (' ', map { sprintf '%02x', $_ } unpack 'C*', $proto),
		$msg);
}


BEGIN { use_ok ('Net::SPDY::Framer') };
require_ok ('Net::SPDY::Framer');

pipe (R, W);
my $framer = new Net::SPDY::Framer ({
	compressor => new Net::SPDY::Compressor,
	socket => *W,
});
ok ($framer, 'Created a framer instance');

my $sess1 = fork;
die $! unless defined $sess1;

unless ($sess1) {
	close R;

	$framer->write_frame (
		type	=> Net::SPDY::Framer::SETTINGS,
		id_values => [{
			flags	=> 1,
			value	=> 1000,
			id	=> 4
		}]
	);

	$framer->write_frame (
		type => Net::SPDY::Framer::SYN_STREAM,
		stream_id => 1,
		associated_stream_id => 0,
		priority => 2,
		flags => 0,
		slot => 0,
		headers => [
			':method'	=> 'GET',
			':scheme'	=> 'https',
			':path'		=> '/',
			':version'	=> 'HTTP/1.1',
			':host'		=> 'example.com:443',
		],
	);

	$framer->write_frame (
		type	=> Net::SPDY::Framer::HEADERS,
		flags => 0,
		stream_id => 1,
		headers => [
			'User-Agent'	=> 'spdy-client Net-Spdy/0.1',
		],
	);

	$framer->write_frame (
		type => Net::SPDY::Framer::SYN_STREAM,
		stream_id => 3,
		associated_stream_id => 1,
		priority => 0,
		flags => Net::SPDY::Framer::FLAG_FIN,
		slot => 0,
		headers => [
			':method'	=> 'GET',
			':scheme'	=> 'https',
			':path'		=> '/',
			':version'	=> 'HTTP/1.1',
			':host'		=> 'example.com:443',
			'User-Agent'	=> 'spdy-client Net-Spdy/0.1',
		],
	);

	$framer->write_frame (
		control => 0,
		data => '',
		stream_id => 1,
		flags => Net::SPDY::Framer::FLAG_FIN,
	);


	$framer->write_frame (
		type	=> Net::SPDY::Framer::PING,
		id	=> 0x706c6c6d,
	);

	$framer->write_frame (
		type	=> Net::SPDY::Framer::GOAWAY,
		last_good_stream_id => 3,
		status	=> 0,
	);

	exit 0;
}
close W;

readcmp (*R,
	"\x80\x03\x00\x04".
	"\x00\x00\x00\x0c".
	"\x00\x00\x00\x01".
	"\x01\x00\x00\x04".
	"\x00\x00\x03\xe8",
	'SETTINGS frame read correctly');

readcmp (*R,
	"\x80\x03\x00\x01".
	"\x00\x00\x00\x86".
	"\x00\x00\x00\x01".
	"\x00\x00\x00\x00".
	"\x40\x00".
	"\x78\x3f\xe3\xc6\xa7\xc2\x00\x6c\x00\x93\xff".
	"\x00\x00\x00\x05".
	"\x00\x00\x00\x07:method\x00\x00\x00\x03GET".
	"\x00\x00\x00\x07:scheme\x00\x00\x00\x05https".
	"\x00\x00\x00\x05:path\x00\x00\x00\x01/".
	"\x00\x00\x00\x08:version\x00\x00\x00\x08HTTP/1.1".
	"\x00\x00\x00\x05:host\x00\x00\x00\x0fexample.com:443".
	"\x00\x00\x00\xff\xff",
	'SYN_STREAM frame read correctly');

readcmp (*R,
	"\x80\x03\x00\x08".
	"\x00\x00\x00\x3c".
	"\x00\x00\x00\x01".
	"\x00\x2e\x00\xd1\xff".
	"\x00\x00\x00\x01".
	"\x00\x00\x00\x0auser-agent\x00\x00\x00\x18spdy-client Net-Spdy/0.1".
	"\x00\x00\x00\xff\xff",
	'HEADERS frame read correctly');

readcmp (*R,
	"\x80\x03\x00\x01".
	"\x01\x00\x00\xaa".
	"\x00\x00\x00\x03".
	"\x00\x00\x00\x01".
	"\x00\x00\x00\x96".
	"\x00\x69".
	"\xff".
	"\x00\x00\x00\x06".
	"\x00\x00\x00\x07:method\x00\x00\x00\x03GET".
	"\x00\x00\x00\x07:scheme\x00\x00\x00\x05https".
	"\x00\x00\x00\x05:path\x00\x00\x00\x01/".
	"\x00\x00\x00\x08:version\x00\x00\x00\x08HTTP/1.1".
	"\x00\x00\x00\x05:host\x00\x00\x00\x0fexample.com:443".
	"\x00\x00\x00\x0auser-agent\x00\x00\x00\x18spdy-client Net-Spdy/0.1".
	"\x00\x00\x00\xff\xff",
	'SYN_STREAM frame read correctly');

readcmp (*R,
	"\x00\x00\x00\x01".
	"\x01\x00\x00\x00",
	'Data frame read correctly');

readcmp (*R,
	"\x80\x03\x00\x06".
	"\x00\x00\x00\x04".
	"pllm",
	'PING frame read correctly');

readcmp (*R,
	"\x80\x03\x00\x07".
	"\x00\x00\x00\x08".
	"\x00\x00\x00\x03".
	"\x00\x00\x00\x00",
	'GOAWAY frame read correctly');

close R;
kill $sess1;
waitpid $sess1, 0;
undef $framer;

pipe (R, W);
my $sess2 = fork;
die $! unless defined $sess2;

unless ($sess2) {
	close R;

	W->print ("\x80\x03\x00\x04".
		"\x00\x00\x00\x0c".
		"\x00\x00\x00\x01".
		"\x01\x00\x00\x04".
		"\x00\x00\x03\xe8");

	W->print ("\x80\x03\x00\x01".
		"\x00\x00\x00\x86".
		"\x00\x00\x00\x01".
		"\x00\x00\x00\x00".
		"\x40\x00".
		"\x78\x3f\xe3\xc6\xa7\xc2\x00\x6c\x00\x93\xff".
		"\x00\x00\x00\x05".
		"\x00\x00\x00\x07:method\x00\x00\x00\x03GET".
		"\x00\x00\x00\x07:scheme\x00\x00\x00\x05https".
		"\x00\x00\x00\x05:path\x00\x00\x00\x01/".
		"\x00\x00\x00\x08:version\x00\x00\x00\x08HTTP/1.1".
		"\x00\x00\x00\x05:host\x00\x00\x00\x0fexample.com:443".
		"\x00\x00\x00\xff\xff");

	W->print ("\x80\x03\x00\x08".
		"\x00\x00\x00\x3c".
		"\x00\x00\x00\x01".
		"\x00\x2e\x00\xd1\xff".
		"\x00\x00\x00\x01".
		"\x00\x00\x00\x0auser-agent\x00\x00\x00\x18spdy-client Net-Spdy/0.1".
		"\x00\x00\x00\xff\xff");

	W->print ("\x80\x03\x00\x01".
		"\x01\x00\x00\xaa".
		"\x00\x00\x00\x03".
		"\x00\x00\x00\x01".
		"\x00\x00\x00\x96".
		"\x00\x69".
		"\xff".
		"\x00\x00\x00\x06".
		"\x00\x00\x00\x07:method\x00\x00\x00\x03GET".
		"\x00\x00\x00\x07:scheme\x00\x00\x00\x05https".
		"\x00\x00\x00\x05:path\x00\x00\x00\x01/".
		"\x00\x00\x00\x08:version\x00\x00\x00\x08HTTP/1.1".
		"\x00\x00\x00\x05:host\x00\x00\x00\x0fexample.com:443".
		"\x00\x00\x00\x0auser-agent\x00\x00\x00\x18spdy-client Net-Spdy/0.1".
		"\x00\x00\x00\xff\xff");

	W->print ("\x00\x00\x00\x01".
		"\x01\x00\x00\x00");

	W->print ("\x80\x03\x00\x06".
		"\x00\x00\x00\x04".
		"pllm");

	W->print ("\x80\x03\x00\x07".
		"\x00\x00\x00\x08".
		"\x00\x00\x00\x03".
		"\x00\x00\x00\x00");

	exit 0;
}
close W;

$framer = new Net::SPDY::Framer ({
	compressor => new Net::SPDY::Compressor,
	socket => *R,
});

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 12,

	entries	=> 1,
	type	=> Net::SPDY::Framer::SETTINGS,
	id_values => [{
		flags	=> 1,
		value	=> 1000,
		id	=> 4
}],
}, 'SETTINGS frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 134,

	type => Net::SPDY::Framer::SYN_STREAM,
	stream_id => 1,
	associated_stream_id => 0,
	priority => 2,
	flags => 0,
	slot => 0,
	headers => [
		':method'	=> 'GET',
		':scheme'	=> 'https',
		':path'		=> '/',
		':version'	=> 'HTTP/1.1',
		':host'		=> 'example.com:443',
	],
}, 'SYN_STREAM frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 60,

	type	=> Net::SPDY::Framer::HEADERS,
	flags => 0,
	stream_id => 1,
	headers => [
		'user-agent'	=> 'spdy-client Net-Spdy/0.1',
	],
}, 'SETTINGS frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 170,

	type => Net::SPDY::Framer::SYN_STREAM,
	stream_id => 3,
	associated_stream_id => 1,
	priority => 0,
	flags => Net::SPDY::Framer::FLAG_FIN,
	slot => 0,
	headers => [
		':method'	=> 'GET',
		':scheme'	=> 'https',
		':path'		=> '/',
		':version'	=> 'HTTP/1.1',
		':host'		=> 'example.com:443',
		'user-agent'	=> 'spdy-client Net-Spdy/0.1',
	],
}, 'SYN_STREAM frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control => 0,
	flags	=> 0,
	length	=> 0,

	data => '',
	stream_id => 1,
	flags => Net::SPDY::Framer::FLAG_FIN,
}, 'Data frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 4,

	type	=> Net::SPDY::Framer::PING,
	id	=> 0x706c6c6d,
}, 'PING frame written correctly');

cmp_deeply ({$framer->read_frame}, {
	control	=> 1,
	version	=> 3,
	flags	=> 0,
	length	=> 8,

	type	=> Net::SPDY::Framer::GOAWAY,
	last_good_stream_id => 3,
	status	=> 0,
}, 'GOAWAY frame written correctly');

kill $sess2;
waitpid $sess2, 0;