/* Code to actually dissect the packets */
static void
dissect_ccsds(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	int          offset          = 0;
	proto_item  *ccsds_packet;
	proto_tree  *ccsds_tree      = NULL;
	proto_item  *primary_header  = NULL;
	proto_tree  *primary_header_tree;
	guint16      first_word;
	guint32      coarse_time;
	guint8       fine_time;
	proto_item  *secondary_header;
	proto_tree  *secondary_header_tree;
        const char*  time_string;
	gint         ccsds_length;
	gint         length          = 0;
	gint         reported_length;
	guint8       checkword_flag  = 0;
	gint         counter         = 0;
	proto_item  *item            = NULL;
	proto_tree  *checkword_tree;
	guint16      checkword_field = 0;
	guint16      checkword_sum   = 0;


	col_set_str(pinfo->cinfo, COL_PROTOCOL, "CCSDS");
	col_set_str(pinfo->cinfo, COL_INFO, "CCSDS Packet");

	first_word=tvb_get_ntohs(tvb, 0);
	col_add_fstr(pinfo->cinfo, COL_INFO, "APID %1$4d (0x%1$03X)", first_word&HDR_APID);

	reported_length = tvb_reported_length_remaining(tvb, 0);
	ccsds_length    = tvb_get_ntohs(tvb, 4) + CCSDS_PRIMARY_HEADER_LENGTH + 1;

	if (tree) {
		/* Min length is size of headers, whereas max length is reported length.
		 * If the length field in the CCSDS header is outside of these bounds,
		 * use the value it violates.  Otherwise, use the length field value.
		 */
		if (ccsds_length > reported_length)
			length = reported_length;
		else if (ccsds_length < CCSDS_PRIMARY_HEADER_LENGTH + CCSDS_SECONDARY_HEADER_LENGTH)
			length = CCSDS_PRIMARY_HEADER_LENGTH + CCSDS_SECONDARY_HEADER_LENGTH;
		else
			length = ccsds_length;

		ccsds_packet = proto_tree_add_item(tree, proto_ccsds, tvb, 0, length, ENC_NA);
		ccsds_tree   = proto_item_add_subtree(ccsds_packet, ett_ccsds);

                /* build the ccsds primary header tree */
		primary_header=proto_tree_add_text(ccsds_tree, tvb, offset, CCSDS_PRIMARY_HEADER_LENGTH, "Primary CCSDS Header");
		primary_header_tree=proto_item_add_subtree(primary_header, ett_ccsds_primary_header);

		proto_tree_add_uint(primary_header_tree, hf_ccsds_version, tvb, offset, 2, first_word);
		proto_tree_add_uint(primary_header_tree, hf_ccsds_type, tvb, offset, 2, first_word);
		proto_tree_add_boolean(primary_header_tree, hf_ccsds_secheader, tvb, offset, 2, first_word);
		proto_tree_add_uint(primary_header_tree, hf_ccsds_apid, tvb, offset, 2, first_word);
		offset += 2;

		proto_tree_add_item(primary_header_tree, hf_ccsds_seqflag, tvb, offset, 2, ENC_BIG_ENDIAN);
		proto_tree_add_item(primary_header_tree, hf_ccsds_seqnum, tvb, offset, 2, ENC_BIG_ENDIAN);
		offset += 2;

		item = proto_tree_add_item(primary_header_tree, hf_ccsds_length, tvb, offset, 2, ENC_BIG_ENDIAN);
	}
	if (ccsds_length > reported_length) {
		expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR,
				       "Length field value is greater than the packet seen on the wire");
	}
	if (tree) {
		offset += 2;
		proto_item_set_end(primary_header, tvb, offset);

                /* build the ccsds secondary header tree */
		if ( first_word & HDR_SECHDR )
		{
			secondary_header=proto_tree_add_text(ccsds_tree, tvb, offset, CCSDS_SECONDARY_HEADER_LENGTH, "Secondary CCSDS Header");
			secondary_header_tree=proto_item_add_subtree(secondary_header, ett_ccsds_secondary_header);

                        /* command ccsds secondary header flags */
		        coarse_time=tvb_get_ntohl(tvb, offset);
			proto_tree_add_item(secondary_header_tree, hf_ccsds_coarse_time, tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;

		        fine_time=tvb_get_guint8(tvb, offset);
			proto_tree_add_item(secondary_header_tree, hf_ccsds_fine_time, tvb, offset, 1, ENC_BIG_ENDIAN);
			++offset;

                        time_string = embedded_time_to_string ( coarse_time, fine_time );
                        proto_tree_add_text(secondary_header_tree, tvb, offset-5, 5, "%s = Embedded Time", time_string);

			proto_tree_add_item(secondary_header_tree, hf_ccsds_timeid, tvb, offset, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(secondary_header_tree, hf_ccsds_checkword_flag, tvb, offset, 1, ENC_BIG_ENDIAN);

			/* Global Preference: how to handle checkword flag */
			switch (global_dissect_checkword) {
			   case 0:
			      /* force checkword presence flag to be false */
			      checkword_flag = 0;
			      break;
			   case 1:
			      /* force checkword presence flag to be true */
			      checkword_flag = 1;
			      break;
			   default:
			      /* use value of checkword presence flag from header */
			      checkword_flag = (tvb_get_guint8(tvb, offset)&0x20) >> 5;
			      break;
			}

                        /* payload specific ccsds secondary header flags */
                        if ( first_word & HDR_TYPE )
                        {
		        	proto_tree_add_item(secondary_header_tree, hf_ccsds_zoe, tvb, offset, 1, ENC_BIG_ENDIAN);
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_packet_type_unused, tvb, offset, 1, ENC_BIG_ENDIAN);
			        ++offset;

			        proto_tree_add_item(secondary_header_tree, hf_ccsds_vid, tvb, offset, 2, ENC_BIG_ENDIAN);
			        offset += 2;

			        proto_tree_add_item(secondary_header_tree, hf_ccsds_dcc, tvb, offset, 2, ENC_BIG_ENDIAN);
			        offset += 2;
                        }

                        /* core specific ccsds secondary header flags */
                        else
                        {
		        	/* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare1, tvb, offset, 1, ENC_BIG_ENDIAN); */
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN);
			        ++offset;

			        /* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare2, tvb, offset, 2, ENC_BIG_ENDIAN); */
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_element_id, tvb, offset, 2, ENC_BIG_ENDIAN);
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_cmd_data_packet, tvb, offset, 2, ENC_BIG_ENDIAN);
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_format_version_id, tvb, offset, 2, ENC_BIG_ENDIAN);
			        proto_tree_add_item(secondary_header_tree, hf_ccsds_extended_format_id, tvb, offset, 2, ENC_BIG_ENDIAN);
			        offset += 2;

			        /* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare3, tvb, offset, 1, ENC_BIG_ENDIAN); */
                                ++offset;

			        proto_tree_add_item(secondary_header_tree, hf_ccsds_frame_id, tvb, offset, 1, ENC_BIG_ENDIAN);
			        ++offset;
                        }

                        /* finish the ccsds secondary header */
			proto_item_set_end(secondary_header, tvb, offset);
		}

		/* If there wasn't a full packet, then don't allow a tree item for checkword. */
		if (reported_length < ccsds_length || ccsds_length < CCSDS_PRIMARY_HEADER_LENGTH + CCSDS_SECONDARY_HEADER_LENGTH) {
			/* Label CCSDS payload 'User Data' */
			if (length > offset)
				proto_tree_add_text(ccsds_tree, tvb, offset, length-offset, "User Data");
			offset += length-offset;
			if (checkword_flag == 1)
				proto_tree_add_text(ccsds_tree, tvb, offset, 0, "Packet does not contain a Checkword");
		}
		/*  Handle checkword according to CCSDS preference setting. */
		else {
			/* Label CCSDS payload 'User Data' */
			proto_tree_add_text(ccsds_tree, tvb, offset, length-offset-2*checkword_flag, "User Data");
			offset += length-offset-2*checkword_flag;

			/* If checkword is present, calculate packet checksum (16-bit running sum) for comparison */
			if (checkword_flag == 1) {
				/* don't count the checkword as part of the checksum */
				while (counter < ccsds_length-2) {
					checkword_sum += tvb_get_ntohs(tvb, counter);
					counter += 2;
				}
				checkword_field = tvb_get_ntohs(tvb, offset);

				/* Report checkword status */
				if (checkword_sum == checkword_field) {
					item = proto_tree_add_uint_format(ccsds_tree, hf_ccsds_checkword, tvb, offset, 2, checkword_field,
							"CCSDS checkword: 0x%04x [correct]", checkword_field);
					checkword_tree = proto_item_add_subtree(item, ett_ccsds_checkword);
					item = proto_tree_add_boolean(checkword_tree, hf_ccsds_checkword_good, tvb, offset, 2, TRUE);
					PROTO_ITEM_SET_GENERATED(item);
					item = proto_tree_add_boolean(checkword_tree, hf_ccsds_checkword_bad, tvb, offset, 2, FALSE);
					PROTO_ITEM_SET_GENERATED(item);
				} else {
					item = proto_tree_add_uint_format(ccsds_tree, hf_ccsds_checkword, tvb, offset, 2, checkword_field,
							"CCSDS checkword: 0x%04x [incorrect, should be 0x%04x]", checkword_field, checkword_sum);
					checkword_tree = proto_item_add_subtree(item, ett_ccsds_checkword);
					item = proto_tree_add_boolean(checkword_tree, hf_ccsds_checkword_good, tvb, offset, 2, FALSE);
					PROTO_ITEM_SET_GENERATED(item);
					item = proto_tree_add_boolean(checkword_tree, hf_ccsds_checkword_bad, tvb, offset, 2, TRUE);
					PROTO_ITEM_SET_GENERATED(item);
				}
				offset += 2;
			}
		}

		/* Give the data dissector any bytes past the CCSDS packet length */
		/* (XXX: Not really OK under 'if (tree)' but will work; see packet-data.c */
		call_dissector(data_handle, tvb_new_subset_remaining(tvb, offset), pinfo, tree);

	} /* if(tree) */
