/* packet-pw-atm.c
 * Routines for ATM PW dissection: it should be conform to RFC 4717.
 *
 * Copyright 2009 _FF_, _ATA_
 *
 * Francesco Fondelli <francesco dot fondelli, gmail dot com>
 * Artem Tamazov <artem [dot] tamazov [at] tellabs [dot] com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
   DONE:
        - ATM N-to-One Cell Mode (with CW)
        - ATM N-to-One Cell Mode (no CW)
        - ATM One-to-One Cell Mode
        - ATM AAL5 SDU Mode
        - ATM AAL5 PDU Mode
*/

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/prefs.h>

#include <wsutil/str_util.h>

#include <wiretap/wtap.h> /*for atm pseudo header*/
#include "packet-mpls.h"
#include "packet-atm.h"
#include "packet-pw-atm.h"
#include "packet-pw-common.h"

void proto_register_pw_atm_ata(void);
void proto_reg_handoff_pw_atm_ata(void);
void proto_register_pw_atm(void);
void proto_reg_handoff_pw_atm(void);

static gint proto_n1_nocw = -1;
static gint proto_n1_cw = -1;
static gint proto_11_or_aal5_pdu = -1;
static gint proto_aal5_sdu = -1;
/* subordinate dissectors: */
static gint proto_control_word = -1;
static gint proto_cell_header = -1;
static gint proto_cell = -1;

static gint ett_encaps = -1;
static gint ett_cw = -1;
static gint ett_cell_header = -1;
static gint ett_cell = -1;

static int hf_pw_type_n1_cw = -1;
static int hf_pw_type_n1_nocw = -1;
static int hf_pw_type_11_vcc = -1;
static int hf_pw_type_11_vpc = -1;
static int hf_pw_type_aal5_sdu = -1;
static int hf_pw_type_aal5_pdu = -1;

static int hf_cell_h_vpi = -1;
static int hf_cell_h_vci = -1;
static int hf_cell_h_pti = -1;
static int hf_cell_h_clp = -1;
static int hf_cell_h_m = -1;
static int hf_cell_h_v = -1;
static int hf_cell_h_rsv = -1;
static int hf_aal5_pdu_rsv = -1;
static int hf_aal5_pdu_u = -1;
static int hf_aal5_pdu_e = -1;

static int hf_cw_bits03 = -1;
static int hf_pref_cw_rsv = -1;
static int hf_generic_cw_rsv = -1;
static int hf_pref_cw_flags = -1;
static int hf_pref_cw_a5s_t = -1;
static int hf_pref_cw_a5s_e = -1;
static int hf_pref_cw_a5s_c = -1;
static int hf_pref_cw_a5s_u = -1;
static int hf_pref_cw_len = -1;
static int hf_pref_cw_rsvlen = -1;
static int hf_cw_seq = -1;
static int hf_n1_cw_ncells = -1;
static int hf_n1_nocw_ncells = -1;
static int hf_11_ncells = -1;
static int hf_gen_cw_atmbyte = -1;
static int hf_cell_payload_len = -1;

static expert_field ei_cell_h_v_not_one = EI_INIT;
static expert_field ei_cell_h_pti_undecoded = EI_INIT;
static expert_field ei_pref_cw_flags = EI_INIT;
static expert_field ei_cell_h_v_not_zero = EI_INIT;
static expert_field ei_pw_payload_size_invalid_note = EI_INIT;
static expert_field ei_pw_payload_size_invalid_error = EI_INIT;
static expert_field ei_cell_h_pti_malformed = EI_INIT;
static expert_field ei_cell_h_rsv = EI_INIT;
static expert_field ei_cell_broken = EI_INIT;
static expert_field ei_cell_h_m = EI_INIT;
static expert_field ei_cw_bits03 = EI_INIT;
static expert_field ei_pw_packet_size_too_small = EI_INIT;
static expert_field ei_pref_cw_len = EI_INIT;
static expert_field ei_gen_cw_atmbyte = EI_INIT;


static dissector_handle_t dh_cell;
static dissector_handle_t dh_cell_header;
static dissector_handle_t dh_control_word;
static dissector_handle_t dh_atm_truncated;
static dissector_handle_t dh_atm_untruncated;
static dissector_handle_t dh_atm_oam_cell;
static dissector_handle_t dh_padding;

typedef enum {
	PWATM_MODE_UNKNOWN = 0
	,PWATM_MODE_N1_NOCW
	,PWATM_MODE_N1_CW
	,PWATM_MODE_11_VCC
	,PWATM_MODE_11_VPC
	,PWATM_MODE_AAL5_SDU
	,PWATM_MODE_AAL5_PDU
} pwatm_mode_t;

typedef enum {
	PWATM_SUBMODE_DEFAULT = 0
	,PWATM_SUBMODE_ADMIN_CELL /*used in aal5_sdu dissector only*/
} pwatm_submode_t;

typedef struct {
	int pw_cell_number;
	int props;
	gint packet_size;
	pwatm_mode_t mode;
	pwatm_submode_t submode;
	struct {
		/*
		 * ATM-specific attributes which remain the same
		 * across all the cells in the pw packet. Values are filled
		 * by sub-dissectors and read by upper-level dissector.
		 * Meanings of values:
		 *   (-1) 	- value is unknown
		 *   (-2) 	- value is different among cells
		 *   positive	- value is the same in all cells
		 * Machinery is implemented in the UPDATE_CUMULATIVE_VALUE macro.
		 */
		gint32 vpi;
		gint32 vci;
		gint32 clp;
		gint32 pti;
	} cumulative;
	gint32 vpi; /*-1 if unknown*/
	gint32 vci; /*-1 if unknown*/
	gint32 pti; /*-1 if unknown*/
	struct {
		/*
		 * Some fields from 3rd byte of CW. Filled by cell_header dissector.
		 * In in AAL5 PDU mode, this allows control_word dissector to print
		 * these values in the CW heading line in the tree.
		 * Meanings of values:
		 *   (-1) 	- value is unknown
		 */
		gint32 m;
		gint32 v;
		gint32 rsv;
		gint32 u;
		gint32 e;
		gint32 clp;
	} cwb3;
	gboolean aal5_sdu_frame_relay_cr_bit; /*see rfc4717 10.1*/
	gboolean cell_mode_oam; /*atm admin cell*/
} pwatm_private_data_t;

#define PWATM_PRIVATE_DATA_T_INITIALIZER {		\
	0, PWC_PACKET_PROPERTIES_T_INITIALIZER, 0	\
	,PWATM_MODE_UNKNOWN, PWATM_SUBMODE_DEFAULT	\
	,{-1, -1, -1, -1 } 				\
	,-1, -1, -1 					\
	,{-1, -1, -1, -1, -1, -1 }			\
	,FALSE, FALSE,					\
	}

#define PTI_IS_ADMIN(pti) ((pti) == 4 || (pti) == 5 || (pti) == 6)  /*see atm_pt_vals[]*/

#define MODE_11(mode) 			(PWATM_MODE_11_VCC == (mode) || PWATM_MODE_11_VPC == (mode))
#define MODE_N1(mode) 			(PWATM_MODE_N1_NOCW == (mode)|| PWATM_MODE_N1_CW == (mode))
#define MODE_11_OR_AAL5_PDU(mode)   	(MODE_11(mode) || PWATM_MODE_AAL5_PDU == (mode))

#define VALUE_SELECTOR_VPC_VCC_PDU(mode,val_vpc,val_vcc,val_pdu)\
	((PWATM_MODE_11_VPC == (mode))				\
		? (val_vpc)					\
		: ((PWATM_MODE_11_VCC == (mode))		\
			? (val_vcc)				\
			: ((PWATM_MODE_AAL5_PDU == (mode))	\
				? (val_pdu)			\
				: 0				\
			  )					\
		  )						\
	)

#define UPDATE_CUMULATIVE_VALUE(cumulative_val,new_val)\
	do\
	{\
		if (-2 >= (cumulative_val))\
		{\
		}\
		else if (-1 == (cumulative_val))\
		{\
			(cumulative_val) = (new_val);\
		}\
		else if ((new_val) != (cumulative_val))\
		{\
			(cumulative_val) = -2;\
		}\
	}\
	while(0)

#define SIZEOF_ATM_CELL_PAYLOAD      48
#define SIZEOF_N1_PW_CELL_HEADER      4
#define SIZEOF_11_VCC_PW_CELL_HEADER  1
#define SIZEOF_11_VPC_PW_CELL_HEADER  3
#define SIZEOF_N1_PW_CELL	(SIZEOF_ATM_CELL_PAYLOAD+SIZEOF_N1_PW_CELL_HEADER)
#define SIZEOF_11_VCC_PW_CELL	(SIZEOF_ATM_CELL_PAYLOAD+SIZEOF_11_VCC_PW_CELL_HEADER)
#define SIZEOF_11_VPC_PW_CELL	(SIZEOF_ATM_CELL_PAYLOAD+SIZEOF_11_VPC_PW_CELL_HEADER)

const char pwc_longname_pw_atm_n1_cw[]          = "MPLS PW ATM N-to-One encapsulation, with CW";
const char pwc_longname_pw_atm_n1_nocw[]        = "MPLS PW ATM N-to-One encapsulation, no CW";
const char pwc_longname_pw_atm_11_or_aal5_pdu[] = "MPLS PW ATM One-to-One or AAL5 PDU encapsulation";
const char pwc_longname_pw_atm_aal5_sdu[]       = "MPLS PW ATM AAL5 CPCS-SDU mode encapsulation";

static const char longname_pw_atm_11_vcc[]      = "MPLS PW ATM One-to-One VCC Cell Transport";
static const char longname_pw_atm_11_vpc[]      = "MPLS PW ATM One-to-One VPC Cell Transport";
static const char longname_pw_atm_aal5_pdu[]    = "MPLS PW ATM AAL5 PDU encapsulation";

static const char shortname_n1_cw[]             = "MPLS PW ATM N:1 CW";
static const char shortname_n1_nocw[]           = "MPLS PW ATM N:1 no CW";
static const char shortname_11_or_aal5_pdu[]    = "MPLS PW ATM 1:1 / AAL5 PDU";
static const char shortname_11_vpc[]            = "MPLS PW ATM 1:1 VPC";
static const char shortname_11_vcc[]            = "MPLS PW ATM 1:1 VCC";
static const char shortname_aal5_sdu[]          = "MPLS PW ATM AAL5 SDU";
static const char shortname_aal5_pdu[]          = "MPLS PW ATM AAL5 PDU";

