/* -*- C -*- */
/*
 * Disassembler for DOCSIS cable modem configuration files
 *
 * $Id: docdisas.c,v 1.1 2000/07/27 16:24:43 petras Exp $
 * 
 * Copyright (C) 2000  Dietmar Petras  <Dietmar.Petras@ELSA.de>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * History:
 * $Log: docdisas.c,v $
 * Revision 1.1  2000/07/27 16:24:43  petras
 * * Based on Revision 1.5 of Disassemble.c
 * * Bug fix with printing of strings with quoted characters.
 * * Verbose output format added (option -v)
 * * Assignments between signed to unsigned corrected.
 *
 * Revision 1.5  2000/07/10 14:29:38  petras
 * Renamed to docdisas.c to allow compilation on DOS with limited file names :-(
 *
 * Revision 1.4  2000/04/17 23:30:22  petras
 * Support for long encoding of length field in SNMP objects.
 *
 * Revision 1.3  2000/04/17 23:20:35  petras
 * Correct version number information from 1.0 to 1.1.
 *
 * Revision 1.2  2000/04/21 12:44:06  petras
 * * bug fix in PrintEOL()
 * * reports missing mandatory fields
 *
 * Revision 1.1  2000/03/01 23:19:37  petras
 * Initial revision.
 *
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include "md5.h"
#include "getopt.h"

int PrettyPrinting = 0;
int Verbose = 0;

#define byte unsigned char

int IndentLevel = 0;
byte *Input;
int InputSize = 0;

/* When not PrettyPrinting, a SNMP_MIB_Object may contain two OBJECT
 * IDENTIFIER, e.g:
 * snmp_mib_object 1.1.1.1 = OBJ_ID 1.1.1.1
 * ExpectingSNMPObjId = 1 indicates, that we expect the first OBJ_ID
*/
int ExpectingSNMPObjId = 0;

/* Ohh, that strange DOS world, but default is UNIX ;-) */
const char *EOL_String = "\n";

int NoErrors = 0;

/* Trace occurance of mandatory fields: */
int Saw_EndOfData = 0;
int Saw_NetworkAccessControl = 0;
int Saw_COS = 0;
int Saw_CM_MIC = 0;
int Saw_CMTS_MIC = 0;

/* {{{ forward declarations */

void TopLevel                  (byte *input, int size);
void ClassOfService            (byte *input, int size);
void ModemCapabilities         (byte *input, int size);
void VendorSpecificInformation (byte *input, int size);
void Telephone                 (byte *input, int size);
void BaselinePrivacy           (byte *input, int size);
void SNMP_MIB_Object           (byte *input, int size);

/* }}} */
/* {{{ helper functions */

void Report(byte *pos, int len) {
  int c=0;
  int i=0;
  if (PrettyPrinting) {
    if (Verbose) {
      while (c<len) {
	for (i=0; i<IndentLevel; i++) printf("   ");
	printf("%04x:  ", (int)(pos+c-Input));
	for (i=0; c<len && i<8; i++,c++) printf(" 0x%02x", (int)pos[c]);
	printf("%s", EOL_String);
      }
    } else {
      printf("%04x:  ", (int)(pos+i-Input));
    }
  }
  for (i=0; i<IndentLevel; i++) printf("   ");
}

void Error(byte *pos) {
  printf("%04x:  Error: ", (int)(pos-Input));
  NoErrors++;
}

void PrintQuotedString(const char *str, int len) {
  int i;
  putchar('\"');
  for (i=0; i<len; i++) {
    char c = str[i];
    if (isprint(c)) {
      if (c=='\\') printf ("\\\\");
      else putchar(c);
    }
    else {
      if (c=='\n') printf ("\\n");
      else if (c=='\r') printf ("\\r");
      else if (c=='\t') printf ("\\t");
      else printf("\\%o", (int)c);
    }
  }
  putchar('\"');
}

void PrintEOL() {
  if (!PrettyPrinting) printf(";");
  printf("%s", EOL_String);
}

