#!/usr/bin/perl -d
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# This program chews up symbolic MIBs and produces a  C  table.   The #
# syntax  of a .mib file is rather poorly characterized; this program #
# basically just handles what I've seen in actual .mib files.         #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
$, = ' ';
$" = '.';

# Patterns:
$psym = '([A-Za-z][-_A-Za-z0-9]*)';
$pnum  = '([0-9]+)';
$DF = '::=';
$LB = '{';
$RB = '}';
$LP = '(';
$RP = ')';

%onam = ('iso', '1');	# OID table with name as key.
%oval = ('1', 'iso');	# OID table with value as key.

# Read the MIB and process each line.
while () {
	&tok(2) || last;
	if ($t[1] eq 'OBJECT-TYPE') {
	   if ($t[0] eq 'IMPORTS') {
		   $imports = &findstr(';');
		   @imports = splice(@t,0,$imports);
		   shift @t;	# Eat up the semicolon.
		   next;
	   }
		$name = $t[0];
		splice(@t,0,2);
		next;
	}
	if ($t[0] eq 'ACCESS') {
		&access($name,$t[1]);
		splice(@t,0,2);
		next;
	}
	if ($t[0] eq 'DESCRIPTION') {
		&descr($name,$t[1]);
		splice(@t,0,2);
		next;
	}
	if ($t[0] eq 'INDEX' && $t[1] eq $LB) {
		splice(@t,0,2);
		&index($name);
		next;
	}
	if ($t[0] eq "STATUS") {
		# It's always "mandatory".
		splice(@t,0,2);
		next;
	}
	if ($t[0] eq "SYNTAX") {
		splice(@t,0,1);
		&syntax($name);
		next;
	}
	if ($t[0] eq $DF && $t[1] eq $LB) {	# "::= { ... }" alone.
		splice(@t,0,2);
		&oid($name);
		next;
	}
	&tok(5) || last;

	if ($t[1] eq 'OBJECT' 
	&&  $t[2] eq 'IDENTIFIER'
	&&  $t[3] eq $DF
	&&  $t[4] eq $LB) {
		$objid = $t[0];
		splice(@t,0,5);
		&oid($objid);
		next;
	}
# We don't actually do anything with "fooEntry ::= SEQUENCE { ... }"
	if ($t[1] eq $DF 
	&&  $t[2] eq 'SEQUENCE'
	&&  $t[3] eq $LB) {
		if (($i = &findstr($RB)) < 0) {
			print STDERR "Line $.: Can't find '$RB'\n";
			next;
		}
		while (@t && $t[0] ne $RB) {
			splice(@t,0,$i + 1);	# Discard them.
		}
		next;
	}
	if ($t[1] eq 'DEFINITIONS'
	&&  $t[2] eq $DF
	&&  $t[3] eq 'BEGIN') {
		$mib = $t[0];
		print "MIB: $mib\n";
		splice(@t,0,4);
		next;
	}
	print "Line $. in $ARGV: Can't handle \"$t[0]\"\n";
	shift @t;
	next;
}