/*
 * These options are needed to support Nokia AXE and stuff alike.
 * Note that these options will affect PW type auto-guessing, if such heuristic
 * implemented in the future.
 */
static gboolean pref_n1_cw_allow_cw_length_nonzero       = FALSE;
static gboolean pref_n1_cw_extend_cw_length_with_rsvd    = FALSE;
static gboolean pref_aal5_sdu_allow_cw_length_nonzero    = FALSE;
static gboolean pref_aal5_sdu_extend_cw_length_with_rsvd = FALSE;


static int
pw_cell_size(const pwatm_mode_t mode, const pwatm_submode_t submode)
{
	switch (mode)
	{
	case PWATM_MODE_N1_NOCW:
	case PWATM_MODE_N1_CW:
		return SIZEOF_N1_PW_CELL;
	case PWATM_MODE_11_VCC:
		return SIZEOF_11_VCC_PW_CELL;
	case PWATM_MODE_11_VPC:
		return SIZEOF_11_VPC_PW_CELL;
	case PWATM_MODE_AAL5_PDU:
		/* AAL5 PDU size is n*48 bytes */
		return SIZEOF_ATM_CELL_PAYLOAD;
	case PWATM_MODE_AAL5_SDU:
		if (PWATM_SUBMODE_ADMIN_CELL == submode)
		{
			return SIZEOF_N1_PW_CELL; /*n:1 encapsulation is used for admin cells*/
		}
		else
		{
			DISSECTOR_ASSERT_NOT_REACHED();
			return 0;
		}
	default:
		DISSECTOR_ASSERT_NOT_REACHED();
		return 0;
	}
}

static int
pw_cell_header_size(const pwatm_mode_t mode, const pwatm_submode_t submode)
{
	switch (mode)
	{
	case PWATM_MODE_N1_NOCW:
	case PWATM_MODE_N1_CW:
		return SIZEOF_N1_PW_CELL_HEADER;
	case PWATM_MODE_11_VCC:
		return SIZEOF_11_VCC_PW_CELL_HEADER;
	case PWATM_MODE_11_VPC:
		return SIZEOF_11_VPC_PW_CELL_HEADER;
	case PWATM_MODE_AAL5_SDU:
		if (PWATM_SUBMODE_ADMIN_CELL == submode)
		{
			return SIZEOF_N1_PW_CELL_HEADER; /*n:1 encapsulation is used for admin cells*/
		}
		else
		{
			DISSECTOR_ASSERT_NOT_REACHED();
			return 0;
		}
	case PWATM_MODE_AAL5_PDU: /*not applicable*/
	default:
		DISSECTOR_ASSERT_NOT_REACHED();
		return 0;
	}
}

static int
number_of_cells(const pwatm_mode_t mode
		,const pwatm_submode_t submode
		,const gint payload_size
		,gint* const remainder_size)
{
	int cells;

	DISSECTOR_ASSERT(payload_size >= 0);

	switch (mode)
	{
	case PWATM_MODE_N1_NOCW:
	case PWATM_MODE_N1_CW:
	case PWATM_MODE_11_VCC:
	case PWATM_MODE_11_VPC:
	case PWATM_MODE_AAL5_PDU:
		cells = payload_size / pw_cell_size(mode, submode);
		*remainder_size = payload_size - (cells * pw_cell_size(mode, submode));
		return cells;
	case PWATM_MODE_AAL5_SDU:
		if (PWATM_SUBMODE_ADMIN_CELL == submode)
		{
			cells = payload_size / pw_cell_size(mode, submode);
			if (cells > 1) cells = 1; /*max. 1 admin cell may be present in aal5 sdu mode */
			*remainder_size = payload_size - (cells * pw_cell_size(mode, submode));
			return cells;
		}
		else
		{
			/*not applicable*/
		}
		/*fallthrough*/
	default:
		*remainder_size = payload_size;
		DISSECTOR_ASSERT_NOT_REACHED();
		return 0;
	}

}


static void
col_append_pw_info(packet_info * pinfo
	,const int payload_size
	,const int cells
	,const int padding_size
	,pwatm_private_data_t * pd)
{
	if (pd->props & PWC_ANYOF_CW_BAD)
	{
		col_append_str(pinfo->cinfo, COL_INFO, "CW:Bad");
	}

	if (pd->props & PWC_PAY_SIZE_BAD)
	{
		if (pd->props & PWC_ANYOF_CW_BAD)
		{
			col_append_str(pinfo->cinfo, COL_INFO, ", ");
		}
		col_append_str(pinfo->cinfo, COL_INFO, "Payload size:Bad, ");
		col_append_fstr(pinfo->cinfo, COL_INFO, "%d byte%s"
			,(int)payload_size
			,plurality(payload_size, "", "s"));
	}

	if (pd->props == 0) /*omit "atm cells" etc if something is wrong*/
	{
		/* number of cells may be not known */
		if (cells >=0)
			col_append_fstr(pinfo->cinfo, COL_INFO, "%d ATM cell%s"
				,cells
				,plurality(cells, "", "s"));
		/*
		 * Display ATM-specific attributes which are the same
		 * across all the cells in the pw packet.
		 * Meanings of values:
		 *   (-1) unknown - not displayed,
		 *   (-2) "not the same in all cells" - not displayed
		 *   positive values - ok, displayed
		 */
		if (pd->cumulative.vpi >= 0)
			col_append_fstr(pinfo->cinfo, COL_INFO, ", VPI:%.4d", pd->cumulative.vpi);
		if (pd->cumulative.vci >= 0)
			col_append_fstr(pinfo->cinfo, COL_INFO, ", VCI:%.5d", pd->cumulative.vci);
		if (pd->cumulative.pti >= 0)
			col_append_fstr(pinfo->cinfo, COL_INFO, ", PTI:%.1d", pd->cumulative.pti);
		if (pd->cumulative.clp >= 0)
			col_append_fstr(pinfo->cinfo, COL_INFO, ", CLP:%.1d", pd->cumulative.clp);
	}

	if (padding_size != 0)
	{
		col_append_fstr(pinfo->cinfo, COL_INFO, ", %d padding"
			,(int)padding_size);
	}
}


static void
prepare_pseudo_header_atm(
	struct atm_phdr *ph,
	const pwatm_private_data_t * const pdata,
	const guint aal)
{
	DISSECTOR_ASSERT(NULL != pdata);
	DISSECTOR_ASSERT(NULL != ph);

	memset(ph, 0 , sizeof(*ph));  /* it is OK to clear unknown values */
	ph->flags		= 0; /* status flags */
	ph->aal			= aal;
	ph->type		= TRAF_UNKNOWN;
	ph->subtype		= TRAF_ST_UNKNOWN;
	ph->vpi			= (pdata->vpi >= 0) ? pdata->vpi : 0 /*unknown*/;
	ph->vci			= (pdata->vci >= 0) ? pdata->vci : 0 /*unknown*/;
	ph->aal2_cid		= 0; /*not applicable*//* channel id */
	ph->channel		= 0; /*unknown*//* link: 0 for DTE->DCE, 1 for DCE->DTE */
	ph->cells		= 0; /*zero indicates that we do not have trailer info*/
	/*user-to-user indicator & CPI*/
	ph->aal5t_u2u		= 0; /* all bits unknown except lsb of UU */
	if (pdata->aal5_sdu_frame_relay_cr_bit)
	{ /* Let's give Frame Relay C/R bit to ATM dissector.*/
		ph->aal5t_u2u |= (1<<8); /*UU octet is at << 8 in aal5t_u2u*/
	}
	ph->aal5t_len		= 0; /*unknown*//* length of the packet from trailer*/
	ph->aal5t_chksum	= 0; /*unknown*//* checksum for AAL5 packet from trailer */
	return;
}


static void
dissect_payload_and_padding(
	tvbuff_t     * tvb
	,packet_info * pinfo
	,proto_tree  * tree
	,const gint    payload_size
	,const gint    padding_size
	,pwatm_private_data_t * pd)
{
	int                    dissected;
	tvbuff_t             * tvb_2;

	for(dissected = 0, pd->pw_cell_number = 0;
		payload_size > dissected;
		++(pd->pw_cell_number))
	{
		tvb_2 = tvb_new_subset_remaining(tvb, dissected);
		dissected += call_dissector_with_data(dh_cell_header, tvb_2, pinfo, tree, pd);

		tvb_2 = tvb_new_subset_remaining(tvb, dissected);

		/*dissect as oam for specific vci/pti, just like atm dissector does*/
		if ((pd->vci >= 0) && (pd->pti >=0))
		{
			if (atm_is_oam_cell(pd->vci, pd->pti))
			{
				pd->cell_mode_oam = TRUE;
			}
		}

		if (pd->cell_mode_oam)
		{
			struct pw_atm_phdr ph;
			tvbuff_t* tvb_3;
			int bytes_to_dissect;
			/* prepare buffer for old-style dissector */
			/* oam cell is always 48 bytes, but payload_size maybe too small */
			if ((payload_size - dissected) >= SIZEOF_ATM_CELL_PAYLOAD)
				bytes_to_dissect = SIZEOF_ATM_CELL_PAYLOAD;
			else
				bytes_to_dissect = (payload_size - dissected);
			tvb_3 = tvb_new_subset_length_caplen(tvb_2, 0, bytes_to_dissect, -1);
			/*aal5_sdu: disable filling columns after 1st (valid) oam cell*/
			if (pd->mode == PWATM_MODE_AAL5_SDU && (pd->pw_cell_number > 0))
				ph.enable_fill_columns_by_atm_dissector = FALSE;
			else
				ph.enable_fill_columns_by_atm_dissector = TRUE;
			/* prepare atm pseudo header for atm OAM cell decoding */
			prepare_pseudo_header_atm(&ph.info, pd, AAL_OAMCELL);

			call_dissector_with_data(dh_atm_oam_cell, tvb_3, pinfo, tree, &ph);
			dissected += bytes_to_dissect;
		}
		else
		{
			dissected += call_dissector(dh_cell, tvb_2, pinfo, tree);
		}
	}

	if (padding_size != 0)
	{
		tvb_2 = tvb_new_subset_remaining(tvb, -padding_size);
		call_dissector(dh_padding, tvb_2, pinfo, tree);
	}
	return;
}