void IncreaseIndentLevel() {
  IndentLevel++;
}

void DecreaseIndentLevel() {
  IndentLevel--;
}



long unsigned Input2UnsignedLong(byte *input, int len) {
  long unsigned val = 0;

  while (len>0) {
    val = val*256 + input[0];
    input++;
    len--;
  }
  return val;
}

long Input2SignedLong(byte *input, int len) {
  long val = 0;
  int positive = 1;

  // specific procedure for first char to deterine sign
  if (len>0) {
    val = input[0];
    if (val>=128) {
      val -= 128;
      positive = 0;
    }
    input++;
    len--;
  }
     
  while (len>0) {
    val = val*256 + input[0];
    input++;
    len--;
  }
  return positive? val : -val;
}

int TestLength(int is_len, int must_len, const char *name, byte *input_pos) {
  int result = 1;
  if (is_len!=must_len) {
    Error(input_pos);
    printf("Illegal length %d of %s, should be %d", is_len, name, must_len);
    printf("%s", EOL_String);
    result = 0;
  }
  return result;
}

/* }}} */

/* {{{ TopLevel() */

void TopLevel(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (input[pos]==255) { /* End-of-Data Marker */
      if (PrettyPrinting) {
	Report(input+pos, 1);
	printf("End-of-Data Marker");
	printf("%s", EOL_String);
      }
      Saw_EndOfData = 1;
      pos++;
      break;
    } else if (input[pos]==0) { /* Pad Configuration Setting */
      if (PrettyPrinting) {
	Report(input+pos, 1);
	printf("Pad Configuration Setting");
	printf("%s", EOL_String);
      }
      pos++;
      break;
    }

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     case 1: /* Downstream Frequency Configuration Setting */
      {
	const int must_len = 4;
	const char *name = "down_freq";
	const char *pretty_name = "Downstream Frequency";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  PrintEOL();
	  if (val % 62500 != 0) {
	    Error(input+pos);
	    printf("%s is not a multiple of 62500 Hz", name);
	  }
	}
      }
      break;
     case 2: /* Upstream Channel ID Configuration Setting */
      {
	const int must_len = 1;
	const char *name = "up_chan_id";
	const char *pretty_name = "Upstream Channel ID";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  PrintEOL();
	}
      }
      break;
     case 3: /* Network Access Control Object */
      {
	const int must_len = 1;
	const char *name = "network_access";
	const char *pretty_name = "Network Access";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  if (val==0 || val==1) {
	    Report(input+pos, 2+len);
	    printf("%s = ", (PrettyPrinting? pretty_name : name));
	    printf("%s", (val? "on":"off"));
	    PrintEOL();
	  } else {
	    Error(input+pos);
	    printf("Illegal value %lu of %s", val, name);
	    printf("%s", EOL_String);
	  }
	  Saw_NetworkAccessControl = 1;
	}
      }
      break;
     case 4: /* Class of Service Configuration Setting */
      {
	const char *name = "cos";
	const char *pretty_name = "Class of Service";
	Report(input+pos, 2+len);
	printf("%s {", (PrettyPrinting? pretty_name : name));
	printf("%s", EOL_String);
	IncreaseIndentLevel();
	ClassOfService(input+pos+2, len);
	DecreaseIndentLevel();
	Report(input+pos+2+len, 0);
	printf("}");
	printf("%s", EOL_String);
	Saw_COS = 1;
      }
      break;
     case 5: /* Modem Capabilities Configuration Setting */
      {
	const char *name = "modem_cap";
	const char *pretty_name = "Modem Capabilities";
	Report(input+pos, 2+len);
	printf("%s {", (PrettyPrinting? pretty_name : name));
	printf("%s", EOL_String);
	IncreaseIndentLevel();
	ModemCapabilities(input+pos+2, len);
	DecreaseIndentLevel();
	Report(input+pos+2+len, 0);
	printf("}");
	printf("%s", EOL_String);
      }
      break;
     case 6: /* CM Message Integrity Check (MIC) Configuration Setting */
      {
	const int must_len = 16;
	const char *pretty_name = "CM Message Integrity Check";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  struct MD5Context cm_mic_md5context;
	  byte verify_cm_mic[16];
	  byte *original_cm_mic = input+pos+2;
	  int i;
	  int equal = 1;

	  /* calculate verification CM MIC */
	  MD5Init(&cm_mic_md5context);
	  MD5Update(&cm_mic_md5context, Input, input+pos-Input);
	  MD5Final(verify_cm_mic, &cm_mic_md5context);
	       
	  /* compare to original CM MIC */
	  for (i=0; i<15; i++) {
	    if (original_cm_mic[i]!=verify_cm_mic[i]) {
	      equal = 0;
	      break;
	    }
	  }
	  if (!equal) {
	    Error(input+pos);
	    printf("%s = failed, file is corrupted!", pretty_name);
	    printf("%s", EOL_String);
	  } else {
	    if (PrettyPrinting) {
	      Report(input+pos, 2+len);
	      printf("%s = ok", pretty_name);
	      printf("%s", EOL_String);
	    }
	  }
	  Saw_CM_MIC = 1;
	}
      }
      break;
     case 7: /* CMTS Message Integrity Check (MIC) Configuration Setting */
      {
	const int must_len = 16;
	const char *pretty_name = "CMTS Message Integrity Check";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  if (PrettyPrinting) {
	    Report(input+pos, 2+len);
	    printf("%s =", pretty_name);
	    for (i=0; i<must_len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	    PrintEOL();
	  }
	  Saw_CMTS_MIC = 1;
	}
      }
      break;
     case 8: /* Vendor ID Configuration Setting */
      {
	const int must_len = 3;
	const char *name = "vendor_id";
	const char *pretty_name = "Vendor ID";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  Report(input+pos, 2+len);
	  printf("%s =", (PrettyPrinting? pretty_name : name));
	  for (i=0; i<must_len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	  PrintEOL();
	}
      }
      break;
     case 9: /* Software Upgrade Filename */
      {
	const char *name = "upgrade_filename";
	const char *pretty_name = "Software Upgrade Filename";
	Report(input+pos, 2+len);
	printf("%s = ", (PrettyPrinting? pretty_name : name));
	PrintQuotedString(input+pos+2, (int)len);
	PrintEOL();
      }
      break;
     case 10: /* SNMP Write-Access Control */
      {
	const char *name = "snmp_write_access";
	const char *pretty_name = "SNMP Write-Access Control";
	int i;
	byte flag = input[pos+2+len-1];
	Report(input+pos, 2+len);
	printf("%s = ", (PrettyPrinting? pretty_name : name));
	printf("%s write-access", (flag?"disallow":"allow"));
	for (i=0; i<len-1; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
	if (flag!=0 && flag!=1) {
	  Error(input+pos+2+len-1);
	  printf("Illegal value %d of flag", (int)flag);
	  printf("%s", EOL_String);
	}
      }
      break;
     case 11: /* SNMP MIB Object */
      {
	const char *name = "snmp_mib_object";
	const char *pretty_name = "SNMP MIB Object";
	if (PrettyPrinting) {
	  Report(input+pos, 2+len);
	  printf("%s {", pretty_name);
	  printf("%s", EOL_String);
	  IncreaseIndentLevel();
	} else {
	  printf("%s ", name);
	  ExpectingSNMPObjId = 1;
	}
	SNMP_MIB_Object(input+pos+2, len);
	if (PrettyPrinting) {
	  DecreaseIndentLevel();
	  Report(input+pos+2+len, 0);
	  printf("}");
	  printf("%s", EOL_String);
	}
      }
      break;
     case 14: /* CPE Ethernet MAC Address */
      {
	const int must_len = 6;
	const char *name = "cpe_mac_addr";
	const char *pretty_name = "CPE Ethernet MAC Address";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  for (i=0; i<must_len; i++) {
	    if (i) printf(":");
	    printf("%02x", (int)input[pos+2+i]);
	  }
	  PrintEOL();
	}
      }
      break;
     case 15: /* Telephone Settings Option */
      {
	const char *name = "telephone";
	const char *pretty_name = "Telephone";
	Report(input+pos, 2+len);
	printf("%s {", (PrettyPrinting? pretty_name : name));
	printf("%s", EOL_String);
	IncreaseIndentLevel();
	Telephone(input+pos+2, len);
	DecreaseIndentLevel();
	Report(input+pos+2+len, 0);
	printf("}");
	printf("%s", EOL_String);
      }
      break;
     case 17: /* Baseline Privacy Configuration Settings Option */
      {
	const char *name = "bpi";
	const char *pretty_name = "Baseline Privacy Configuration";
	Report(input+pos, 2+len);
	printf("%s {", (PrettyPrinting? pretty_name : name));
	printf("%s", EOL_String);
	IncreaseIndentLevel();
	BaselinePrivacy(input+pos+2, len);
	DecreaseIndentLevel();
	Report(input+pos+2+len, 0);
	printf("}");
	printf("%s", EOL_String);
      }
      break;
     case 18: /* Maximum Number of CPEs */
      {
	const int must_len = 1;
	const char *name = "max_num_cpe";
	const char *pretty_name = "Maximum Number of CPEs";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  PrintEOL();
	}
      }
      break;
     case 19: /* TFTP Server Timestamp */
      {
	const int must_len = 4;
	const char *pretty_name = "TFTP Server Timestamp";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  if (PrettyPrinting) {
	    Report(input+pos, 2+len);
	    printf("%s = ", pretty_name);
	    printf("%lu seconds since 00:00 1 Jan. 1900", val);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 20: /* TFTP Server Provisioned Modem Address */
      {
	const int must_len = 4;
	const char *pretty_name = "TFTP Server Provisioned Modem Address";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  if (PrettyPrinting) {
	    Report(input+pos, 2+len);
	    printf("%s = ",pretty_name);
	    for (i=0; i<must_len; i++) {
	      if (i) printf(".");
	      printf("%d", (int)input[pos+2+i]);
	    }
	    PrintEOL();
	  }
	}
      }
      break;
     case 21: /* Software Upgrade TFTP Server */
      {
	const int must_len = 4;
	const char *name = "upgrade_server";
	const char *pretty_name = "Software Upgrade TFTP Server";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  for (i=0; i<must_len; i++) {
	    if (i) printf(".");
	    printf("%d", (int)input[pos+2+i]);
	  }
	  PrintEOL();
	}
      }
      break;
     case 43: /* Vendor-Specific Information */
      {
	const char *name = "vendor_specific_info";
	const char *pretty_name = "Vendor-Specific Information";
	Report(input+pos, 2+len);
	printf("%s {", (PrettyPrinting? pretty_name : name));
	printf("%s", EOL_String);
	IncreaseIndentLevel();
	VendorSpecificInformation(input+pos+2, len);
	DecreaseIndentLevel();
	Report(input+pos+2+len, 0);
	printf("}");
	printf("%s", EOL_String);
      }
      break;
     default: /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Unsupported configuration, type = %d, len = %d, value =",
	       (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }
    pos += 2+len;
  }
}