/* Code to actually dissect the packets */
static void
dissect_ccsds(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    int          offset          = 0;
    proto_item  *ccsds_packet;
    proto_tree  *ccsds_tree;
    proto_item  *primary_header;
    proto_tree  *primary_header_tree;
    guint16      first_word;
    guint32      coarse_time;
    guint8       fine_time;
    proto_item  *secondary_header;
    proto_tree  *secondary_header_tree;
    const char  *time_string;
    gint         ccsds_length;
    gint         length          = 0;
    gint         reported_length;
    guint8       checkword_flag  = 0;
    gint         counter         = 0;
    proto_item  *item, *checkword_item = NULL;
    proto_tree  *checkword_tree;
    guint16      checkword_field = 0;
    guint16      checkword_sum   = 0;
    tvbuff_t    *next_tvb;
    static const int * header_flags[] = {
        &hf_ccsds_version,
        &hf_ccsds_type,
        &hf_ccsds_secheader,
        &hf_ccsds_apid,
        NULL
    };

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "CCSDS");
    col_set_str(pinfo->cinfo, COL_INFO, "CCSDS Packet");

    first_word = tvb_get_ntohs(tvb, 0);
    col_add_fstr(pinfo->cinfo, COL_INFO, "APID %4d (0x%03X)", first_word&HDR_APID, first_word&HDR_APID);

    reported_length = tvb_reported_length_remaining(tvb, 0);
    ccsds_length    = tvb_get_ntohs(tvb, 4) + CCSDS_PRIMARY_HEADER_LENGTH + 1;


    /* Min length is size of headers, whereas max length is reported length.
     * If the length field in the CCSDS header is outside of these bounds,
     * use the value it violates.  Otherwise, use the length field value.
     */
    if (ccsds_length > reported_length)
        length = reported_length;
    else if (ccsds_length < CCSDS_PRIMARY_HEADER_LENGTH + CCSDS_SECONDARY_HEADER_LENGTH)
        length = CCSDS_PRIMARY_HEADER_LENGTH + CCSDS_SECONDARY_HEADER_LENGTH;
    else
        length = ccsds_length;

    ccsds_packet = proto_tree_add_item(tree, proto_ccsds, tvb, 0, length, ENC_NA);
    ccsds_tree   = proto_item_add_subtree(ccsds_packet, ett_ccsds);

            /* build the ccsds primary header tree */
    primary_header_tree = proto_tree_add_subtree(ccsds_tree, tvb, offset, CCSDS_PRIMARY_HEADER_LENGTH,
                            ett_ccsds_primary_header, &primary_header, "Primary CCSDS Header");

    proto_tree_add_bitmask(primary_header_tree, tvb, offset, hf_ccsds_header_flags,
                    ett_ccsds_primary_header_flags, header_flags, ENC_BIG_ENDIAN);
    offset += 2;

    proto_tree_add_item(primary_header_tree, hf_ccsds_seqflag, tvb, offset, 2, ENC_BIG_ENDIAN);
    proto_tree_add_item(primary_header_tree, hf_ccsds_seqnum, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;

    item = proto_tree_add_item(primary_header_tree, hf_ccsds_length, tvb, offset, 2, ENC_BIG_ENDIAN);

    if (ccsds_length > reported_length) {
        expert_add_info(pinfo, item, &ei_ccsds_length_error);
    }

    offset += 2;
    proto_item_set_end(primary_header, tvb, offset);

    /* build the ccsds secondary header tree */
    if ( first_word & HDR_SECHDR )
    {
        secondary_header_tree = proto_tree_add_subtree(ccsds_tree, tvb, offset, CCSDS_SECONDARY_HEADER_LENGTH,
                        ett_ccsds_secondary_header, &secondary_header, "Secondary CCSDS Header");

                    /* command ccsds secondary header flags */
            coarse_time = tvb_get_ntohl(tvb, offset);
        proto_tree_add_item(secondary_header_tree, hf_ccsds_coarse_time, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;

        fine_time = tvb_get_guint8(tvb, offset);
        proto_tree_add_item(secondary_header_tree, hf_ccsds_fine_time, tvb, offset, 1, ENC_BIG_ENDIAN);
        ++offset;

        time_string = embedded_time_to_string ( coarse_time, fine_time );
        proto_tree_add_string(secondary_header_tree, hf_ccsds_embedded_time, tvb, offset-5, 5, time_string);

        proto_tree_add_item(secondary_header_tree, hf_ccsds_timeid, tvb, offset, 1, ENC_BIG_ENDIAN);
        checkword_item = proto_tree_add_item(secondary_header_tree, hf_ccsds_checkword_flag, tvb, offset, 1, ENC_BIG_ENDIAN);

        /* Global Preference: how to handle checkword flag */
        switch (global_dissect_checkword) {
           case 0:
              /* force checkword presence flag to be false */
              checkword_flag = 0;
              break;
           case 1:
              /* force checkword presence flag to be true */
              checkword_flag = 1;
              break;
           default:
              /* use value of checkword presence flag from header */
              checkword_flag = (tvb_get_guint8(tvb, offset)&0x20) >> 5;
              break;
        }

        /* payload specific ccsds secondary header flags */
        if ( first_word & HDR_TYPE )
        {
            proto_tree_add_item(secondary_header_tree, hf_ccsds_zoe, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(secondary_header_tree, hf_ccsds_packet_type_unused, tvb, offset, 1, ENC_BIG_ENDIAN);
            ++offset;

            proto_tree_add_item(secondary_header_tree, hf_ccsds_vid, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;

            proto_tree_add_item(secondary_header_tree, hf_ccsds_dcc, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
        }

        /* core specific ccsds secondary header flags */
        else
        {
            /* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare1, tvb, offset, 1, ENC_BIG_ENDIAN); */
            proto_tree_add_item(secondary_header_tree, hf_ccsds_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            ++offset;

            /* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare2, tvb, offset, 2, ENC_BIG_ENDIAN); */
            proto_tree_add_item(secondary_header_tree, hf_ccsds_element_id, tvb, offset, 2, ENC_BIG_ENDIAN);
            proto_tree_add_item(secondary_header_tree, hf_ccsds_cmd_data_packet, tvb, offset, 2, ENC_BIG_ENDIAN);
            proto_tree_add_item(secondary_header_tree, hf_ccsds_format_version_id, tvb, offset, 2, ENC_BIG_ENDIAN);
            proto_tree_add_item(secondary_header_tree, hf_ccsds_extended_format_id, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;

            /* proto_tree_add_item(secondary_header_tree, hf_ccsds_spare3, tvb, offset, 1, ENC_BIG_ENDIAN); */
            ++offset;

            proto_tree_add_item(secondary_header_tree, hf_ccsds_frame_id, tvb, offset, 1, ENC_BIG_ENDIAN);
            ++offset;
        }

                    /* finish the ccsds secondary header */
        proto_item_set_end(secondary_header, tvb, offset);
    }