static gboolean
too_small_packet_or_notpw(tvbuff_t * tvb
	,packet_info * pinfo
	,proto_tree  * tree
	,const int     proto_handler
	,const char  * const proto_name_column)
{
	gint packet_size;
	packet_size = tvb_reported_length_remaining(tvb, 0);
	/*
	 * FIXME
	 * "4" below should be replaced by something like "min_packet_size_this_dissector"
	 * Also call to dissect_try_cw_first_nibble() should be moved before this block
	 */
	if (packet_size < 4) /* 4 is smallest size which may be sensible (for PWACH dissector) */
	{
		proto_item  * item;
		item = proto_tree_add_item(tree, proto_handler, tvb, 0, -1, ENC_NA);
		expert_add_info_format(pinfo, item, &ei_pw_packet_size_too_small,
				       "PW packet size (%d) is too small to carry sensible information"
				       ,(int)packet_size);
		/* represent problems in the Packet List pane */
		col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);
		col_set_str(pinfo->cinfo, COL_INFO, "Malformed: PW packet is too small");
		return TRUE;
	}
	if (dissect_try_cw_first_nibble(tvb, pinfo, tree))
	{
		return TRUE;
	}
	return FALSE;
}


/*
 * NOTE. RFC describes ATM-specific byte in a cumbersome way.
 * It is a part of CW, but at the same time, it must be repeated
 * with each cell, _except_ first.
 *
 * Alternatively, ATM-specific byte may be considered as part of
 * PW payload (i.e., as part of pw atm cell header), so we can say that
 * it is being repeated with each cell.
 *
 * This dissector is written according to the latter consideration.
 */
static int
dissect_11_or_aal5_pdu(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void* data _U_)
{
	const char           * proto_name_column;
	const char           * proto_name_tree = NULL;
	gint                   payload_size;
	int                    cells;
	pwatm_private_data_t   pd              = PWATM_PRIVATE_DATA_T_INITIALIZER;

	proto_name_column = &shortname_11_or_aal5_pdu[0];
	if (too_small_packet_or_notpw(tvb, pinfo, tree, proto_11_or_aal5_pdu, proto_name_column))
	{
		return 1;
	}
	pd.packet_size = tvb_reported_length_remaining(tvb, 0);

	/*
	 * Guess encapsulation mode according to M & V bits from the 3rd byte of CW.
	 * Also adjust protocol name strings.
	 */
	{
		guint8 third_byte;
		third_byte = tvb_get_guint8(tvb, 3);
		if (0 == (third_byte & 0x80 /*generic_cw.m*/))
		{ /*1:1 modes*/
			if (0 != (third_byte & 0x40 /*generic_cw.v*/))
			{
				pd.mode = PWATM_MODE_11_VPC;
				proto_name_column = &shortname_11_vpc[0];
				proto_name_tree = &longname_pw_atm_11_vpc[0];
			}
			else
			{
				pd.mode = PWATM_MODE_11_VCC;
				proto_name_column = &shortname_11_vcc[0];
				proto_name_tree = &longname_pw_atm_11_vcc[0];
			}
		}
		else
		{
			pd.mode = PWATM_MODE_AAL5_PDU;
			proto_name_column = &shortname_aal5_pdu[0];
			proto_name_tree = &longname_pw_atm_aal5_pdu[0];
		}
	}


	/* check how "good" is this packet */
	pd.props = PWC_PACKET_PROPERTIES_T_INITIALIZER;
	if (0 != (tvb_get_guint8(tvb, 0) & 0xf0 /*bits03*/))
	{
		pd.props |= PWC_CW_BAD_BITS03;
	}
	if (0 != (tvb_get_guint8(tvb, 0) & 0x0f /*generic_cw.rsvd*/))
	{
		pd.props |= PWC_CW_BAD_RSV;
	}

	/*
	 * Do not dissect and validate atm-specific byte (3rd byte of CW).
	 * It will be dissected/validated as pw cell header.
	 */

	/*
	 * Decide about payload length and padding.
	 *
	 * Is padding allowed?
	 * 	eth header length  == 14
	 * 	mpls label length  ==  4
	 * 	cw length 	   ==  4
	 * 	min payload length == 48
	 * 	=> 14 + 4 + 4 + 48 == 70
	 *        => 70 >= 64
	 *	    => no padding allowed
	 */
	if (MODE_11(pd.mode))
	{
		gint bad_padding_size;
		payload_size = pd.packet_size - (PWC_SIZEOF_CW-1);
		cells = number_of_cells(pd.mode, pd.submode, payload_size, &bad_padding_size);
		if ((0 == cells) || (0 != bad_padding_size))
		{
			pd.props |= PWC_PAY_SIZE_BAD;
		}
	}
	else
	{ /*aal5_pdu mode*/
		gint bad_padding_size;
		payload_size = pd.packet_size - PWC_SIZEOF_CW;
		cells = number_of_cells(pd.mode, pd.submode, payload_size, &bad_padding_size);
		/* at least 1 cell must be present in the packet in this mode*/
		if ((1 > cells) || (0 != bad_padding_size))
		{
			pd.props |= PWC_PAY_SIZE_BAD;
		}
		cells = -1; /*this value not needed anymore, suppress pinting of it*/
	}

	if (PWATM_MODE_AAL5_PDU == pd.mode)
	{
		/* sub-dissectors _may_ overwrite columns in aal5_pdu mode */
		col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);
		col_clear(pinfo->cinfo, COL_INFO);
		col_append_pw_info(pinfo, payload_size, cells, 0, &pd);
	}

	{
		proto_item* item;
		item = proto_tree_add_item(tree, proto_11_or_aal5_pdu, tvb, 0, -1, ENC_NA);
		/*overwrite heading line*/
		proto_item_set_text(item, proto_name_tree, 0/*-warn gcc 3.4.4*/);
		pwc_item_append_text_n_items(item, cells, "good ATM cell");
		{
			proto_tree* tree2;
			tree2 = proto_item_add_subtree(item, ett_encaps);
			{
				proto_item* item2;
				item2 = proto_tree_add_boolean(tree2
					,VALUE_SELECTOR_VPC_VCC_PDU(pd.mode
						,hf_pw_type_11_vpc
						,hf_pw_type_11_vcc
						,hf_pw_type_aal5_pdu)
					,tvb, 0, 0, TRUE);
				PROTO_ITEM_SET_GENERATED(item2);
				if (MODE_11(pd.mode))
				{
					item2 = proto_tree_add_int(tree2, hf_11_ncells, tvb, 0, 0, cells);
					PROTO_ITEM_SET_GENERATED(item2);
				}
			}
		}
		if (pd.props & PWC_PAY_SIZE_BAD)
		{
			expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
				"PW payload size (%d) must be <> 0 and multiple of %d",
				(int)payload_size, pw_cell_size(pd.mode, pd.submode));
			if ((payload_size != 0) && MODE_11(pd.mode))
			{
				expert_add_info_format(pinfo, item, &ei_cell_broken,
					"PW ATM cell [%.3d] is broken", (int)cells);
			}
		}
	}

	{
		tvbuff_t* tvb_2;
		tvb_2 = tvb_new_subset_length(tvb, 0, PWC_SIZEOF_CW);
		call_dissector_with_data(dh_control_word, tvb_2, pinfo, tree, &pd);

		tvb_2 = tvb_new_subset_remaining(tvb, (PWC_SIZEOF_CW-1));
		if (MODE_11(pd.mode))
		{
			dissect_payload_and_padding(tvb_2, pinfo, tree, payload_size, 0, &pd);
		}
		else
		{ /*aal5_pdu mode*/
			if (payload_size != 0)
			{
				tvbuff_t* tvb_3;
				struct atm_phdr ph;

				tvb_3 = tvb_new_subset_remaining(tvb_2, 1);
				/* prepare atm pseudo header for atm aal5 decoding */
				prepare_pseudo_header_atm(&ph, &pd, AAL_5);
				call_dissector_with_data(dh_atm_untruncated, tvb_3, pinfo, tree, &ph);
			}
		}
	}

	if (MODE_11(pd.mode))
	{
		/* overwrite everything written by sub-dissectors in 1:1 modes*/
		col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);
		col_clear(pinfo->cinfo, COL_INFO);
		col_append_pw_info(pinfo, payload_size, cells, 0, &pd);
	}

	return tvb_captured_length(tvb);
}