/* }}} */
/* {{{ ClassOfService() */

void ClassOfService(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     case 1: /* Class ID */
      {
	const int must_len = 1;
	const char *name = "class_id";
	const char *pretty_name = "Class ID";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  PrintEOL();
	  if (val<1 || val>16) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..16]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 2: /* Maximum Downstream Rate Configuration Setting */
      {
	const int must_len = 4;
	const char *name = "max_down_rate";
	const char *pretty_name = "Maximum Downstream Rate";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" bit/s");
	  PrintEOL();
	}
      }
      break;
     case 3: /* Maximum Upstream Rate Configuration Setting */
      {
	const int must_len = 4;
	const char *name = "max_up_rate";
	const char *pretty_name = "Maximum Upstream Rate";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" bit/s");
	  PrintEOL();
	}
      }
      break;
     case 4: /* Upstream Channel Priority Configuration Setting */
      {
	const int must_len = 1;
	const char *name = "up_chan_prio";
	const char *pretty_name = "Upstream Channel Priority";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  PrintEOL();
	  if (val>7) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [0..7]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 5: /* Guaranteed Minimum Upstream Channel Data Rate Configuration Setting */
      {
	const int must_len = 4;
	const char *name = "min_up_rate";
	const char *pretty_name = "Guaranteed Minimum Upstream Channel Data Rate";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" bit/s");
	  PrintEOL();
	}
      }
      break;
     case 6: /* Maximum Upstream Channel Transmit Burst Configuration Setting */
      {
	const int must_len = 2;
	const char *name = "max_burst_size";
	const char *pretty_name = "Maximum Upstream Channel Transmit Burst";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" byte");
	  PrintEOL();
	}
      }
      break;
     case 7: /* Class-of-Service Privacy Enable */
      {
	const int must_len = 1;
	const char *name = "cos_bpi_enable";
	const char *pretty_name = "Class-of-Service Privacy";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  if (val==0 || val==1) {
	    Report(input+pos, 2+len);
	    printf("%s = ", (PrettyPrinting? pretty_name : name));
	    printf("%s", (val?"enabled":"disabled"));
	    PrintEOL();
	  } else {
	    Error(input+pos+2);
	    printf("Illegal value %lu of %s", val, name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;

     default: /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Unsupported configuration, type = %d, len = %d, value =", (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }
    pos += 2+len;
  }
}

/* }}} */
/* {{{ ModemCapabilities() */

void ModemCapabilities(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
      
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     case 1: /* Concatenation Support */
      {
	const int must_len = 1;
	const char *name = "concatenation";
	const char *pretty_name = "Concatenation Support";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  if (val==0 || val==1) {
	    Report(input+pos, 2+len);
	    printf("%s = ", (PrettyPrinting? pretty_name : name));
	    printf("%s", (val? "on":"off"));
	    PrintEOL();
	  } else {
	    Error(input+pos+2);
	    printf("Illegal value %lu of %s", val, name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;

     default: /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Unsupported configuration, type = %d, len = %d, value =", (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }
    pos += 2+len;
  }
}

/* }}} */
/* {{{ VendorSpecificInformation() */

void VendorSpecificInformation(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     case 8: /* Vendor ID Configuration Setting */
      {
	const int must_len = 3;
	const char *name = "vendor_id";
	const char *pretty_name = "Vendor ID";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  int i;
	  Report(input+pos, 2+len);
	  printf("%s =", name);
	  for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	  PrintEOL();
	}
      }

     default:	 /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Vendor specific configuration, type = %d, len = %d, value =",
	       (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }
    pos += 2+len;
  }
}

/* }}} */
/* {{{ Telephone() */

void Telephone(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     default:	 /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Telephone Settings Option, type = %d, len = %d, value =",
	       (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }
    pos += 2+len;
  }
}

