
#
# validate the IPv4 header checksum.
# $bytes[] is an array of 16bit values, with $cnt elements in the array.
#
sub dump {
	print "\n";
	for ($i = 0; $i < $#bytes; $i++) {
		printf "%04x ", $bytes[$i];
	}
	print "\n";
}

sub dosum {
	local($seed) = $_[0];
	local($start) = $_[1];
	local($max) = $_[2];
	local($idx) = $start;
	local($lsum) = $seed;

	for ($idx = $start, $lsum = $seed; $idx < $max; $idx++) {
		$lsum += $bytes[$idx];
	}
	$lsum = ($lsum & 0xffff) + ($lsum >> 16);
	$lsum = ~$lsum & 0xffff;
	return $lsum;
}

sub ipv4check {
	local($base) = $_[0];
	$hl = $bytes[$base] / 256;
	return if (($hl >> 4) != 4);	# IPv4 ?
	$hl &= 0xf;
	$hl <<= 1;			# get the header length in 16bit words

	$hs = &dosum(0, $base, $base + $hl);
	$osum = $bytes[$base + 5];

	if ($hs != 0) {
		$bytes[$base + 5] = 0;
		$hs2 = &dosum(0, $base, $base + $hl);
		$bytes[$base + 5] = $osum;
		printf " IP: ($hl,%x) %x != %x", $hs, $osum, $hs2;
	} else {
		print " IP($base): ok ";
	}

	#
	# Recognise TCP & UDP and calculate checksums for each of these.
	#
	if (($bytes[$base + 4] & 0xff) == 6) {
		&tcpcheck($base);
	}

	if (($bytes[$base + 4] & 0xff) == 17) {
		&udpcheck($base);
	}

	if (($bytes[$base + 4] & 0xff) == 1) {
		&icmpcheck($base);
	}
	if ($base == 0) {
		print "\n";
	}
}

sub tcpcheck {
	local($base) = $_[0];
	local($hl) = $bytes[$base] / 256;
	return if (($hl >> 4) != 4);
	return if ($bytes[$base + 3] & 0x1fff);
	$hl &= 0xf;
	$hl <<= 1;

	local($hs2);
	local($hs) = 6;	# TCP
	local($len) = $bytes[$base + 1] - ($hl << 1);
	$hs += $len;
	$hs += $bytes[$base + 6];	# source address
	$hs += $bytes[$base + 7];
	$hs += $bytes[$base + 8];	# destination address
	$hs += $bytes[$base + 9];
	local($tcpsum) = $hs;

	local($thl) = $bytes[$base + $hl + 6] >> 8;
	$thl &= 0xf0;
	$thl >>= 2;

	$x = $bytes[$base + 1];
	$y = ($cnt - $base) * 2;
	$z = 0;
	if ($bytes[$base + 1] > ($cnt - $base) * 2) {
		print "[cnt=$cnt base=$base]";
		$x = $bytes[$base + 1];
		$y = ($cnt - $base) * 2;
		$z = 1;
	} elsif (($cnt - $base) * 2 < $hl + 20) {
		$x = ($cnt - $base) * 2;
		$y = $hl + 20;
		$z = 2;
	} elsif (($cnt - $base) * 2 < $hl + $thl) {
		$x = ($cnt - $base) * 2;
		$y = $hl + $thl;
		$z = 3;
	} elsif ($len < $thl) {
		$x = ($cnt - $base) * 2;
		$y = $len;
		$z = 4;
	}

	if ($z) {
		print " TCP: missing data($x $y $z) $hl";
#		&dump();
		return;
	}

	local($tcpat) = $base + $hl;
	$hs = &dosum($tcpsum, $tcpat, $cnt);
	if ($hs != 0) {
		local($osum) = $bytes[$tcpat + 8];
		$bytes[$base + $hl + 8] = 0;
		$hs2 = &dosum($tcpsum, $tcpat, $cnt);
		$bytes[$tcpat + 8] = $osum;
		printf " TCP: (%x) %x != %x", $hs, $osum, $hs2;
	} else {
		print " TCP: ok ($x $y)";
	}
}