static int
dissect_aal5_sdu(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void* data _U_)
{
	const char           * proto_name_column;
	gint                   payload_size;
	gint                   padding_size;
	int                    cells;
	pwatm_private_data_t   pd      = PWATM_PRIVATE_DATA_T_INITIALIZER;

	pd.mode = PWATM_MODE_AAL5_SDU;

	proto_name_column = &shortname_aal5_sdu[0];
	if (too_small_packet_or_notpw(tvb, pinfo, tree, proto_aal5_sdu, proto_name_column))
	{
		return 1;
	}
	pd.packet_size = tvb_reported_length_remaining(tvb, 0);

	/* check how "good" is this packet */
	/* also decide payload length from packet size and CW */
	if (0 != (tvb_get_guint8(tvb, 0) & 0xf0 /*bits03*/))
	{
		pd.props |= PWC_CW_BAD_BITS03;
	}

	pd.submode = PWATM_SUBMODE_DEFAULT;
	if (0 != (tvb_get_guint8(tvb, 0) & 0x08 /*preferred_cw.T*/))
	{
		pd.submode = PWATM_SUBMODE_ADMIN_CELL;
	}

	if (! pref_aal5_sdu_extend_cw_length_with_rsvd)
	{
		if (0 != (tvb_get_guint8(tvb, 1) & 0xc0 /*preferred_cw.rsvd*/))
		{
			pd.props |= PWC_CW_BAD_RSV;
		}
	}
	{
		/* RFC4717:
		 * [ If the packet's length (defined as the length of the layer 2 payload
		 * plus the length of the control word) is less than 64 bytes, the
		 * length field MUST be set to the packet's length.  Otherwise, the
		 * length field MUST be set to zero... Note that the length field
		 * is not used in the N-to-one mode and MUST be set to 0. ]
		 *
		 * Also we allow some "extensions"conducted by pref_xxx.
		 */
		gint payload_size_from_packet;
		int cw_len; /*length field from cw*/

		payload_size_from_packet = pd.packet_size - PWC_SIZEOF_CW;
		if (pref_aal5_sdu_extend_cw_length_with_rsvd)
		{
			cw_len = tvb_get_guint8(tvb, 1) & 0xff;
		}
		else
		{
			cw_len = tvb_get_guint8(tvb, 1) & 0x3f;
		}

		/*
		 * Initial assumptions: no padding,
		 * payload size derived from psn packet size.
		 */
		payload_size = payload_size_from_packet;
		padding_size = 0;

		if (0 == cw_len)
		{
			/*keep initial assumptions*/
		}
		else if (!pref_aal5_sdu_allow_cw_length_nonzero
		         && (PWATM_SUBMODE_ADMIN_CELL == pd.submode))
		{
			/*
			 * The "allow CW.Length != 0" option affects
			 * ATM admin cell submode only, because this submode
			 * is equal to N:1 encapsulation.
			 * CW.Length !=0 is always OK for normal (AAL5 payload) submode.
			 */
			pd.props |= PWC_CW_BAD_LEN_MUST_BE_0;
		}
		else
		{
			gint payload_size_from_cw;
			payload_size_from_cw = cw_len - PWC_SIZEOF_CW;
			if (payload_size_from_cw <= 0)
			{
				pd.props |= PWC_CW_BAD_PAYLEN_LE_0;
			}
			else if (payload_size_from_cw > payload_size_from_packet)
			{
				pd.props |= PWC_CW_BAD_PAYLEN_GT_PACKET;
			}
			else
			{

				payload_size = payload_size_from_cw;
				padding_size = payload_size_from_packet - payload_size_from_cw; /* >=0 */
				if (padding_size != 0)
				{
					/*
					 * Padding is not allowed in ATM admin cell submode only,
					 * because this submode is equal to N:1 encapsulation.
					 * Padding is OK for normal (AAL5 payload) submode.
					 */
					if (PWATM_SUBMODE_ADMIN_CELL == pd.submode)
					{
						pd.props |= PWC_CW_BAD_PADDING_NE_0;
						/*restore sizes*/
						payload_size = payload_size_from_packet;
						padding_size = 0;
					}
				}
			}
		}

		if (PWATM_SUBMODE_ADMIN_CELL == pd.submode)
		{
			gint bad_padding_size;
			cells = number_of_cells(pd.mode, pd.submode, payload_size, &bad_padding_size);
			/* only one atm admin cell is allowed in the packet in this mode*/
			if ((1 != cells) || (0 != bad_padding_size))
			{
				pd.props |= PWC_PAY_SIZE_BAD;
			}
		}
		else
		{
			cells = -1; /*unknown*/
			/* Any size is allowed for AAL5 SDU payload */
		}
	}

	/* fill columns in Packet List */
	col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);
	if (PWATM_SUBMODE_ADMIN_CELL == pd.submode)
	{
		col_append_str(pinfo->cinfo, COL_PROTOCOL, ", OAM cell");
	}

	col_clear(pinfo->cinfo, COL_INFO);
	col_append_pw_info(pinfo, payload_size, cells, padding_size, &pd);

	{
		proto_item* item;
		item = proto_tree_add_item(tree, proto_aal5_sdu, tvb, 0, -1, ENC_NA);
		{
			proto_tree* tree2;
			tree2 = proto_item_add_subtree(item, ett_encaps);
			{
				item = proto_tree_add_boolean(tree2, hf_pw_type_aal5_sdu, tvb, 0, 0, TRUE);
				PROTO_ITEM_SET_GENERATED(item);
			}
		}
		if (pd.props & PWC_PAY_SIZE_BAD)
		{
			DISSECTOR_ASSERT(PWATM_SUBMODE_ADMIN_CELL == pd.submode);
			expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
				"In ATM admin cell mode,"
				" PW payload size (%d) must be == %d (exactly 1 admin cell)",
				(int)payload_size, (int)SIZEOF_N1_PW_CELL);
		}
	}

	{
		tvbuff_t* tvb_2;
		tvb_2 = tvb_new_subset_length(tvb, 0, PWC_SIZEOF_CW);
		call_dissector_with_data(dh_control_word, tvb_2, pinfo, tree, &pd);

		tvb_2 = tvb_new_subset_remaining(tvb, PWC_SIZEOF_CW);
		if (PWATM_SUBMODE_ADMIN_CELL == pd.submode)
		{
			dissect_payload_and_padding(tvb_2, pinfo, tree, payload_size, padding_size, &pd);
		}
		else /*AAL5 payload*/
		{
			if (payload_size != 0)
			{
				tvbuff_t* tvb_3;
				struct atm_phdr ph;

				tvb_3 = tvb_new_subset_length(tvb_2, 0, payload_size);
				/* prepare atm pseudo header for atm aal5 decoding */
				prepare_pseudo_header_atm(&ph, &pd, AAL_5);
				call_dissector_with_data(dh_atm_truncated, tvb_3, pinfo, tree, &ph); /* no PAD and trailer */
			}
			if (padding_size != 0)
			{
				tvbuff_t* tvb_3;
				tvb_3 = tvb_new_subset_length_caplen(tvb_2, payload_size, padding_size, -1);
				call_dissector(dh_padding, tvb_3, pinfo, tree);
			}
		}
	}
	return tvb_captured_length(tvb);
}


static int
dissect_n1_cw(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void* data _U_)
{
	const char           * proto_name_column;
	gint                   payload_size;
	gint                   padding_size;
	int                    cells;
	pwatm_private_data_t   pd      = PWATM_PRIVATE_DATA_T_INITIALIZER;

	pd.mode = PWATM_MODE_N1_CW;

	proto_name_column = &shortname_n1_cw[0];
	if (too_small_packet_or_notpw(tvb, pinfo, tree, proto_n1_cw, proto_name_column))
	{
		return 1;
	}
	pd.packet_size = tvb_reported_length_remaining(tvb, 0);

	/* check how "good" is this packet */
	/* also decide payload length from packet size and CW */
	pd.props = PWC_PACKET_PROPERTIES_T_INITIALIZER;
	if (0 != (tvb_get_guint8(tvb, 0) & 0xf0 /*bits03*/))
	{
		pd.props |= PWC_CW_BAD_BITS03;
	}
	if (0 != (tvb_get_guint8(tvb, 0) & 0x0f /*preferred_cw.flags*/))
	{
		pd.props |= PWC_CW_BAD_FLAGS;
	}
	if (! pref_n1_cw_extend_cw_length_with_rsvd)
	{
		if (0 != (tvb_get_guint8(tvb, 1) & 0xc0 /*preferred_cw.rsvd*/))
		{
			pd.props |= PWC_CW_BAD_RSV;
		}
	}
	{
		/* RFC4717:
		 * [ If the packet's length (defined as the length of the layer 2 payload
		 * plus the length of the control word) is less than 64 bytes, the
		 * length field MUST be set to the packet's length.  Otherwise, the
		 * length field MUST be set to zero... Note that the length field
		 * is not used in the N-to-one mode and MUST be set to 0. ]
		 *
		 * Also we allow some "extensions"conducted by pref_xxx.
		 */
		gint payload_size_from_packet;
		int cw_len; /*length field from cw*/

		payload_size_from_packet = pd.packet_size - PWC_SIZEOF_CW;
		if (pref_n1_cw_extend_cw_length_with_rsvd)
		{
			cw_len = tvb_get_guint8(tvb, 1) & 0xff;
		}
		else
		{
			cw_len = tvb_get_guint8(tvb, 1) & 0x3f;
		}

		/*
		 * Initial assumptions: no padding,
		 * payload size derived from psn packet size.
		 */
		payload_size = payload_size_from_packet;
		padding_size = 0;

		if (0 == cw_len)
		{
			/*keep initial assumptions*/
		}
		else if (!pref_n1_cw_allow_cw_length_nonzero)
		{
			pd.props |= PWC_CW_BAD_LEN_MUST_BE_0;
		}
		else
		{
			gint payload_size_from_cw;
			payload_size_from_cw = cw_len - PWC_SIZEOF_CW;
			if (payload_size_from_cw <= 0)
			{
				pd.props |= PWC_CW_BAD_PAYLEN_LE_0;
			}
			else if (payload_size_from_cw > payload_size_from_packet)
			{
				pd.props |= PWC_CW_BAD_PAYLEN_GT_PACKET;
			}
			else
			{

				payload_size = payload_size_from_cw;
				padding_size = payload_size_from_packet - payload_size_from_cw; /* >=0 */
				if (padding_size != 0)
				{
					pd.props |= PWC_CW_BAD_PADDING_NE_0;
					/*restore sizes*/
					payload_size = payload_size_from_packet;
					padding_size = 0;
				}
			}
		}
		{
			gint bad_padding_size;
			cells = number_of_cells(pd.mode, pd.submode, payload_size, &bad_padding_size);
			if ((0 == cells) || (0 != bad_padding_size))
			{
				pd.props |= PWC_PAY_SIZE_BAD;
			}
		}
	}

	{
		proto_item* item;
		item = proto_tree_add_item(tree, proto_n1_cw, tvb, 0, -1, ENC_NA);
		pwc_item_append_text_n_items(item, cells, "good ATM cell");
		{
			proto_tree* tree2;
			tree2 = proto_item_add_subtree(item, ett_encaps);
			{
				proto_item* item2;
				item2 = proto_tree_add_boolean(tree2, hf_pw_type_n1_cw, tvb, 0, 0, TRUE);
				PROTO_ITEM_SET_GENERATED(item2);
				item2 = proto_tree_add_int(tree2, hf_n1_cw_ncells, tvb, 0, 0, cells);
				PROTO_ITEM_SET_GENERATED(item2);
			}
		}
		if (pd.props & PWC_PAY_SIZE_BAD)
		{
			if (payload_size != 0)
			{
				expert_add_info_format(pinfo, item, &ei_cell_broken,
					"PW ATM cell [%.3d] is broken", (int)cells);
				expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_note,
					"PW payload size (%d) must be <>0 and multiple of %d",
					(int)payload_size, (int)SIZEOF_N1_PW_CELL);
			}
			else
			{
				expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
					"PW payload size (%d) must be <>0 and multiple of %d",
					(int)payload_size, (int)SIZEOF_N1_PW_CELL);
			}
		}
	}

	{
		tvbuff_t* tvb_2;
		tvb_2 = tvb_new_subset_length(tvb, 0, PWC_SIZEOF_CW);
		call_dissector_with_data(dh_control_word, tvb_2, pinfo, tree, &pd);

		tvb_2 = tvb_new_subset_remaining(tvb, PWC_SIZEOF_CW);
		dissect_payload_and_padding(tvb_2, pinfo, tree, payload_size, padding_size, &pd);
	}

	/* fill columns in Packet List */
	/* overwrite everything written by sub-dissectors */
	col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);

	col_clear(pinfo->cinfo, COL_INFO);
	col_append_pw_info(pinfo, payload_size, cells, padding_size, &pd);
	return tvb_captured_length(tvb);
}