/* }}} */
/* {{{ BaselinePrivacy() */

void BaselinePrivacy(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    byte len;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos+1);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos];
    len = input[pos+1];

    if (pos+2+len>size) {/* value exceeds size */
      Error(input+pos+1);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += 2+size;
      break;
    }

    switch (type) {
     case 1: /* Authorize Wait Timeout */
      {
	const int must_len = 4;
	const char *name = "authorize_wait_timeout";
	const char *pretty_name = "Authorize Wait Timeout";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>30) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..30]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 2: /* Reauthorize Wait Timeout */
      {
	const int must_len = 4;
	const char *name = "reauthorize_wait_timeout";
	const char *pretty_name = "Reauthorize Wait Timeout";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>20) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..20]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 3: /* Authorization Grace Time */
      {
	const int must_len = 4;
	const char *name = "authorization_grace_time";
	const char *pretty_name = "Authorization Grace Time";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>1800) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..1800]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 4: /* Operational Wait Timeout */
      {
	const int must_len = 4;
	const char *name = "operational_wait_timeout";
	const char *pretty_name = "Operational Wait Timeout";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>10) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..10]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 5: /* Rekey Wait Timeout */
      {
	const int must_len = 4;
	const char *name = "rekey_wait_timeout";
	const char *pretty_name = "Rekey Wait Timeout";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>10) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..10]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 6: /* TEK Grace Time */
      {
	const int must_len = 4;
	const char *name = "tek_grace_time";
	const char *pretty_name = "TEK Grace Time";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	  if (val<1 || val>1800) {
	    Error(input+pos+2);
	    printf("%s out of range, valid range is [1..1800]", name);
	    printf("%s", EOL_String);
	  }
	}
      }
      break;
     case 7: /* Authorize Reject Wait Timeout */
      {
	const int must_len = 4;
	const char *name = "authorize_reject_wait_timeout";
	const char *pretty_name = "Authorize Reject Wait Timeout";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  long unsigned val = Input2UnsignedLong(input+pos+2, must_len);
	  Report(input+pos, 2+len);
	  printf("%s = ", (PrettyPrinting? pretty_name : name));
	  printf("%lu", val);
	  if (PrettyPrinting) printf(" sec");
	  PrintEOL();
	}
      }
      break;
     default:	 /* unsupported type */
      {
	int i;
	Report(input+pos, 2+len);
	printf("Baseline Privacy Option, type = %d, len = %d, value =",
	       (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+2+i]);
	PrintEOL();
      }
      break;
    }

    pos += 2+len;
  }
}

