/**************************************************************************** 
** File: lcp.c
**
** Author: Mike Borella
**
** Comments: LCP module.
**
** $Id: lcp.c,v 1.11 2007/06/25 11:47:56 farooq-i-azam Exp $
**
** 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 Library 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.
**
*****************************************************************************/

#include "lcp.h"

#define LCP_OPTION_LEN 64

/*
 * LCP codes
 */

#define LCP_CODE_CONFREQ        1
#define LCP_CODE_CONFACK        2
#define LCP_CODE_CONFNAK        3
#define LCP_CODE_CONFREJ        4
#define LCP_CODE_TERMREQ        5
#define LCP_CODE_TERMACK        6
#define LCP_CODE_CODEREJ        7
#define LCP_CODE_PROTREJ        8
#define LCP_CODE_ECHOREQ        9
#define LCP_CODE_ECHOREP        10
#define LCP_CODE_DISCREQ        11
#define LCP_CODE_IDENTIFICATION 12
#define LCP_CODE_TIMEREMAINING  13

/*
 * LCP code map
 */

strmap_t lcp_code_map[] =
{
  { LCP_CODE_CONFREQ,        "Configure-Request" },
  { LCP_CODE_CONFACK,        "Configure-Ack" },
  { LCP_CODE_CONFNAK,        "Configure-Nak" },
  { LCP_CODE_CONFREJ,        "Configure-Reject" },
  { LCP_CODE_TERMREQ,        "Terminate-Request" }, 
  { LCP_CODE_TERMACK,        "Terminate-Ack" }, 
  { LCP_CODE_CODEREJ,        "Code-Rej" },
  { LCP_CODE_PROTREJ,        "Protocol-Reject" },
  { LCP_CODE_ECHOREQ,        "Echo-Request" },
  { LCP_CODE_ECHOREP,        "Echo-Reply" },
  { LCP_CODE_DISCREQ,        "Discard-Request" },
  { LCP_CODE_IDENTIFICATION, "Identification" },
  { LCP_CODE_TIMEREMAINING,  "Time-Remaining" },
  { 0, ""}
};

/*
 * LCP options
 */

#define LCP_OPTION_VENDORSPECIFIC       0
#define LCP_OPTION_MAXRECVUNIT          1
#define LCP_OPTION_ASYNCCTRLCHARMAP     2
#define LCP_OPTION_AUTHPROTOCOL         3
#define LCP_OPTION_QUALITYPROTOCOL      4
#define LCP_OPTION_MAGICNUMBER          5
#define LCP_OPTION_PROTOFIELDCOMP       7
#define LCP_OPTION_ADDRCTRLFIELDCOMP    8
#define LCP_OPTION_FCSALTERNATIVES      9
#define LCP_OPTION_SELFDESCRIBINGPAD    10
#define LCP_OPTION_NUMBEREDMODE         11
#define LCP_OPTION_CALLBACK             13
#define LCP_OPTION_MULTILINKMRRU        17
#define LCP_OPTION_MULTILINKSSNHEADER   18
#define LCP_OPTION_MULTILINKENDPNTDISC  19
#define LCP_OPTION_PROPRIETARY          20
#define LCP_OPTION_DCEIDENTIFIER        21
#define LCP_OPTION_MULTILINKPLUSPROC    22
#define LCP_OPTION_LINKDISCFORBACP      23
#define LCP_OPTION_LCPAUTH              24
#define LCP_OPTION_COBS                 25
#define LCP_OPTION_PREFIXELISION        26
#define LCP_OPTION_MULTILINKHDRFORMAT   27
#define LCP_OPTION_INTERNATIONALIZATION 28
#define LCP_OPTION_SIMPLEDATALINKSONET  29
#define LCP_OPTION_PPPMUXING            30

/*
 * LCP option map
 */