static int
dissect_n1_nocw(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void* data _U_)
{
	const char           * proto_name_column = &shortname_n1_nocw[0];
	gint                   payload_size;
	int                    cells;
	pwatm_private_data_t   pd                = PWATM_PRIVATE_DATA_T_INITIALIZER;

	pd.mode = PWATM_MODE_N1_NOCW;
	pd.packet_size = tvb_reported_length_remaining(tvb, 0);

	/* check how "good" is this packet */
	/* also decide payload length from packet size */
	pd.props = PWC_PACKET_PROPERTIES_T_INITIALIZER;
	payload_size = pd.packet_size;
	{
		gint bad_padding_size;
		cells = number_of_cells(pd.mode, pd.submode, pd.packet_size, &bad_padding_size);
		if ((cells == 0) || (bad_padding_size != 0))
		{
			pd.props |= PWC_PAY_SIZE_BAD;
		}
	}

	{
		proto_item* item;
		item = proto_tree_add_item(tree, proto_n1_nocw, tvb, 0, -1, ENC_NA);
		pwc_item_append_text_n_items(item, cells, "ATM cell");
		{
			proto_tree* tree2;
			tree2 = proto_item_add_subtree(item, ett_encaps);
			{
				proto_item* item2;
				item2 = proto_tree_add_boolean(tree2, hf_pw_type_n1_nocw, tvb, 0, 0, TRUE);
				PROTO_ITEM_SET_GENERATED(item2);
				item2 = proto_tree_add_int(tree2, hf_n1_nocw_ncells, tvb, 0, 0, cells);
				PROTO_ITEM_SET_GENERATED(item2);
			}
		}
		if (pd.props & PWC_PAY_SIZE_BAD)
		{
			if (payload_size != 0)
			{
				expert_add_info_format(pinfo, item, &ei_cell_broken,
					"Last PW ATM cell [%.3d] is broken", (int)cells);
				expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_note,
					"PW payload size (%d) must be <>0 and multiple of %d",
					(int)payload_size, (int)SIZEOF_N1_PW_CELL);
			}
			else
			{
				expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
					"PW payload size (%d) must be <>0 and multiple of %d",
					(int)payload_size, (int)SIZEOF_N1_PW_CELL);
			}
		}
	}

	dissect_payload_and_padding(tvb, pinfo, tree, payload_size, 0, &pd);

	/* fill columns in Packet List */
	/* overwrite everything written by sub-dissectors */
	col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name_column);

	col_clear(pinfo->cinfo, COL_INFO);
	col_append_pw_info(pinfo, payload_size, cells, 0, &pd);
	return tvb_captured_length(tvb);
}


static void
proto_item_append_text_cwb3_fields(proto_item * item, const pwatm_private_data_t * const pd)
{
	if (NULL == item) return;
	DISSECTOR_ASSERT(NULL != pd);
	if (pd->cwb3.m   >= 0)
		proto_item_append_text(item, "M:%.1u  "  , (unsigned)(pd->cwb3.m));
	if (pd->cwb3.v   >= 0)
		proto_item_append_text(item, "V:%.1u  "  , (unsigned)(pd->cwb3.v));
	if (pd->cwb3.rsv >= 0)
		proto_item_append_text(item, "RSV:%.1u  ", (unsigned)(pd->cwb3.rsv));
	if (pd->cwb3.u >= 0)
		proto_item_append_text(item, "U:%.1u  "  , (unsigned)(pd->cwb3.u));
	if (pd->cwb3.e >= 0)
		proto_item_append_text(item, "EFCI:%.1u  ",(unsigned)(pd->cwb3.e));
	if (pd->cwb3.clp >= 0)
		proto_item_append_text(item, "CLP:%.1u  ", (unsigned)(pd->cwb3.clp));
	return;
}