/* }}} */
/* {{{ SNMP_MIB_Object() */

void SNMP_MIB_Object(byte *input, int size) {
  int pos = 0;
 
  while (pos<size) { 
    byte type;
    int len;
    int start_pos = pos;

    if (pos+2>size) {/* too short for type/length tupple */
      Error(input+pos+1);
      printf("Uncovered byte at end of input");
      printf("%s", EOL_String);
      pos++;
      break;
    }
	 
    type = input[pos++];
    len = input[pos++];
    if (len>=128) {
      /* long encoding of length */
      unsigned long tmp_len;
      len -= 128;
      tmp_len = Input2UnsignedLong(input+pos, len);
      pos += len;
      len = (int)tmp_len;
    }
      
    if (pos+len>size) {/* value exceeds size */
      Error(input+pos);
      printf("Value length exceeds size, skipping %d bytes", size);
      printf("%s", EOL_String);
      pos += start_pos+size;
      break;
    }

    switch (type) {
     case 0x02: /* INTEGER */
      {
	const char *name = "int";
	const char *pretty_name = "INTEGER";
	long val = Input2SignedLong(input+pos, len);
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	printf("%ld", val);
	PrintEOL();
      }
      break;
     case 0x04: /* OCTET STRING */
      {
	const char *name = "string";
	const char *pretty_name = "OCTET STRING";
	int i;
	int printable;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	/* there are two display possibilities:
	 * 1. string
	 * 2. byte sequence
	 * I choose string if all characters are printable */
	printable = 1;
	for (i=0; i<len; i++) {
	  if (!isprint(input[pos+i])) {
	    printable = 0;
	    break;
	  }
	}
	if (printable) {
	  PrintQuotedString(input+pos, (int)len);
	} else {
	  for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	}
	PrintEOL();
      }
      break;
     case 0x05: /* NULL */
      {
	const int must_len = 0;
	const char *name = "null";
	const char *pretty_name = "NULL";
	if (TestLength(len, must_len, pretty_name, input+pos+1)) {
	  if (PrettyPrinting) {
	    Report(input+start_pos, len+pos-start_pos);
	    printf("%s = ", pretty_name);
	  } else {
	    printf("%s", name);
	  }
	  PrintEOL();
	}
      }
      break;
     case 0x06: /* OBJECT IDENTIFIER */
      {
	const char *name = "object_id";
	const char *pretty_name = "OBJECT IDENTIFIER";
	unsigned long sub_id;
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  if (!ExpectingSNMPObjId) printf("%s ", name);
	}
	sub_id = 0;
	for (i=0; i<len; i++) {
	  byte c = input[pos+i];
	  if (i==0) {
	    // specific rule for first byte
	    printf("%d.%d", (int)c/40, (int)c%40);
	  } else {
	    int last = (c<128); // save continue flag
	    c &= 0x7f; // reset continue flag
	    sub_id = (sub_id<<7) + c;
	    if (last) {
	      // is the last byte of a sub identifier, so print it
	      printf(".%lu", sub_id);
	      sub_id = 0; // reset it for the next sub identifier
	    }
	  }
	}
	if (PrettyPrinting) {
	  PrintEOL();
	} else {
	  if (ExpectingSNMPObjId) {
	    printf(" = ");
	    ExpectingSNMPObjId = 0;
	  } else {
	    PrintEOL();
	  }
	}
      }
      break;

     case 0x30: /* SEQUENCE */
      {
	Report(input+start_pos, len+pos-start_pos);
	if (PrettyPrinting) {
	  printf("SEQUENCE {");
	  printf("%s", EOL_String);
	}
	IncreaseIndentLevel();
	SNMP_MIB_Object(input+pos, len);
	DecreaseIndentLevel();
	Report(input+pos+len, 0);
	if (PrettyPrinting) {
	  printf("}");
	  printf("%s", EOL_String);
	}
      }
      break;
     case 0x40: /* IpAddress */
      {
	const int must_len = 4;
	const char *name = "ipaddress";
	const char *pretty_name = "IpAddress";
	if (TestLength(len, must_len, pretty_name, input+start_pos+1)) {
	  int i;
	  if (PrettyPrinting) {
	    Report(input+start_pos, len+pos-start_pos);
	    printf("%s = ", pretty_name);
	  } else {
	    printf("%s ", name);
	  }
	  for (i=0; i<must_len; i++) {
	    if (i) printf(".");
	    printf("%d", (int)input[pos+i]);
	  }
	  PrintEOL();
	}
      }
      break;
     case 0x41: /* Couter32 */
      {
	const char *name = "couter32";
	const char *pretty_name = "Couter32";
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
     case 0x42: /* Gauge32 */
      {
	const char *name = "gauge32";
	const char *pretty_name = "Gauge32";
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
     case 0x43: /* TimeTicks */
      {
	const char *name = "timeticks";
	const char *pretty_name = "TimeTicks";
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
     case 0x44: /* Opaque */
      {
	const char *name = "opaque";
	const char *pretty_name = "Opaque";
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
     case 0x46: /* Counter64 */
      {
	const char *name = "counter64";
	const char *pretty_name = "Counter64";
	int i;
	if (PrettyPrinting) {
	  Report(input+start_pos, len+pos-start_pos);
	  printf("%s = ", pretty_name);
	} else {
	  printf("%s ", name);
	}
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
     default:	 /* unsupported type */
      {
	int i;
	Report(input+start_pos, len+pos-start_pos);
	printf("unknown ASN.1 encoding, type = %d, len = %d, value =",
	       (int)type, (int)len);
	for (i=0; i<len; i++) printf(" 0x%02x", (int)input[pos+i]);
	PrintEOL();
      }
      break;
    }

    pos += len;
  }
}

/* }}} */

/* {{{ main() */

void HelpMessage(const char *program) {
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "This is the GNU DOCSIS configuration file disassembler version 1.2");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "Usage:  %s [options] config-file", program);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "Input format: DOCSIS-1.0 configuration files");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "Options:");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "  -p      print more readable (pretty) output");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "  -a      output syntax of GNU DOCSIS configuration file assembler (default)");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "  -d      on UNIX platform print end-of-line in DOS mode");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "  -h      Print this message.");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "  -v      Verbose output, also implies -p (enables pretty output).");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "Report bugs to Dietmar.Petras@ELSA.de");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s version 1.2, Copyright (C) 2000 by Dietmar Petras", program);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s comes with ABSOLUTELY NO WARRANTY; This is free software,", program);
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "and you are welcome to redistribute it under certain conditions; read the");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "COPYING file from the distribution.");
  fprintf(stderr, "%s", EOL_String);
  fprintf(stderr, "%s", EOL_String);
}