# Dump the table in object-id order:
print "Table:\n";
foreach $v (sort(oidorder keys(%oval))) {
	$i = &inst($oval{$v});
	$n = $oval{$v};
	$N = &caps($n);
	$a = $access{$n} || 'XX';
	$d = $descr{$n};
	$t = ($i eq 'G') ? 'GROUP' : &caps($type{$n}) || 'OTHER';
	print "{{$v.$i},";
	print "\t$t,";
	print "\t$N,";
	print "\t$a,";
	print "\t\"$n\"";
	print "},";
#	print "\t/* $d */" if $d;
	print "\n";
}
exit 0;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# &access(name,type)                                                  #
#                                                                     #
# This registers the read/write permissions for the named variable.   #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub access {
	local($name,$val) = @_;
	$val =~ s/\s+//g;
	if ($val eq 'read-only') {
		$val = 'RO'
	} elsif ($val eq 'read-write') {
		$val = 'RW'
	} elsif ($val eq 'write-only') {
		$val = 'WO'
	} elsif ($val eq 'not-accessible') {
		$val = 'NA'
	}
	$access{$name} = $val;
}
# - - - - - - - - - - - - - - - - - - - - - - - - #
# &addoid($name,$val,...)                         #
#                                                 #
# Add a new oid to the tables; return its value.  #
# - - - - - - - - - - - - - - - - - - - - - - - - #
sub addoid {
	local($n,@l) = @_;
	local($l);
	if ($onam{$n}) {
		print STDERR "Redefining $n (was $onam{$n})\n";
	}
	$l = "@l";
	$onam{$n} = $l;
	$oval{$l} = $n;
	print "OID: $l $n\n";
	return $l;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#    &caps(string)                                                    #
#  Return the string with all letters capitalized.                    #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub caps {
	local($x) = @_;
	$x =~ tr/a-z/A-Z/;
	$x;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#   &descr(name,text)                                                 #
# This registers a text description for the named variable.           #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub descr {
	local($name,$text) = @_;
	$descr{$name} = $text;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#   &findstr(pattern)                                                 #
# Given  a  string,  expand  @t until the string appears.  The return #
# value is the index where it is found.  The return value is -1 if we #
# hit EOF first.                                                      #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub findstr {
	local($s) = @_; local($n);
	while (($n = &havestr($s)) < 0) {
		if (!&tok(@t + 1)) {
			print "EOF\n";
			return -1;
	}	}
	$fs = $n;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#
# Get the value for a symbolic name, or '*' if it isn't defined. #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#
sub getoid {
	local($n) = @_;
	if ($onam{$n}) {return $onam{$n};}
	print STDERR "Line $Line: Can't decode \"$n\"\n";
	return '*';
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#     &havestr(string)                                                #
#  Look thru @t for a string that exactly matches  the  string.   The #
#  return value is the index where it is found. The return value is 0 #
#  if we hit EOF first.                                               #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub havestr {
	local($s) = @_; local($n);
	for $n (0 .. $#t) {
		if ($t[$n] eq $s) {
			return $n;
		}
	}
	-1;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#   &index(name)                                                      #
# This registers an index for the named variable. This routine should #
# find @t filled with "type ...  }".                                  #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub index {
	local($name) = @_;
	local(@list,@new); # local($indx);
	if (($indx = &findstr($RB)) < 0) {
		print STDERR "Line $Line: Can't find matching '}'\n";
	} else {
		@new = splice(@t,0,$indx);
		@list = (@list , @new);
		$index{$name} = "@list";
		$indices{$name} = @list;
		splice(@t,0,1);		# Get rid of the right brace.
	}
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# &inst(name)                                                         #
#                                                                     #
# This returns a code for the object instance for the name.  The code #
# is  '0',  'G',  or a string of 'F's, one per object-instance value. #
# This is rather kludgy,  and  uses  the  fact  that  the  INDEX  was #
# attached  to  the  parent node, not to this one.  Also, we look for #
# IpAddress" items, and convert them to four 'F's.                    #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub inst {
	local($name) = @_;
	local($inst,$i,@i,$p,$t);
	if ($p = $parent{$name}) {
		if ($i = $index{$p}) {
			@i = split(/\./,$i);
			for (@i) {
				$t = $type{$_};
				if ($type{$_} eq "IpAddress") {
					$inst .= '.F.F.F.F';
				} else {
					$inst .= '.F';
				}
			}
			$inst =~ s/^\.//;
		} elsif ($children{$name}) {
			$inst = 'G';
		} else {
			$inst = '0';
		}
	} else {
		$inst = 'R';	# Root is assumed to be a group.
	}
	$inst;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# This function takes a name and a definition in the @t vector.   The #
# definition  is scanned, and values are added to the %onam and %oval #
# tables if possible.  There are several formats.                     #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub oid {
	local($name) = @_;
	local($i,$o,$xx);
	if (&findstr($RB) < 0) {
		printf STDERR "Line $Line: Incomplete object definition.\n";
		return 0;
	}
	while ($t[0] ne $RB) {
		if (@t > 4 && $t[1] eq $LP && $t[3] eq $RP) {	# name(num)
			$o = &addoid($t[0],$o,$t[2]);
			&parent($xx,$t[0]) if $xx;
			$xx = $t[0];	# Remember last list item.
			splice(@t,0,4);
			next;
		}
		if (@t > 2 && $t[1] > 0) {		# name num
			$o = &addoid($name,&getoid($t[0]),$t[1]);
			&parent($t[0],$name);
			$xx = $t[0];
			splice(@t,0,2);
			next;
		} 
		if (@t > 1) { 			# name
			$o .= &getoid($t[0]);
			&parent($xx,$t[0]) if $xx;
			$xx = $t[0];
			splice(@t,0,1);
			next;
		}
		print STDERR "Line $Line: Can't handle \"@t\"\n";
	}
	splice(@t,0,1);	# Delete the right brace.
	$oval{$name};	# Probably not used.
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Here's a routine to determine which of two oid values comes first.  #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub oidorder {
	local($i,@a,@b);
	@a = split(/\./,$a);
	@b = split(/\./,$b);
	for ($i=0; $a[$i] && $b[$i]; $i++) {
		return -1 if ($a[$i] < $b[$i]);
		return  1 if ($a[$i] > $b[$i]);
	}
	return  1 if $a[$i];
	return -1 if $b[$i];
	return  0;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#
# &parent(x,y,...) enters x as the parent of the rest of the args. #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#
sub parent {
	local($p) = shift(@_);
	foreach (@_) {
		$parent{$_} = $p;
		$children{$p} .= $_ . ' ';
	}
}
# - - - - - - - - - - - - - - - #
# Process the SEQUENCE command. #
# - - - - - - - - - - - - - - - #
sub seq {
	local($item);
#	print "Seq: $SEQUENCE $item\n";
	&getblock($RB);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#    &getblock(delimiter)                                             #
# Eat up input until the next line that ends with the delimiter,  and #
# combine it all into one long string.                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub getblock {
	local($dlm) = @_;
	local($val);
	while (<>) {
#		++$Line;
		close(ARGV) if eof;
		s/\n$/ /;
		s/\s*--\s*.*//;
#		print "Got: $_\n";
		if (/(.*)$dlm\s*$/) {
			$val .= $1;
			return $val;
		}
		$val .= $_;
	}
	printf STDERR "Line $.: EOF in {block}\n";
	$val;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# This takes care of the cruft necessary to keep $Line and $ARGV  set #
# to the correct thing for error messages.                            #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub newfile {
	print "Close $ARGV.\n";
	close(ARGV);
#	$Line = 0;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#   &syntax(name)                                                     #
# This registers the type of the named variable.  The type is one  or #
# more tokens in @t.                                                  #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub syntax {
	local($name) = @_;
	local($type,$i);
	$type = shift @t;
	if ($type eq 'INTEGER') {
		if ($t[0] eq $LP) {	# INTEGER ( x .. y )
			$i = &findstr($RP);
			splice(@t,0,$i+1);	# Discard them.
		} elsif ($t[0] eq $LB) {	# INTEGER { x .. y }
			$i = &findstr($RB);
			splice(@t,0,$i+1);	# Discard them.
		}
	} elsif ($type eq 'OBJECT') {
		if ($t[0] eq 'IDENTIFIER') {
			$type = 'OBJID';
			shift @t;
		}
	} elsif ($type eq 'OCTET') {
		if ($t[0] eq 'STRING') {
			$type = 'STRING';
			shift @t;
		}
	} elsif ($type eq 'DisplayString') {
		if ($t[0] eq $LP) {	# DisplayString ( x .. y )
			$i = &findstr($RP);
			splice(@t,0,$i+1);	# Discard them.
		}
	} elsif ($type eq 'Sequence' || $type eq 'SEQUENCE') {
		if ($t[0] eq 'of' || $t[0] eq 'OF') {
			shift @t;	# Delete the 'of'.
			shift @t;	# Delete the type.
		}
	}
	$type{$name} = $type;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#   &tok(n)                                                           #
# Read in enough to make sure that there at least n tokens in the  @t #
# list;  return the number of tokens in @t, or zero for failure if we #
# are at EOF and there aren't n tokens left.                          #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
sub tok {
	local($n) = @_;
	local($line,@line,$s);
	while (@t < $n) {
		close(ARGV) if eof;
		$line = <> 
			|| return 0;	# EOF.
		if (++$Line == 1) {
			printf "File $ARGV:\n";
		}
		while ($line) {
			$line =~ s/^\s+//;	# Strip off initial white stuff.
			last if !$line;
			if ($line =~ s/^--.*//) {
				last;
			}
			if (substr($line,0,1) eq '"') {	# Quoted string.
				if ($line =~ s/^"(.*)"\n//) {	# Complete on this line.
					$s = $1;
				} elsif ($line =~ s/^"(.*)//) {	# Incomplete.
					$s = $1;
					$s .= &getblock('"');
				}
				@t = (@t , $s);
				next;
			}
			if ($line =~ s/^$psym//) {	# Symbolic name.
				@t = (@t , $1);
				next;
			}
			if ($line =~ s/^$pnum//) {	# Number.
				@t = (@t , $1);
				next;
			}
			if ($line =~ s/^([(){};,])\s*//) {	# Assorted punctuation.
				@t = (@t , $1);
				next;
			}
			if ($line =~ s/^(::=)\s*//) {	# BNF definition.
				@t = (@t , $1);
				next;
			}
			if ($line =~ s/^(\.\.)\b//) {	# BNF definition.
				@t = (@t , $1);
				next;
			}
		}
	}
	return scalar(@t);
}