strmap_t lcp_option_map[] = 
{
  { LCP_OPTION_VENDORSPECIFIC,        "vendor specific" },
  { LCP_OPTION_MAXRECVUNIT,           "max receive unit" },
  { LCP_OPTION_ASYNCCTRLCHARMAP,      "async char control map" },
  { LCP_OPTION_AUTHPROTOCOL,          "authentication protocol" },
  { LCP_OPTION_QUALITYPROTOCOL,       "quality protocol" },
  { LCP_OPTION_MAGICNUMBER,           "magic number" },
  { LCP_OPTION_PROTOFIELDCOMP,        "protocol field compression" },
  { LCP_OPTION_ADDRCTRLFIELDCOMP,     "address control field compression" },
  { LCP_OPTION_FCSALTERNATIVES,       "FCS alternatives" },
  { LCP_OPTION_SELFDESCRIBINGPAD,     "self describing pad" },
  { LCP_OPTION_NUMBEREDMODE,          "numbered mode" },
  { LCP_OPTION_CALLBACK,              "callback" },
  { LCP_OPTION_MULTILINKMRRU,         "multilink MRRU" },
  { LCP_OPTION_MULTILINKSSNHEADER,    "multilink short seq number header" },
  { LCP_OPTION_MULTILINKENDPNTDISC,   "multilink endpoint discriminator" },
  { LCP_OPTION_PROPRIETARY,           "proprietary" },
  { LCP_OPTION_DCEIDENTIFIER,         "DCE identifier" },
  { LCP_OPTION_MULTILINKPLUSPROC,     "multilink-plus procedure" },
  { LCP_OPTION_LINKDISCFORBACP,       "link discriminator for BACP" },
  { LCP_OPTION_LCPAUTH,               "LCP authentication" },
  { LCP_OPTION_COBS,                  "consistent overhead byte stuffing" },
  { LCP_OPTION_PREFIXELISION,         "prefix elision" },
  { LCP_OPTION_MULTILINKHDRFORMAT,    "multilink header format" },
  { LCP_OPTION_INTERNATIONALIZATION,  "internationalization" },
  { LCP_OPTION_SIMPLEDATALINKSONET,   "simple datalink on SONET/SDH" },
  { LCP_OPTION_PPPMUXING,             "PPP muxing" },
  { 0, "" } 
};

/*
 * LCP frame format
 */

typedef struct lcp
{
  u_int8_t  code;
  u_int8_t  identifier;
  u_int16_t length;
} lcp_t;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_lcp()
**
** Displays LCP packets.
**
**----------------------------------------------------------------------------
*/

