###################################################
# parse an Wireshark conformance file
# Copyright jelmer@samba.org 2005
# released under the GNU GPL

=pod

=head1 NAME

Parse::Pidl::Wireshark::Conformance - Conformance file parser for Wireshark

=head1 DESCRIPTION

This module supports parsing Wireshark conformance files (*.cnf).

=head1 FILE FORMAT

Pidl needs additional data for Wireshark output. This data is read from 
so-called conformance files. This section describes the format of these 
files.

Conformance files are simple text files with a single command on each line.
Empty lines and lines starting with a '#' character are ignored.
Arguments to commands are seperated by spaces.

The following commands are currently supported:

=over 4

=item I<TYPE> name dissector ft_type base_type mask valsstring alignment

Register new data type with specified name, what dissector function to call 
and what properties to give header fields for elements of this type.

=item I<NOEMIT> type

Suppress emitting a dissect_type function for the specified type

=item I<PARAM_VALUE> type param

Set parameter to specify to dissector function for given type.

=item I<HF_FIELD> hf title filter ft_type base_type valsstring mask description

Generate a custom header field with specified properties.

=item I<HF_RENAME> old_hf_name new_hf_name

Force the use of new_hf_name when the parser generator was going to 
use old_hf_name.

This can be used in conjunction with HF_FIELD in order to make more than 
one element use the same filter name.

=item I<ETT_FIELD> ett

Register a custom ett field

=item I<STRIP_PREFIX> prefix

Remove the specified prefix from all function names (if present).
	
=item I<PROTOCOL> longname shortname filtername

Change the short-, long- and filter-name for the current interface in
Wireshark.

=item I<FIELD_DESCRIPTION> field desc

Change description for the specified header field. `field' is the hf name of the field.

=item I<IMPORT> dissector code...

Code to insert when generating the specified dissector. @HF@ and 
@PARAM@ will be substituted.

=item I<INCLUDE> filename

Include conformance data from the specified filename in the dissector.

=item I<TFS> hf_name "true string" "false string"

Override the text shown when a bitmap boolean value is enabled or disabled.

=item I<MANUAL> fn_name

Force pidl to not generate a particular function but allow the user 
to write a function manually. This can be used to remove the function 
for only one level for a particular element rather than all the functions and 
ett/hf variables for a particular element as the NOEMIT command does.

=back

=head1 EXAMPLE

	INFO_KEY OpenKey.Ke

=cut

package Parse::Pidl::Wireshark::Conformance;

require Exporter;
use vars qw($VERSION);
$VERSION = '0.01';

@ISA = qw(Exporter);
@EXPORT_OK = qw(ReadConformance ReadConformanceFH valid_ft_type valid_base_type);

use strict;

use Parse::Pidl qw(fatal warning error);
use Parse::Pidl::Util qw(has_property);
use Parse::Pidl::Typelist qw(addType);

sub handle_type($$$$$$$$$$)
{
	my ($pos,$data,$name,$dissectorname,$ft_type,$base_type,$mask,$valsstring,$alignment) = @_;

	unless(defined($alignment)) {
		error($pos, "incomplete TYPE command");
		return;
	}

	unless ($dissectorname =~ /.*dissect_.*/) {
		warning($pos, "dissector name does not contain `dissect'");
	}

	unless(valid_ft_type($ft_type)) {
		warning($pos, "invalid FT_TYPE `$ft_type'");
	}

	unless (valid_base_type($base_type)) {
		warning($pos, "invalid BASE_TYPE `$base_type'");
	}

	$dissectorname =~ s/^\"(.*)\"$/$1/g;

	if (not ($dissectorname =~ /;$/)) {
		warning($pos, "missing semicolon");
	}

	$data->{types}->{$name} = {
		NAME => $name,
		POS => $pos,
		USED => 0,
		DISSECTOR_NAME => $dissectorname,
		FT_TYPE => $ft_type,
		BASE_TYPE => $base_type,
		MASK => $mask,
		VALSSTRING => $valsstring,
		ALIGNMENT => $alignment
	};

	addType({
		NAME => $name,
		TYPE => "CONFORMANCE",
		BASEFILE => "conformance file",
		DATA => {
			NAME => $name,
			TYPE => "CONFORMANCE",
			ALIGN => $alignment
		}
	});
}

sub handle_tfs($$$$$)
{
	my ($pos,$data,$hf,$trues,$falses) = @_;

	unless(defined($falses)) {
		error($pos, "incomplete TFS command");
		return;
	}

	$data->{tfs}->{$hf} = {
		TRUE_STRING => $trues,
		FALSE_STRING => $falses
	};
}

sub handle_hf_rename($$$$)
{
	my ($pos,$data,$old,$new) = @_;

	unless(defined($new)) {
		warning($pos, "incomplete HF_RENAME command");
		return;
	}

	$data->{hf_renames}->{$old} = {
		OLDNAME => $old,
		NEWNAME => $new,
		POS => $pos,
		USED => 0
	};
}

sub handle_param_value($$$$)
{
	my ($pos,$data,$dissector_name,$value) = @_;

	unless(defined($value)) {
		error($pos, "incomplete PARAM_VALUE command");
		return;
	}

	$data->{dissectorparams}->{$dissector_name} = {
		DISSECTOR => $dissector_name,
		PARAM => $value,
		POS => $pos,
		USED => 0
	};
}