static int
dissect_control_word(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void* data)
{
	pwatm_private_data_t* pd;

	/* Reject the packet if data is NULL */
	if (data == NULL)
		return 0;
	pd = (pwatm_private_data_t *)data;

	/*
	 * NB: do not touch columns -- keep info from previous dissector
	 */

	{
		gint size;
		size = tvb_reported_length_remaining(tvb, 0);
		if (size < PWC_SIZEOF_CW)
		{
			proto_item  *item;
			item = proto_tree_add_item(tree, proto_control_word, tvb, 0, -1, ENC_NA);
			expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
					       "Packet (size: %d) is too small to carry MPLS PW Control Word"
					       ,(int)size);
			return tvb_captured_length(tvb);
		}
	}

	{
		proto_item* item_top;
		proto_tree* tree2;
		proto_item* item;

		item_top = proto_tree_add_item(tree, proto_control_word, tvb, 0, -1, ENC_NA);
		pwc_item_append_cw(item_top, tvb_get_ntohl(tvb, 0), FALSE);

		tree2 = proto_item_add_subtree(item_top, ett_cw);

		/* bits 0..3 */
		item = proto_tree_add_item(tree2, hf_cw_bits03, tvb, 0, 1, ENC_BIG_ENDIAN);
		if (pd->props & PWC_CW_BAD_BITS03)
		{
			/* add item to tree (and show it) only if its value is wrong*/
			expert_add_info(pinfo, item, &ei_cw_bits03);
		}
		else
		{
			PROTO_ITEM_SET_HIDDEN(item); /* show only in error cases */
		}

		/* flags */
		if (MODE_N1(pd->mode))
		{
			item = proto_tree_add_item(tree2, hf_pref_cw_flags, tvb, 0, 1, ENC_BIG_ENDIAN);
			if (pd->props & PWC_CW_BAD_FLAGS)
			{
				expert_add_info(pinfo, item, &ei_pref_cw_flags);
			}
		}
		if (pd->mode == PWATM_MODE_AAL5_SDU)
		{
			proto_tree_add_item(tree2, hf_pref_cw_a5s_t, tvb, 0, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(tree2, hf_pref_cw_a5s_e, tvb, 0, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(tree2, hf_pref_cw_a5s_c, tvb, 0, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(tree2, hf_pref_cw_a5s_u, tvb, 0, 1, ENC_BIG_ENDIAN);
			/*
			 * rfc4717: [When FRF.8.1 Frame Relay/ATM PVC Service Interworking [RFC3916]
			 * traffic is being transported, the CPCS-UU Least Significant Bit
			 * (LSB) of the AAL5 CPCS-PDU may contain the Frame Relay C/R bit.
			 * The ingress router, PE1, SHOULD copy this bit to the U bit of the
			 * control word.  The egress router, PE2, SHOULD copy the U bit to
			 * the CPCS-UU Least Significant Bit (LSB) of the AAL5 CPCS PDU.]
			 *
			 * Let's remember this bit (and then transfer it to ATM dissector).
			 */
			pd->aal5_sdu_frame_relay_cr_bit =
				(0 == (tvb_get_guint8(tvb, 0) & 0x01 /*preferred_cw.U*/))
				? FALSE : TRUE;
		}

		/* reserved bits */
		if (MODE_11_OR_AAL5_PDU(pd->mode)
		    || (MODE_N1(pd->mode) && !pref_n1_cw_extend_cw_length_with_rsvd)
		    /* for N:1 add RSV only if it is NOT used in length */
		    || ((pd->mode == PWATM_MODE_AAL5_SDU) && !pref_aal5_sdu_extend_cw_length_with_rsvd)
		    /* for AAl5 SDU add RSV only if it is NOT used in length */)
		{
			if (MODE_11_OR_AAL5_PDU(pd->mode))
			{
				item = proto_tree_add_item(tree2
							   ,hf_generic_cw_rsv, tvb, 0, 1, ENC_BIG_ENDIAN);
			}
			else
			{ /*preferred cw*/
				item = proto_tree_add_item(tree2
							   ,hf_pref_cw_rsv, tvb, 1, 1, ENC_BIG_ENDIAN);
			}

			if (pd->props & PWC_CW_BAD_RSV)
			{
				expert_add_info(pinfo, item, &ei_cw_bits03);
			}
			else
			{
				PROTO_ITEM_SET_HIDDEN(item); /*...and show only in error cases */
			}
		}

		/* length */
		if (MODE_N1(pd->mode)
		    || (PWATM_MODE_AAL5_SDU == pd->mode))
		{
			{
				int hf_len = hf_pref_cw_len;
				if (MODE_N1(pd->mode))
				{
					if (pref_n1_cw_extend_cw_length_with_rsvd)
						hf_len = hf_pref_cw_rsvlen;
				}
				else /*PW_MODE_AAL5_SDU*/
				{
					if (pref_aal5_sdu_extend_cw_length_with_rsvd)
						hf_len = hf_pref_cw_rsvlen;
				}
				item = proto_tree_add_item(tree2, hf_len, tvb, 1, 1, ENC_BIG_ENDIAN);
			}
			if (pd->props & PWC_CW_BAD_LEN_MUST_BE_0)
			{
				expert_add_info_format(pinfo, item, &ei_pref_cw_len,
						       "Bad Length: must be 0 for this encapsulation");
			}
			if (pd->props & PWC_CW_BAD_PAYLEN_LE_0)
			{
				expert_add_info_format(pinfo, item, &ei_pref_cw_len,
						       "Bad Length: too small, must be >= %d",
						       (int)(PWC_SIZEOF_CW+SIZEOF_N1_PW_CELL));
			}
			if (pd->props & PWC_CW_BAD_PAYLEN_GT_PACKET)
			{
				expert_add_info_format(pinfo, item, &ei_pref_cw_len,
						       "Bad Length: must be <= than PSN packet size (%d)",
						       (int)pd->packet_size);
			}
			if (pd->props & PWC_CW_BAD_PADDING_NE_0)
			{
				expert_add_info_format(pinfo, item, &ei_pref_cw_len,
						       "Bad Length: must be == PSN packet size (%d), no padding allowed",
						       (int)pd->packet_size);
			}
		}

		/* sequence number */
		proto_tree_add_item(tree2, hf_cw_seq, tvb
				    ,MODE_11_OR_AAL5_PDU(pd->mode) ? 1 : 2, 2, ENC_BIG_ENDIAN);

		/* atm-specific byte */
		if (MODE_11(pd->mode))
		{
			item = proto_tree_add_item(tree2, hf_gen_cw_atmbyte, tvb, 3, 1, ENC_BIG_ENDIAN);
			expert_add_info_format(pinfo, item, &ei_gen_cw_atmbyte,
						   "ATM-specific byte of CW is fully dissected below as %s%s"
						   ,(PWATM_MODE_11_VPC == pd->mode) ? "a part of "	: ""
						   ,"PW ATM Cell Header [000]");
			/*
			 * Note: if atm-specific byte contains something wrong
			 * (e.g. non-zero RSV or inadequate V), CW is not
			 * marked as "bad".
			 */
		}

		/*3rd byte of CW*/
		if (PWATM_MODE_AAL5_PDU == pd->mode)
		{
			tvbuff_t* tvb_2;
			tvb_2 = tvb_new_subset_remaining(tvb, (PWC_SIZEOF_CW-1));
			call_dissector_with_data(dh_cell_header, tvb_2, pinfo, tree2, pd);
			proto_item_append_text(item_top, ", ");
			proto_item_append_text_cwb3_fields(item_top, pd);
		}
	}

	return tvb_captured_length(tvb);
}


/*
 * This function is also used to dissect 3rd byte of CW in AAL5 PDU mode.
 */
static int
dissect_cell_header(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * data)
{
	pwatm_private_data_t * pd;
	gboolean               is_enough_data;
	int                    dissect_size;

	/* Reject the packet if data is NULL */
	if (data == NULL)
		return 0;
	pd = (pwatm_private_data_t *)data;

	pd->vpi	     = pd->vci = pd->pti = -1;
	pd->cwb3.clp = pd->cwb3.m = pd->cwb3.v = pd->cwb3.rsv = pd->cwb3.u = pd->cwb3.e = -1;

	if (PWATM_MODE_AAL5_PDU == pd->mode)
	{
		if (tvb_reported_length_remaining(tvb, 0) < 1)
		{
			is_enough_data = FALSE;
			dissect_size = 0;
		}
		else
		{
			is_enough_data = TRUE;
			dissect_size = 1;
		}
	}
	else
	{
		gint size;
		size = tvb_reported_length_remaining(tvb, 0);

		if (size < pw_cell_header_size(pd->mode, pd->submode))
		{
			is_enough_data = FALSE;
			dissect_size = size;
		}
		else
		{
			is_enough_data = TRUE;
			dissect_size = pw_cell_header_size(pd->mode, pd->submode);
		}
	}

	/*
	 * NB: do not touch columns -- keep info from previous dissector
	 */

	/* Collect info for upper-level dissectors regardless of
	 * the presence of the tree
	 */
	if (is_enough_data)
	{
		guint8 tmp8;
		switch (pd->mode)
		{
		case PWATM_MODE_AAL5_SDU:
			DISSECTOR_ASSERT(pd->submode == PWATM_SUBMODE_ADMIN_CELL);
			/*fallthrough for ATM admin cell submode only*/
			/* FALL THROUGH */
		case PWATM_MODE_N1_CW:
		case PWATM_MODE_N1_NOCW:
			pd->vpi		= (tvb_get_ntohs (tvb, 0) >> 4);
			pd->vci		= (tvb_get_ntoh24(tvb, 1) >> 4) & 0xffff;
			tmp8 		= (tvb_get_guint8(tvb, 3));
			pd->pti		= (tmp8 >> 1) & 0x07;
			pd->cwb3.clp 	= (tmp8 >> 0) & 0x01;
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.vpi, pd->vpi);
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.vci, pd->vci);
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.pti, pd->pti);
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.clp, pd->cwb3.clp);
			/*
			 * OAM cell mode is always used for aal5_sdu/admin_cell mode,
			 * even if pti indicates user cell.
			 */
			pd->cell_mode_oam =
				((pd->mode == PWATM_MODE_AAL5_SDU) && (pd->submode == PWATM_SUBMODE_ADMIN_CELL))
				|| PTI_IS_ADMIN(pd->pti);
			break;
		case PWATM_MODE_11_VPC:
			pd->vci = tvb_get_ntohs(tvb, 1);
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.vci, pd->vci);
			/*fallthrough*/
		case PWATM_MODE_11_VCC:
			tmp8	= (tvb_get_guint8(tvb, 0));
			pd->cwb3.m	= (tmp8 >> 7) & 0x1;
			pd->cwb3.v	= (tmp8 >> 6) & 0x1;
			pd->cwb3.rsv	= (tmp8 >> 4) & 0x3;
			pd->pti		= (tmp8 >> 1) & 0x7;
			pd->cwb3.clp	= (tmp8 >> 0) & 0x1;
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.pti, pd->pti);
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.clp, pd->cwb3.clp);
			/*
			 * OAM cell mode is possible if packet contains atm cell (m == 0).
			 */
			pd->cell_mode_oam = PTI_IS_ADMIN(pd->pti) && (pd->cwb3.m == 0);
			break;
		case PWATM_MODE_AAL5_PDU:
			tmp8		= (tvb_get_guint8(tvb, 0));
			pd->cwb3.m	= (tmp8 >> 7) & 0x1;
			pd->cwb3.v	= (tmp8 >> 6) & 0x1;
			pd->cwb3.rsv	= (tmp8 >> 3) & 0x7;
			pd->cwb3.u	= (tmp8 >> 2) & 0x1;
			pd->cwb3.e	= (tmp8 >> 1) & 0x1;
			pd->cwb3.clp	= (tmp8 >> 0) & 0x1;
			UPDATE_CUMULATIVE_VALUE(pd->cumulative.clp, pd->cwb3.clp);
			break;
		default:
			DISSECTOR_ASSERT_NOT_REACHED();
			break;
		}
	}

	{
		proto_item* item;

		item = proto_tree_add_item(tree, proto_cell_header, tvb
			,0
			,dissect_size
			,ENC_NA);
		if (PWATM_MODE_AAL5_PDU == pd->mode)
		{
			proto_item_set_text(item, "Third byte of Control Word"); /*overwrite heading line*/
			/* cwb3 fileds are appended to CW heading line, not here */
		}
		else
		{
			proto_item_append_text(item, " [%.3d]", pd->pw_cell_number);
			proto_item_append_text(item, ", ");
			if (pd->vpi >= 0)
				proto_item_append_text(item, "VPI:%.4u  ", (unsigned)(pd->vpi));
			if (pd->vci >= 0)
				proto_item_append_text(item, "VCI:%.5u  ", (unsigned)(pd->vci));
			if (pd->pti >= 0)
				proto_item_append_text(item, "PTI:%.1u  ", (unsigned)(pd->pti));
			proto_item_append_text_cwb3_fields(item, pd);
		}

		{
			proto_tree* tree2;
			tree2 = proto_item_add_subtree(item, ett_cell_header);
			if (is_enough_data)
			{
				proto_item* item2;
				if (MODE_N1(pd->mode)
				    || ((pd->mode == PWATM_MODE_AAL5_SDU) && (pd->submode == PWATM_SUBMODE_ADMIN_CELL)))
				{
					proto_tree_add_uint(tree2, hf_cell_h_vpi, tvb, 0, 2, (unsigned)pd->vpi);
					proto_tree_add_uint(tree2, hf_cell_h_vci, tvb, 1, 3, (unsigned)pd->vci);

					item2 = proto_tree_add_item(tree2, hf_cell_h_pti, tvb, 3, 1, ENC_BIG_ENDIAN);
					if (NULL == try_val_to_str(pd->pti, atm_pt_vals))
					{
						expert_add_info_format(pinfo, item2, &ei_cell_h_pti_undecoded,
							"Unknown value of PTI field (%d) in the ATM cell header",
							pd->pti);
					}
					else if ((pd->mode == PWATM_MODE_AAL5_SDU) && !PTI_IS_ADMIN(pd->pti))
					{
						expert_add_info_format(pinfo, item2, &ei_cell_h_pti_malformed,
							"ATM admin cell is transerred;"
							" PTI field (%d) should be 4, 5 or 6.",
							pd->pti);
					}

					proto_tree_add_item(tree2, hf_cell_h_clp, tvb, 3, 1, ENC_BIG_ENDIAN);
				}
				else if (MODE_11_OR_AAL5_PDU(pd->mode))
				{
					item2 = proto_tree_add_item(tree2, hf_cell_h_m  , tvb, 0, 1, ENC_BIG_ENDIAN);
					if ((0 != pd->cwb3.m) && MODE_11(pd->mode))
					{
						expert_add_info(pinfo, item2, &ei_cell_h_m);
					}

					item2 = proto_tree_add_item(tree2, hf_cell_h_v  , tvb, 0, 1, ENC_BIG_ENDIAN);
					if ((0 == pd->cwb3.v) && (PWATM_MODE_11_VPC == pd->mode))
					{
						expert_add_info(pinfo, item2, &ei_cell_h_v_not_zero);
					}
					if ((0 != pd->cwb3.v) && (PWATM_MODE_11_VCC == pd->mode))
					{
						expert_add_info_format(pinfo, item2, &ei_cell_h_v_not_one,
							"1:1 VCC mode:"
							" V bit must be 0 to indicate that VCI is absent");
					}
					if ((0 != pd->cwb3.v) && (PWATM_MODE_AAL5_PDU == pd->mode))
					{
						expert_add_info_format(pinfo, item2, &ei_cell_h_v_not_one,
							"AAL5 PDU mode:"
							" V bit must be 0 to indicate that VCI is absent");
					}

					item2 = proto_tree_add_item(tree2
						,(PWATM_MODE_AAL5_PDU == pd->mode)
							? hf_aal5_pdu_rsv
							: hf_cell_h_rsv
						,tvb, 0, 1, ENC_BIG_ENDIAN);
					if (0 != pd->cwb3.rsv)
					{
						expert_add_info(pinfo, item2, &ei_cell_h_rsv);
					}
					else
					{
				                PROTO_ITEM_SET_HIDDEN(item2); /*...and show only in error cases */
			                }

					if (MODE_11(pd->mode))
					{
						item2 = proto_tree_add_item(tree2, hf_cell_h_pti, tvb, 0, 1, ENC_BIG_ENDIAN);
						if (NULL == try_val_to_str(pd->pti, atm_pt_vals))
						{
							expert_add_info_format(pinfo, item2, &ei_cell_h_pti_undecoded,
								"Unknown value of PTI field (%d) in the atm-specific byte"
								,pd->pti);
						}
					}
					else
					{
						proto_tree_add_item(tree2, hf_aal5_pdu_u, tvb, 0, 1, ENC_BIG_ENDIAN);
						proto_tree_add_item(tree2, hf_aal5_pdu_e, tvb, 0, 1, ENC_BIG_ENDIAN);
					}

					proto_tree_add_item(tree2, hf_cell_h_clp, tvb, 0, 1, ENC_BIG_ENDIAN);

					if (PWATM_MODE_11_VPC == pd->mode)
					{
						proto_tree_add_uint(tree2, hf_cell_h_vci, tvb, 1, 2
							,(unsigned)pd->vci);
					}
				}
				else
				{
					DISSECTOR_ASSERT_NOT_REACHED();
				}
			}
			else
			{
				expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
					"Not enough data (size: %d), impossible to decode",
					(int)dissect_size);
			}
		}
	}
	return dissect_size;
}