sub udpcheck {
	local($base) = $_[0];
	local($hl) = $bytes[0] / 256;
	return if (($hl >> 4) != 4);
	return if ($bytes[3] & 0x1fff);
	$hl &= 0xf;
	$hl <<= 1;

	local($hs2);
	local($hs) = 17;	# UDP
	local($len) = $bytes[$base + 1] - ($hl << 1);
	$hs += $len;
	$hs += $bytes[$base + 6];	# source address
	$hs += $bytes[$base + 7];
	$hs += $bytes[$base + 8];	# destination address
	$hs += $bytes[$base + 9];
	local($udpsum) = $hs;

	if ($bytes[$base + 1] > ($cnt - $base) * 2) {
		print " UDP: missing data(1)";
		return;
	} elsif ($bytes[$base + 1] < ($hl << 1) + 8) {
		print " UDP: missing data(2)";
		return;
	} elsif (($cnt - $base) * 2 < ($hl << 1) + 8) {
		print " UDP: missing data(3)";
		return;
	}

	local($udpat) = $base + $hl;
	$hs = &dosum($udpsum, $udpat, $cnt);
	local($osum) = $bytes[$udpat + 3];

	#
	# It is valid for UDP packets to have a 0 checksum field.
	# If it is 0, then display what it would otherwise be.
	#
	if ($osum == 0) {
		printf " UDP: => %x", $hs;
	} elsif ($hs != 0) {
		$bytes[$udpat + 3] = 0;
		$hs2 = &dosum($udpsum, $udpat, $cnt);
		$bytes[$udpat + 3] = $osum;
		printf " UDP: (%x) %x != %x", $hs, $osum, $hs2;
	} else {
		print " UDP: ok";
	}
}

sub icmpcheck {
	local($base) = $_[0];
	local($hl) = $bytes[$base + 0] / 256;
	return if (($hl >> 4) != 4);
	return if ($bytes[3] & 0x1fff);
	$hl &= 0xf;
	$hl <<= 1;

	local($hs);
	local($hs2);

	local($len) = $bytes[$base + 1] - ($hl << 1);

	if ($bytes[$base + 1] > ($cnt - $base) * 2) {
		print " ICMP: missing data(1)";
		return;
	} elsif ($bytes[$base + 1] < ($hl << 1) + 8) {
		print " ICMP: missing data(2)";
		return;
	} elsif (($cnt - $base) * 2 < ($hl << 1) + 8) {
		print " ICMP: missing data(3)";
		return;
	}

	local($osum) = $bytes[$base + $hl + 1];
	$bytes[$base + $hl + 1] = 0;
	$hs2 = &dosum(0, $base + $hl, $cnt);
	$bytes[$base + $hl + 1] = $osum;

	if ($osum != $hs2) {
		printf " ICMP: (%x) %x != %x", $hs, $osum, $hs2;
	} else {
		print " ICMP: ok";
	}
	if ($base == 0) {
		$type = $bytes[$hl] >> 8;
		if ($type == 3 || $type == 4 || $type == 5 ||
		    $type == 11 || $type == 12) {
			&ipv4check($hl + 4);
		}
	}
}

while ($#ARGV >= 0) {
	open(I, "$ARGV[0]") || die $!;
	print "--- $ARGV[0] ---\n";
	$multi = 0;
	while (<I>) {
		chop;
		s/#.*//g;

		#
		# If the first non-comment, non-empty line of input starts
		# with a '[', then allow the input to be a multi-line hex
		# string, otherwise it has to be all on one line.
		#
		if (/^\[/) {
			$multi=1;
			s/^\[[^]]*\]//g;

		}
		s/^ *//g;
		if (length == 0) {
			next if ($cnt == 0);
			&ipv4check(0);
			$cnt = 0;
			$multi = 0;
			next;
		}

		#
		# look for 16 bits, represented with leading 0's as required,
		# in hex.
		#
		s/\t/ /g;
		while (/^[0-9a-fA-F][0-9a-fA-F] [0-9a-fA-F][0-9a-fA-F] .*/) {
			s/^([0-9a-fA-F][0-9a-fA-F]) ([0-9a-fA-F][0-9a-fA-F]) (.*)/$1$2 $3/;
		}
		while (/.* [0-9a-fA-F][0-9a-fA-F] [0-9a-fA-F][0-9a-fA-F] .*/) {
$b=$_;
			s/(.*?) ([0-9a-fA-F][0-9a-fA-F]) ([0-9a-fA-F][0-9a-fA-F]) (.*)/$1 $2$3 $4/g;
		}
		if (/.* [0-9a-fA-F][0-9a-fA-F] [0-9a-fA-F][0-9a-fA-F]/) {
$b=$_;
			s/(.*?) ([0-9a-fA-F][0-9a-fA-F]) ([0-9a-fA-F][0-9a-fA-F])/$1 $2$3/g;
		}
		while (/^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F].*/) {
			$x = $_;
			$x =~ s/([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]).*/$1/;
			$x =~ s/ *//g;
			$y = hex $x;
			s/[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] *(.*)/$1/;
			$bytes[$cnt] = $y;
#print "bytes[$cnt] = $x\n";
			$cnt++;
		}

		#
		# Pick up stragler bytes.
		#
		if (/^[0-9a-fA-F][0-9a-fA-F]/) {
			$y = hex $_;
			$bytes[$cnt++] = $y * 256;
		}
		if ($multi == 0 && $cnt > 0) {
			&ipv4check(0);
			$cnt = 0;
		}
	}

	if ($cnt > 0) {
		&ipv4check(0);
	}
	close(I);
	shift(@ARGV);
}