int main(int argc, char **argv) {
  const char *in_file_name;
  FILE *in_file;
  int len;
  int c;
  
  opterr = 0; /* getopt does not report errors */
  
  while ((c=getopt(argc, argv, "phdv"))!=-1) {
    switch (c) {
     case 'p':
      PrettyPrinting = 1;
      break;
     case 'h':
      HelpMessage(argv[0]);
      exit(0);
      break;
     case 'd':
      EOL_String = "\r\n";
      break;
     case 'v':
      Verbose = PrettyPrinting = 1;
      break;
     case '?': /* unknown option character */
      fprintf(stderr, "%s: unknown option `-%c'", argv[0], optopt);
      fprintf(stderr, "%s", EOL_String);
      fprintf(stderr, "Use `%s -h' for a complete list of options.", argv[0]);
      fprintf(stderr, "%s", EOL_String);
      exit(-1);
      break;
     default:
      abort();
      break;
    }
  }

  if (argc-optind!=1) {
    fprintf(stderr, "%s: no config-file given", argv[0]);
    fprintf(stderr, "%s", EOL_String);
    exit(-1);
  }
  in_file_name = argv[optind++];
  if ((in_file = fopen(in_file_name, "rb")) == NULL) {
    printf ("Error opening %s, %s", in_file_name, strerror(errno));
    fprintf(stderr, "%s", EOL_String);
    exit(-1);
  }

  InputSize = 4096;
  Input = (byte *)malloc(4096);

  len = 0;

  while ((c=fgetc(in_file)) != EOF) {
    Input[len++] = (byte)c;
    if (len == InputSize) {
      int new_size = InputSize * 2;
      byte *new_input = (byte *)malloc(new_size);
      memcpy(new_input, Input, InputSize);
      free(Input);
      Input = new_input;
      InputSize = new_size;
    }
  }
  fclose(in_file); in_file=NULL;
      
  TopLevel(Input, len);

  free(Input);
  
  /* check for occurance of mandatory fields */
  if (!Saw_EndOfData) {
    fprintf(stderr, "Error: no End-of-Data marker\n");
    NoErrors++;
  }
  if (!Saw_NetworkAccessControl) {
    fprintf(stderr, "Error: no Network Access Control object\n");
    NoErrors++;
  }
  if (!Saw_COS) {
    fprintf(stderr, "Error: no Class of Service configuration setting\n");
    NoErrors++;
  }
  if (!Saw_CM_MIC) {
    fprintf(stderr, "Error: no CM Message Integrity Check (MIC) Configuration Setting\n");
    NoErrors++;
  }
  if (!Saw_CMTS_MIC) {
    fprintf(stderr, "Error: no CMTS Message Integrity Check (MIC) Configuration Setting\n");
    NoErrors++;
  }
  if (NoErrors==1) {
    fprintf(stderr, "\n1 error found!!!\n");
  } else if (NoErrors>1) {
    fprintf(stderr, "\n%d errors found!!!\n", NoErrors);
  }

  return NoErrors;
}

/* }}} */

/* {{{ Local variables */

/*
  Local variables:
  folded-file: t
  c-basic-offset: 2
  End:
*/

/* }}} */