sub valid_base_type($)
{
	my $t = shift;
	return 0 unless($t =~ /^BASE_.*/);
	return 1;
}

sub valid_ft_type($)
{
	my $t = shift;
	return 0 unless($t =~ /^FT_.*/);
	return 1;
}

sub handle_hf_field($$$$$$$$$$)
{
	my ($pos,$data,$index,$name,$filter,$ft_type,$base_type,$valsstring,$mask,$blurb) = @_;

	unless(defined($blurb)) {
		error($pos, "incomplete HF_FIELD command");
		return;
	}

	unless(valid_ft_type($ft_type)) {
		warning($pos, "invalid FT_TYPE `$ft_type'");
	}

	unless(valid_base_type($base_type)) {
		warning($pos, "invalid BASE_TYPE `$base_type'");
	}

	$data->{header_fields}->{$index} = {
		INDEX => $index,
		POS => $pos,
		USED => 0,
		NAME => $name,
		FILTER => $filter,
		FT_TYPE => $ft_type,
		BASE_TYPE => $base_type,
		VALSSTRING => $valsstring,
		MASK => $mask,
		BLURB => $blurb
	};
}

sub handle_strip_prefix($$$)
{
	my ($pos,$data,$x) = @_;

	push (@{$data->{strip_prefixes}}, $x);
}

sub handle_noemit($$$)
{
	my ($pos,$data,$type) = @_;

	if (defined($type)) {
	    $data->{noemit}->{$type} = 1;
	} else {
	    $data->{noemit_dissector} = 1;
	}
}

sub handle_manual($$$)
{
	my ($pos,$data,$fn) = @_;

	unless(defined($fn)) {
		warning($pos, "incomplete MANUAL command");
		return;
	}

    $data->{manual}->{$fn} = 1;
}

sub handle_protocol($$$$$$)
{
	my ($pos, $data, $name, $longname, $shortname, $filtername) = @_;

	$data->{protocols}->{$name} = {
		LONGNAME => $longname,
		SHORTNAME => $shortname,
		FILTERNAME => $filtername
	};
}

sub handle_fielddescription($$$$)
{
	my ($pos,$data,$field,$desc) = @_;

	unless(defined($desc)) {
		warning($pos, "incomplete FIELD_DESCRIPTION command");
		return;
	}

	$data->{fielddescription}->{$field} = {
		DESCRIPTION => $desc,
		POS => $pos,
		USED => 0
	};
}

sub handle_import
{
	my $pos = shift @_;
	my $data = shift @_;
	my $dissectorname = shift @_;

	unless(defined($dissectorname)) {
		error($pos, "no dissectorname specified");
		return;
	}

	$data->{imports}->{$dissectorname} = {
		NAME => $dissectorname,
		DATA => join(' ', @_),
		USED => 0,
		POS => $pos
	};
}

sub handle_ett_field
{
	my $pos = shift @_;
	my $data = shift @_;
	my $ett = shift @_;

	unless(defined($ett)) {
		error($pos, "incomplete ETT_FIELD command");
		return;
	}

	push (@{$data->{ett}}, $ett);
}

sub handle_include
{
	my $pos = shift @_;
	my $data = shift @_;
	my $fn = shift @_;

	unless(defined($fn)) {
		error($pos, "incomplete INCLUDE command");
		return;
	}

	ReadConformance($fn, $data);
}

my %field_handlers = (
	TYPE => \&handle_type,
	NOEMIT => \&handle_noemit, 
	MANUAL => \&handle_manual,
	PARAM_VALUE => \&handle_param_value, 
	HF_FIELD => \&handle_hf_field, 
	HF_RENAME => \&handle_hf_rename, 
	ETT_FIELD => \&handle_ett_field,
	TFS => \&handle_tfs,
	STRIP_PREFIX => \&handle_strip_prefix,
	PROTOCOL => \&handle_protocol,
	FIELD_DESCRIPTION => \&handle_fielddescription,
	IMPORT => \&handle_import,
	INCLUDE => \&handle_include
);

sub ReadConformance($$)
{
	my ($f,$data) = @_;
	my $ret;

	open(IN,"<$f") or return undef;

	$ret = ReadConformanceFH(*IN, $data, $f);

	close(IN);

	return $ret;
}

sub ReadConformanceFH($$$)
{
	my ($fh,$data,$f) = @_;

	my $incodeblock = 0;

	my $ln = 0;

	foreach (<$fh>) {
		$ln++;
		next if (/^#.*$/);
		next if (/^$/);

		s/[\r\n]//g;

		if ($_ eq "CODE START") {
			$incodeblock = 1;
			next;
		} elsif ($incodeblock and $_ eq "CODE END") {
			$incodeblock = 0;
			next;
		} elsif ($incodeblock) {
			if (exists $data->{override}) {
				$data->{override}.="$_\n";
			} else {
				$data->{override} = "$_\n";
			}
			next;
		}

		my @fields = /([^ "]+|"[^"]+")/g;

		my $cmd = $fields[0];

		shift @fields;

		my $pos = { FILE => $f, LINE => $ln };

		next unless(defined($cmd));

		if (not defined($field_handlers{$cmd})) {
			warning($pos, "Unknown command `$cmd'");
			next;
		}
		
		$field_handlers{$cmd}($pos, $data, @fields);
	}

	if ($incodeblock) {
		warning({ FILE => $f, LINE => $ln }, 
			"Expecting CODE END");
		return undef;
	}

	return 1;
}

1;