static int
dissect_cell(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * data)
{
	gboolean is_enough_data;
	int      dissect_size;
	gint size;
	proto_item* item;
	pwatm_private_data_t * pd = (pwatm_private_data_t *)data;

	size = tvb_reported_length_remaining(tvb, 0);
	if (size < SIZEOF_ATM_CELL_PAYLOAD)
	{
		is_enough_data = FALSE;
		dissect_size = size;
	}
	else
	{
		is_enough_data = TRUE;
		dissect_size = SIZEOF_ATM_CELL_PAYLOAD;
	}

	/*
	 * NB: do not touch columns -- keep info from previous dissector
	 */

	item = proto_tree_add_item(tree, proto_cell, tvb, 0, dissect_size, ENC_NA);
	if (NULL != pd)
		proto_item_append_text(item, " [%.3d]", pd->pw_cell_number);

	pwc_item_append_text_n_items(item, dissect_size, "byte");
	if (!is_enough_data)
	{
		expert_add_info_format(pinfo, item, &ei_pw_payload_size_invalid_error,
			"Bad length of cell payload: must be == %d",
			(int)SIZEOF_ATM_CELL_PAYLOAD);
	}

	{
		proto_tree* tree2;
		tvbuff_t* tvb_d;
		tree2 = proto_item_add_subtree(item, ett_cell);
		tvb_d = tvb_new_subset_length_caplen(tvb, 0, dissect_size, -1);
		call_data_dissector(tvb_d, pinfo, tree2);
		item = proto_tree_add_int(tree2, hf_cell_payload_len, tvb, 0, 0, dissect_size);
		PROTO_ITEM_SET_HIDDEN(item);
	}

	return dissect_size;
}