void dump_lcp(packet_t *pkt)
{
  lcp_t lcp;

  /* Set the layer */
  set_layer(LAYER_DATALINK);

  /*
   * Get the header
   */

  if (get_packet_bytes((u_int8_t *) &lcp, pkt, sizeof(lcp_t)) == 0)
    return;

  /*
   * Conversion
   */

  lcp.length = ntohs(lcp.length);

  /*
   * Dump the header
   */

  if (my_args->m)
    {
      display_minimal_string("| LCP ");
      display_minimal_string(map2str(lcp_code_map, lcp.code));
      display_minimal_string(" ");
    }
  else
    {
      display_header_banner("LCP Header");
      display_strmap("Code", lcp.code, lcp_code_map);
      display("Identifier", &lcp.identifier, 1, DISP_DEC);
      display("Length", (u_int8_t *) &lcp.length, 2, DISP_DEC);
    }

  /*
   * Grab the payload data
   */

  if (lcp.length > sizeof(lcp_t))
    {
      switch(lcp.code)
	{
	case LCP_CODE_CONFREQ:
	case LCP_CODE_CONFACK:
	case LCP_CODE_CONFNAK:
	case LCP_CODE_CONFREJ:
	  {
	    u_int8_t bytes_read = 0;
	    u_int8_t type;
	    u_int8_t length;
	    u_int8_t value [LCP_OPTION_LEN];
	    int comma = 0;
	    
	    /*
	     * LCP options appear in Type-Length-Value format 
	     */
	    
	    while (bytes_read < lcp.length - sizeof(lcp_t))
	      {
		/*
		 * Get type
		 */
		
		if (get_packet_bytes((u_int8_t *) &type, pkt, 1)  == 0)
		  break;
		bytes_read ++;


		/*
		 * Get length
		 */
		
		if (get_packet_bytes((u_int8_t *) &length, pkt, 1)  == 0)
		  break;
		bytes_read ++;

		/* 
		 * In minimal mode we start all LCP options with an open paren
		 * and put a comma before all but the first
		 */

		if (my_args->m)
		  {
		    if (comma)
		      display_minimal_string(", ");
		    else
		      display_minimal_string("(");
		  }
		comma = 1;

		/*
		 * Display the type and length
		 */
		
		if (my_args->m)
		  {
		    display_minimal_string(map2str(lcp_option_map, type));
		  }
		else
		  {
		    display_strmap("Option", type, lcp_option_map);
		    display("  Length", &length, 1, DISP_DEC);
		  }
		
		/*
		 * Figure out if we need to get a value
		 */

		if (length > 2)
		  {
		    if (get_packet_bytes((u_int8_t *) value, pkt, length-2)  
			== 0)
		      break;
		    bytes_read = bytes_read + length - 2;
		    
		    /*
		     * Display the value
		     */

		    if (my_args->m)
		      {
			display_minimal_string(" ");
			display_minimal(value, length-2, DISP_HEX);
		      }
		    else
		      {
			display("  Value", (u_int8_t *) value, length-2,
				DISP_HEX);
		      }
		  } /* if */
		
	      } /* while */
	    
	    /* 
	     * Insert end paren in minimal mode
	     */

	    if (my_args->m)
	      display_minimal_string(")");

	  }
	  break;
	  
	case LCP_CODE_TERMREQ:
	case LCP_CODE_TERMACK:
	  {
	    u_int8_t * data;
	    u_int8_t data_len;

	    /*
	     * Read the data, if any
	     */

	    data_len = lcp.length - sizeof(lcp_t);
	    if (data_len > 0)
	      {
		/* allocate memory for the data */
		data = (u_int8_t *) my_malloc(data_len);

		/* grab the data field */
		if (get_packet_bytes(data, pkt, data_len) == 0)
		  { 
		    my_free(data);
		    return;
		  }
	
		/* dump it out as hex, but don't dump in minimal mode */
		if (!my_args->m)
		  {
		    display("Data", data, data_len, DISP_HEX);
		  }
		
		/* free that darn mem! */
		my_free(data);
	      }
	  }
	  break;

	case LCP_CODE_ECHOREQ:
	case LCP_CODE_ECHOREP:
	case LCP_CODE_DISCREQ:
	case LCP_CODE_IDENTIFICATION:
	case LCP_CODE_TIMEREMAINING:
	  {
	    u_int32_t  magic_number;
	    u_int8_t * data;
	    u_int8_t   data_len;
	    
	    /* get the magic number */
	    if (get_packet_bytes((u_int8_t *) &magic_number, pkt, 4) == 0)
	      return;

	    /* display it */
	    if (my_args->m)
	      {
		display_minimal((u_int8_t *) &magic_number, 4, DISP_HEX);
		display_minimal_string(" ");
	      }
	    else
	      {
		display("Magic number", (u_int8_t *) &magic_number, 4, 
			DISP_HEX);
	      }

	    /* Read the data, if any */
	    data_len = lcp.length - sizeof(lcp_t) - 4;
	    if (data_len > 0)
	      {
		/* Allocate memory for the data - extra for nulling a string */
		data = (u_int8_t *) my_malloc(data_len + 1);

		/* grab the data field */
		if (get_packet_bytes(data, pkt, data_len) == 0)
		  { 
		    my_free(data);
		    return;
		  }
		data[data_len] = '\0';

		/* dump it out as hex, but don't dump in minimal mode */
		if (!my_args->m)
		  {
		    if (isprint_str(data, data_len))
		      display_string("Data", data);
		    else
		      display("Data", data, data_len, DISP_HEX);
		  }
		
		/* free that darn mem! */
		my_free(data);
	      }
	  
	  }
	  break;

	case LCP_CODE_CODEREJ:
	  dump_lcp(pkt);
	  break;

	/**
	 * In this case, the payload contains two-byte rejected protocol
	 * field, followed by which is the rejected information.
	 **/

	case LCP_CODE_PROTREJ:
	  {
	    u_int16_t 	proto;
	    u_int8_t	* data;
	    u_int8_t	data_len;	    
	
	    /*
	     * Get the rejected protocol field
	     */
	    if (get_packet_bytes((u_int8_t *) &proto, pkt, 2) == 0)
	      return;    

	    if (my_args->m)
	      {
	        display_minimal((u_int8_t *) &proto, 2, DISP_HEX);
		display_minimal_string(" ");
	      }
    	    else	
	      {
	        display("Rejected Protocol", (u_int8_t *) &proto, 2, DISP_HEX);	
  	      }	
	    /*
	     * Read and display the protocol data if any
	     */	    
	    
	    data_len = lcp.length - sizeof(lcp_t) - 2;

	    if (data_len > 0)
	      {
		data = (u_int8_t *) my_malloc(data_len);

		if (get_packet_bytes(data, pkt, data_len) == 0)
		  {
		    my_free(data);
		    return;
		  }
		if (!my_args->m)
		  {
		    display("Rejected Data", data, data_len, DISP_HEX);
		  }
	
		my_free(data);
	      } /* if */
	  }
	  break;

	default:
	  break;
	}
    }

  /* dump the hex buffer */
  if (!my_args->l)
    hexbuffer_flush();
}