void
proto_register_pw_atm_ata(void)
{
	static const value_string clp_vals[] = {
		{ 0, "High priority" },
		{ 1, "Low priority" },
		{ 0, NULL }
	};
	static const value_string m_vals[] = {
		{ 0, "ATM cell" },
		{ 1, "AAL5 payload" },
		{ 0, NULL }
	};
	static const value_string u_vals[] = {
		{ 0, "This frame does not contain the last cell of AAL5 PDU" },
		{ 1, "This frame contains the last cell of AAL5 PDU" },
		{ 0, NULL }
	};
	static const value_string e_vals[] = {
		{ 0, "Congestion is not experienced" },
		{ 1, "Congestion is experienced for one or more ATM AAL5 cells" },
		{ 0, NULL }
	};
	static hf_register_info hfa_cell_header[] = {
		{ &hf_cell_h_vpi	,{"VPI"				,"atm.vpi"
					  ,FT_UINT16	,BASE_DEC	,NULL           ,0
					  ,NULL						,HFILL }},

		{ &hf_cell_h_vci	,{"VCI"				,"atm.vci"
					  ,FT_UINT16	,BASE_DEC	,NULL		,0
					  ,NULL						,HFILL }},

		{ &hf_cell_h_pti	,{"Payload Type"		,"atm.pti"
					  ,FT_UINT8	,BASE_DEC	,VALS(atm_pt_vals),0x0e
					  ,"The 3-bit Payload Type Identifier (PTI) incorporates ATM Layer"
					  " PTI coding of the cell.  These bits are set to the value of the"
					  " PTI of the encapsulated ATM cell."
					  ,HFILL }},

		{ &hf_cell_h_clp	,{"Cell Loss Priority"		,"atm.clp"
					  ,FT_UINT8	,BASE_DEC	,VALS(clp_vals)	,0x01
					  ,"The Cell Loss Priority (CLP) field indicates CLP value"
					  " of the encapsulated cell."
					  ,HFILL }},

		{ &hf_cell_h_m		,{"Transport Mode"		,"atm.pw_control_byte.m"
					  ,FT_UINT8	,BASE_DEC	,VALS(m_vals)	,0x80
					  ,"Bit (M) of the control byte indicates  whether the packet"
					  " contains an ATM cell or a frame payload. If set to 0,"
					  " the packet contains an ATM cell. If set to 1, the PDU"
					  " contains an AAL5 payload."
					  ,HFILL }},

		{ &hf_cell_h_v		,{"VCI Present"			,"atm.pw_control_byte.v"
					  ,FT_BOOLEAN	,8		,TFS(&tfs_yes_no),0x40
					  ,"Bit (V) of the control byte indicates whether the VCI field"
					  " is present in the packet. If set to 1, the VCI field is present"
					  " for the cell. If set to 0, no VCI field is present."
					  ,HFILL }},

		{ &hf_cell_h_rsv	,{"Reserved bits"		,"atm.pw_control_byte.rsv"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0x30
					  ,"The reserved bits should be set to 0 at the transmitter and"
					  " ignored upon reception."
					  ,HFILL }},

		{ &hf_aal5_pdu_rsv	,{"Reserved bits"		,"atm.pw_control_byte.rsv"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0x38
					  ,"The reserved bits should be set to 0 at the transmitter and"
					  " ignored upon reception."
					  ,HFILL }},

		{ &hf_aal5_pdu_u	,{"U bit"			,"atm.pw_control_byte.u"
					  ,FT_UINT8	,BASE_DEC	,VALS(u_vals)	,0x04
					  ,"Indicates whether this frame contains the last cell of"
					  " an AAL5 PDU and represents the value of the ATM User-to-User"
					  " bit for the last ATM cell of the PSN frame. Note: The ATM"
					  " User-to-User bit is the least significant bit of the PTI"
					  " in the ATM header."
					  ,HFILL }},

		{ &hf_aal5_pdu_e	,{"EFCI"			,"atm.pw_control_byte.efci"
					  ,FT_UINT8	,BASE_DEC	,VALS(e_vals)	,0x02
					  ,"EFCI is set to the EFCI state of the last cell of the"
					  " AAL5 PDU or AAL5 fragment. Note: The EFCI state is"
					  " indicated in the middle bit of each ATM cell's PTI."
					  ,HFILL }}
	};
	static hf_register_info hfa_cell[] = {
		{&hf_cell_payload_len	,{"Length"			,"atm.cell.len"
					  ,FT_INT32	,BASE_DEC	,NULL		,0
					  ,NULL						,HFILL }}
	};

	static hf_register_info hfa_n1_nocw[] = {
		{ &hf_n1_nocw_ncells,
		  { "Number of good N:1 no CW encapsulated cells", "pw.atm.n1_nocw.cells", FT_INT32,
		    BASE_DEC, NULL, 0, NULL, HFILL }},
		{ &hf_pw_type_n1_nocw,
		  { "N:1 noCW", "pw.type.atm.n1nocw", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }}
	};

	static hf_register_info hfa_n1_cw[] = {
		{ &hf_n1_cw_ncells,
		  { "Number of good N:1 CW encapsulated cells", "pw.atm.n1_cw.cells", FT_INT32,
		    BASE_DEC, NULL, 0, NULL, HFILL }},
		{ &hf_pw_type_n1_cw,
		  { "N:1 CW", "pw.type.atm.n1cw", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }}
	};

	static hf_register_info hfa_11_aal5pdu[] = {
		{ &hf_11_ncells,
		  { "Number of good 1:1 encapsulated cells", "pw.atm.11.cells", FT_INT32,
		    BASE_DEC, NULL, 0, NULL, HFILL }},
		{ &hf_pw_type_11_vcc,
		  { "1:1 VCC", "pw.type.atm.11vcc", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }},
		{ &hf_pw_type_11_vpc,
		  { "1:1 VPC", "pw.type.atm.11vpc", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }},
		{ &hf_pw_type_aal5_pdu,
		  { "AAL5 PDU", "pw.type.atm.aal5pdu", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }}
	};

	static hf_register_info hfa_aal5_sdu[] = {
		{ &hf_pw_type_aal5_sdu,
		  { "AAL5 SDU", "pw.type.atm.aal5sdu", FT_BOOLEAN, 0, NULL, 0x0,
		    NULL, HFILL }}
	};

	static const value_string a5s_t_vals[] = {
		{ 0, "AAL5 payload" },
		{ 1, "ATM admin cell" },
		{ 0, NULL }
	};

	static const value_string a5s_e_vals[] = {
		{ 0, "No congestion" },
		{ 1, "Congestion experienced" },
		{ 0, NULL }
	};

	static hf_register_info hfa_cw[] = {
		{ &hf_cw_bits03		,{"Bits 0 to 3"			,"pw.cw.bits03"
					  ,FT_UINT8	,BASE_HEX	,NULL		,0xf0
					  ,NULL						,HFILL }},

		{ &hf_pref_cw_flags	,{"Flags"			,"pw.cw.flags"
					  ,FT_UINT8	,BASE_HEX	,NULL		,0x0f
					  ,NULL							,HFILL }},

		{ &hf_pref_cw_a5s_t	,{"Payload type"		,"atm.pt"
					  ,FT_UINT8	,BASE_DEC	,VALS(a5s_t_vals),0x08
					  ,"Bit (T) of the control word indicates whether the packet contains"
					  " an ATM admin cell or an AAL5 payload. If T = 1, the packet"
					  " contains an ATM admin cell, encapsulated according to the N:1"
					  " cell relay encapsulation. If not set, the PDU"
					  " contains an AAL5 payload."
					  ,HFILL }},

		{ &hf_pref_cw_a5s_e	,{"EFCI bit"			,"atm.efci"
					  ,FT_UINT8	,BASE_DEC	,VALS(a5s_e_vals),0x04
					  ,"The ingress router sets this bit to 1 if the EFCI bit"
					  " of the final cell of those that transported the AAL5 CPCS-SDU is"
					  " set to 1, or if the EFCI bit of the single ATM cell to be"
					  " transported in the packet is set to 1. Otherwise, this bit"
					  " is set to 0."
					  ,HFILL }},

		{ &hf_pref_cw_a5s_c	,{"CLP bit"			,"atm.clp"
					  ,FT_UINT8	,BASE_DEC	,VALS(clp_vals)	,0x02
					  ,"The ingress router sets this bit to 1 if the CLP bit"
					  " of any of the ATM cells that transported the AAL5 CPCS-SDU is set"
					  " to 1, or if the CLP bit of the single ATM cell to be transported"
					  " in the packet is set to 1. Otherwise this bit is set to 0."
					  ,HFILL }},

		{ &hf_pref_cw_a5s_u	,{"U bit (Command/Response)"	,"pw.cw.aal5sdu.u"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0x01
					  ,"When FRF.8.1 Frame Relay/ATM PVC Service Interworking [RFC3916]"
					  " traffic is being transported, the Least-Significant Bit of CPCS-UU"
					  " of the AAL5 CPCS-PDU may contain the Frame Relay C/R bit."
					  " The ingress router copies this bit here."
					  ,HFILL }},

		{ &hf_pref_cw_rsv	,{"Reserved bits"		,"pw.cw.rsv"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0xc0
					  ,NULL						,HFILL }},

		{ &hf_generic_cw_rsv	,{"Reserved bits"		,"pw.cw.rsv"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0x0f
					  ,NULL						,HFILL }},

		{ &hf_pref_cw_len	,{"Length"			,"pw.cw.length"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0x3f
					  ,NULL						,HFILL }},

		{ &hf_pref_cw_rsvlen	,{"Length (extended)"		,"pw.cw.length"
					  ,FT_UINT8	,BASE_DEC	,NULL		,0xff
					  ,NULL						,HFILL }},

		{ &hf_cw_seq		,{"Sequence number"		,"pw.cw.seqno"
					  ,FT_UINT16	,BASE_DEC	,NULL		,0
					  ,NULL						,HFILL }},

		{ &hf_gen_cw_atmbyte	,{"ATM-specific byte"		,"pw.cw.3rd_byte"
					  ,FT_UINT8	,BASE_HEX	,NULL		,0xFF
					  ,NULL						,HFILL }}
	};
	static gint *ett_array[] = {
		&ett_encaps
		,&ett_cw
		,&ett_cell_header
		,&ett_cell
	};
	static ei_register_info ei[] = {
		{ &ei_pw_packet_size_too_small, { "pw.packet_size_too_small", PI_MALFORMED, PI_ERROR, "PW packet size too small", EXPFILL }},
		{ &ei_pw_payload_size_invalid_error, { "pw.payload.size_invalid", PI_MALFORMED, PI_ERROR, "PW payload size invalid", EXPFILL }},
		{ &ei_cell_broken, { "atm.cell_broken", PI_MALFORMED, PI_ERROR, "PW ATM cell is broken", EXPFILL }},
		{ &ei_pw_payload_size_invalid_note, { "pw.payload.size_invalid", PI_MALFORMED, PI_NOTE, "PW payload size invalid", EXPFILL }},
		{ &ei_cw_bits03, { "pw.cw.bits03.not_zero", PI_MALFORMED, PI_ERROR, "Bits 0..3 of Control Word must be 0", EXPFILL }},
		{ &ei_pref_cw_flags, { "pw.cw.flags.not_zero", PI_MALFORMED, PI_ERROR, "Flags must be 0 for PW ATM N:1 encapsulation", EXPFILL }},
		{ &ei_pref_cw_len, { "pw.cw.length.invalid", PI_MALFORMED, PI_ERROR, "Bad Length: must be 0 for this encapsulation", EXPFILL }},
		{ &ei_cell_h_pti_undecoded, { "atm.pti.invalid", PI_UNDECODED, PI_WARN, "Unknown value of PTI field in the ATM cell header", EXPFILL }},
		{ &ei_cell_h_pti_malformed, { "atm.pti.invalid", PI_MALFORMED, PI_ERROR, "ATM admin cell is transerred. PTI field should be 4, 5 or 6.", EXPFILL }},
		{ &ei_cell_h_m, { "atm.pw_control_byte.m.not_zero", PI_MALFORMED, PI_ERROR, "1:1 mode: M bit must be 0 to distinguish from AAL5 PDU mode", EXPFILL }},
		{ &ei_cell_h_v_not_zero, { "atm.pw_control_byte.v.not_one", PI_MALFORMED, PI_ERROR, "1:1 VPC mode: V bit must be 1 to indicate that VCI is present", EXPFILL }},
		{ &ei_cell_h_v_not_one, { "atm.pw_control_byte.v.not_zero", PI_MALFORMED, PI_ERROR, "1:1 VCC mode: V bit must be 0 to indicate that VCI is absent", EXPFILL }},
		{ &ei_cell_h_rsv, { "atm.pw_control_byte.rsv.not_zero", PI_MALFORMED, PI_ERROR, "Reserved bits in the 3rd byte of CW must be 0", EXPFILL }},
		{ &ei_gen_cw_atmbyte, { "pw.cw.atmbyte", PI_PROTOCOL, PI_NOTE, "ATM-specific byte of CW is fully dissected below", EXPFILL }},
	};

	expert_module_t* expert_cell;

	proto_n1_cw =
		proto_register_protocol(pwc_longname_pw_atm_n1_cw
					,shortname_n1_cw
					,"mplspwatmn1cw");
	proto_11_or_aal5_pdu =
		proto_register_protocol(pwc_longname_pw_atm_11_or_aal5_pdu
					,shortname_11_or_aal5_pdu
					,"mplspwatm11_or_aal5pdu");
	proto_aal5_sdu =
		proto_register_protocol(pwc_longname_pw_atm_aal5_sdu
					,shortname_aal5_sdu
					,"mplspwatmaal5sdu");
	proto_n1_nocw =
		proto_register_protocol(pwc_longname_pw_atm_n1_nocw
					,shortname_n1_nocw
					,"mplspwatmn1nocw");
	proto_control_word =
		proto_register_protocol("MPLS PW ATM Control Word"
					,"MPLS PW ATM Control Word"
					,"mplspwatmcontrolword");
	proto_cell_header =
		proto_register_protocol("MPLS PW ATM Cell Header"
					,"MPLS PW ATM Cell Header"
					,"mplspwatmcellheader");
	proto_cell =
		proto_register_protocol("ATM Cell"
					,"ATM Cell"
					,"mplspwatmcell");

	proto_register_field_array( proto_cell	 		,hfa_cell	,array_length(hfa_cell));
	expert_cell = expert_register_protocol(proto_cell);
	expert_register_field_array(expert_cell, ei, array_length(ei));

	proto_register_field_array( proto_cell_header		,hfa_cell_header,array_length(hfa_cell_header));
	proto_register_field_array( proto_control_word		,hfa_cw		,array_length(hfa_cw));
	proto_register_field_array( proto_n1_nocw		,hfa_n1_nocw	,array_length(hfa_n1_nocw));
	proto_register_field_array( proto_n1_cw			,hfa_n1_cw	,array_length(hfa_n1_cw));
	proto_register_field_array( proto_11_or_aal5_pdu	,hfa_11_aal5pdu	,array_length(hfa_11_aal5pdu));
	proto_register_field_array( proto_aal5_sdu		,hfa_aal5_sdu	,array_length(hfa_aal5_sdu));

	proto_register_subtree_array(ett_array, array_length(ett_array));

	dh_control_word = register_dissector("mpls_pw_atm_control_word"	,dissect_control_word	,proto_control_word);
	dh_cell = register_dissector("mpls_pw_atm_cell"	,dissect_cell		,proto_cell);
	dh_cell_header = register_dissector("mpls_pw_atm_cell_header",dissect_cell_header	,proto_cell_header);
	register_dissector("mpls_pw_atm_aal5_sdu"	,dissect_aal5_sdu	,proto_aal5_sdu);
	register_dissector("mpls_pw_atm_11_or_aal5_pdu"	,dissect_11_or_aal5_pdu	,proto_11_or_aal5_pdu);
	register_dissector("mpls_pw_atm_n1_cw"		,dissect_n1_cw		,proto_n1_cw);
	register_dissector("mpls_pw_atm_n1_nocw"	,dissect_n1_nocw	,proto_n1_nocw);
	{
		static const char description_allow_cw_length_nonzero[] =
			"Enable to allow non-zero Length in Control Word."
			" This may be needed to correctly decode traffic from some legacy devices"
			" which generate non-zero Length even if there is no padding in the packet."
			" Note that Length should have proper value (dissector checks this anyway)."
			"\n\n"
			"Disable to blame all packets with CW.Length <> 0. This conforms to RFC4717."
			;
		static const char description_extend_cw_length_with_rsvd[] =
			"Enable to use reserved bits (8..9) of Control Word as an extension of CW.Length."
			" This may be needed to correctly decode traffic from some legacy devices"
			" which uses reserved bits as extension of Length"
			"\n\n"
			"Disable to blame all packets with CW.Reserved <> 0. This conforms to RFC4717."
			;
		module_t * module_n1_cw;
		module_t * module_aal5_sdu;

		module_n1_cw = prefs_register_protocol(proto_n1_cw,NULL);
		prefs_register_bool_preference(module_n1_cw
			,"allow_cw_length_nonzero"
			,"Allow CW.Length <> 0"
			,&description_allow_cw_length_nonzero[0]
			,&pref_n1_cw_allow_cw_length_nonzero);
		prefs_register_bool_preference(module_n1_cw
			,"extend_cw_length_with_rsvd"
			,"Use CW.Reserved as extension of CW.Length"
			,&description_extend_cw_length_with_rsvd[0]
			,&pref_n1_cw_extend_cw_length_with_rsvd);

		module_aal5_sdu = prefs_register_protocol(proto_aal5_sdu,NULL);
		prefs_register_bool_preference(module_aal5_sdu
			,"allow_cw_length_nonzero_aal5"
			,"Allow CW.Length <> 0"
			,&description_allow_cw_length_nonzero[0]
			,&pref_aal5_sdu_allow_cw_length_nonzero);
		prefs_register_bool_preference(module_aal5_sdu
			,"extend_cw_length_with_rsvd_aal5"
			,"Use CW.Reserved as extension of CW.Length"
			,&description_extend_cw_length_with_rsvd[0]
			,&pref_aal5_sdu_extend_cw_length_with_rsvd);
	}
}


void
proto_reg_handoff_pw_atm_ata(void)
{
	dissector_handle_t h;
	h = find_dissector("mpls_pw_atm_n1_cw");
	dissector_add_for_decode_as( "mpls.label", h );
	h = find_dissector("mpls_pw_atm_n1_nocw");
	dissector_add_for_decode_as( "mpls.label", h );
	h = find_dissector("mpls_pw_atm_11_or_aal5_pdu");
	dissector_add_for_decode_as( "mpls.label", h );
	h = find_dissector("mpls_pw_atm_aal5_sdu");
	dissector_add_for_decode_as( "mpls.label", h );

	dh_atm_truncated   = find_dissector("atm_pw_truncated");
	dh_atm_untruncated = find_dissector("atm_pw_untruncated");
	dh_atm_oam_cell	   = find_dissector("atm_pw_oam_cell");
	dh_padding	   = find_dissector("pw_padding");
}

/*
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
 * :indentSize=8:tabSize=8:noTabs=false:
 */
