/* packet-jxta.c
 * Routines for JXTA packet dissection
 * Copyright 2004, Mike Duigou <bondolo@jxta.org>
 * Heavily based on packet-jabber.c, which in turn is heavily based on 
 * on packet-acap.c, which in turn is heavily based on 
 * packet-imap.c, Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
 *
 * $Id: packet-jxta.c 13904 2005-03-25 22:37:58Z sahlberg $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * Copyright 1998 Gerald Combs
 *
 * Copied from packet-pop.c, packet-jabber.c, packet-udp.c
 *
 * JXTA specification from http://spec.jxta.org
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

#include <string.h>
#include <glib.h>
#include <epan/packet.h>
#include <epan/conversation.h>
#include <epan/strutil.h>
#include <epan/prefs.h>
#include "packet-tcp.h"

static const char JXTA_UDP_SIG[] = { 'J', 'X', 'T', 'A' };
static const char JXTA_MSG_SIG[] = { 'j', 'x', 'm', 'g' };
static const char JXTA_MSGELEM_SIG[] = { 'j', 'x', 'e', 'l' };

static const char JXTA_WELCOME_MSG_SIG[] = { 'J', 'X', 'T', 'A', 'H', 'E', 'L', 'L', 'O', ' ' };

static int proto_jxta = -1;
static int proto_message_jxta = -1;

static dissector_table_t media_type_dissector_table=NULL;
static dissector_handle_t tcp_jxta_handle=NULL;

static int hf_jxta_udp = -1;
static int hf_jxta_udpsig = -1;
static int hf_jxta_welcome = -1;
static int hf_jxta_framing = -1;
static int hf_jxta_framing_header = -1;
static int hf_jxta_framing_header_name = -1;
static int hf_jxta_framing_header_value_length = -1;
static int hf_jxta_framing_header_value = -1;
static int hf_jxta_message = -1;
static int hf_jxta_message_sig = -1;
static int hf_jxta_message_version = -1;
static int hf_jxta_message_namespaces_count = -1;
static int hf_jxta_message_namespace_name = -1;
static int hf_jxta_message_element_count = -1;
static int hf_jxta_element = -1;
static int hf_jxta_element_sig = -1;
static int hf_jxta_element_namespaceid = -1;
static int hf_jxta_element_flags = -1;
static int hf_jxta_element_flag_hasType = -1;
static int hf_jxta_element_flag_hasEncoding = -1;
static int hf_jxta_element_flag_hasSignature = -1;
static int hf_jxta_element_name = -1;
static int hf_jxta_element_type = -1;
static int hf_jxta_element_encoding = -1;
static int hf_jxta_element_content_len = -1;
static int hf_jxta_element_content = -1;
static int hf_jxta_element_content_text = -1;

/** our header fields */
static hf_register_info hf[] = {
    {&hf_jxta_udp,
     {"JXTA UDP Message", "jxta.udp", FT_NONE, BASE_NONE, NULL, 0x0,
      "JXTA UDP Message", HFILL}
     },
    {&hf_jxta_udpsig,
     {"Signature", "jxta.udpsig", FT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA UDP Signature", HFILL}
     },
    {&hf_jxta_welcome,
     {"Welcome Message", "jxta.welcome", FT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Connection Welcome Message", HFILL}
     },
    {&hf_jxta_framing,
     {"JXTA Message Framing", "jxta.framing", FT_NONE, BASE_NONE, NULL, 0x0,
      "JXTA Message Framing Headers", HFILL}
     },
    {&hf_jxta_framing_header,
     {"Header", "jxta.framing.header", FT_NONE, BASE_NONE, NULL, 0x0,
      "JXTA Message Framing Header", HFILL}
     },
    {&hf_jxta_framing_header_name,
     {"Name", "jxta.framing.header.name", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Framing Header Name", HFILL}
     },
    {&hf_jxta_framing_header_value_length,
     {"Value Length", "jxta.framing.header.valuelen", FT_UINT16, BASE_DEC, NULL, 0x0,
      "JXTA Message Framing Header Value Length", HFILL}
     },
    {&hf_jxta_framing_header_value,
     {"Value", "jxta.framing.header.value", FT_BYTES, BASE_HEX, NULL, 0x0,
      "JXTA Message Framing Header Value", HFILL}
     },
    {&hf_jxta_message,
     {"JXTA Message", "jxta.message", FT_NONE, BASE_NONE, NULL, 0x0,
      "JXTA Message", HFILL}
     },
    {&hf_jxta_message_sig,
     {"Signature", "jxta.message.signature", FT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Signature", HFILL}
     },
    {&hf_jxta_message_version,
     {"Version", "jxta.message.version", FT_UINT8, BASE_DEC, NULL, 0x0,
      "JXTA Message Version", HFILL}
     },
    {&hf_jxta_message_namespaces_count,
     {"Namespace Count", "jxta.message.namespaces", FT_UINT16, BASE_DEC, NULL, 0x0,
      "JXTA Message Namespaces", HFILL}
     },
    {&hf_jxta_message_namespace_name,
     {"Namespace Name", "jxta.message.namespace.name", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Namespace Name", HFILL}
     },
    {&hf_jxta_message_element_count,
     {"Element Count", "jxta.message.elements", FT_UINT16, BASE_DEC, NULL, 0x0,
      "JXTA Message Element Count", HFILL}
     },
    {&hf_jxta_element,
     {"JXTA Message Element", "jxta.message.element", FT_NONE, BASE_NONE, NULL, 0x0,
      "JXTA Message Element", HFILL}
     },
    {&hf_jxta_element_sig,
     {"Signature", "jxta.message.element.signature", FT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Element Signature", HFILL}
     },
    {&hf_jxta_element_namespaceid,
     {"Namespace ID", "jxta.message.element.namespaceid", FT_UINT8, BASE_DEC, NULL, 0x0,
      "JXTA Message Element Namespace ID", HFILL}
     },
    {&hf_jxta_element_flags,
     {"Flags", "jxta.message.element.flags", FT_UINT8, BASE_HEX, NULL, 0x0,
      "JXTA Message Element Flags", HFILL}
     },
    {&hf_jxta_element_flag_hasType,
     {"hasType", "jxta.message.element.flags.hasType", FT_BOOLEAN, 3, TFS(&flags_set_truth), 0x01,
      "JXTA Message Element Flag -- hasType", HFILL}
     },
    {&hf_jxta_element_flag_hasEncoding,
     {"hasEncoding", "jxta.message.element.flags.hasEncoding", FT_BOOLEAN, 3, TFS(&flags_set_truth), 0x02,
      "JXTA Message Element Flag -- hasEncoding", HFILL}
     },
    {&hf_jxta_element_flag_hasSignature,
     {"hasSignature", "jxta.message.element.flags.hasSignature", FT_BOOLEAN, 3, TFS(&flags_set_truth), 0x04,
      "JXTA Message Element Flag -- hasSignature", HFILL}
     },
    {&hf_jxta_element_name,
     {"Element Name", "jxta.message.element.name", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Element Name", HFILL}
     },
    {&hf_jxta_element_type,
     {"Element Type", "jxta.message.element.type", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Element Name", HFILL}
     },
    {&hf_jxta_element_encoding,
     {"Element Type", "jxta.message.element.encoding", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      "JXTA Message Element Encoding", HFILL}
     },
    {&hf_jxta_element_content_text,
     {"Element Content", "jxta.message.element.text_content", FT_UINT_STRING, BASE_HEX, NULL, 0x0,
      "JXTA Message Element Text Content", HFILL}
     },
    {&hf_jxta_element_content_len,
     {"Element Content Length", "jxta.message.element.content.length", FT_UINT32, BASE_DEC, NULL, 0x0,
      "JXTA Message Element Content Length", HFILL}
     },
    {&hf_jxta_element_content,
     {"Element Content", "jxta.message.element.content", FT_BYTES, BASE_HEX, NULL, 0x0,
      "JXTA Message Element Content", HFILL}
     },
};

/**
*    JXTA Protocol subtree handles
**/
static gint ett_jxta_welcome = -1;
static gint ett_jxta_udp = -1;
static gint ett_jxta_framing = -1;
static gint ett_jxta_framing_header = -1;
static gint ett_jxta_msg = -1;
static gint ett_jxta_elem = -1;
static gint ett_jxta_elem_flags = -1;

/** 
*   JXTA Protocol subtree array
**/
static gint *const ett[] = {
    &ett_jxta_welcome,
    &ett_jxta_udp,
    &ett_jxta_framing,
    &ett_jxta_framing_header,
    &ett_jxta_msg,
    &ett_jxta_elem,
    &ett_jxta_elem_flags
};

/** 
*   global preferences
**/
static gboolean gDESEGMENT = TRUE;
static gboolean gUDP_HEUR = FALSE;
static gboolean gTCP_HEUR = FALSE;

/**
*   Prototypes
**/
static gboolean dissect_jxta_UDP_heur(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);
static gboolean dissect_jxta_TCP_heur(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);

static int dissect_jxta_udp(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);
static int dissect_jxta_tcp(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);

static int dissect_jxta_welcome(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);
static int dissect_jxta_message_framing(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, guint64 * content_length,
                                        char **content_type);
static int dissect_jxta_message(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree);
static int dissect_jxta_message_element(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, int ns_count,
                                        const char **namespaces);

void proto_reg_handoff_jxta(void);
void proto_reg_handoff_message_jxta(void);

/**
*   Heuristically dissect a tvbuff containing a JXTA UDP Message
*
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return TRUE if the tvb contained JXTA data which was dissected otherwise FALSE
**/
static gboolean dissect_jxta_UDP_heur(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    /* This is a heuristic dissector, which means we get all the UDP
     * traffic not sent to a known dissector and not claimed by
     * a heuristic dissector called before us!
     */

    if (!gUDP_HEUR)
        return FALSE;

    if (tvb_memeql(tvb, 0, JXTA_UDP_SIG, sizeof(JXTA_UDP_SIG)) != 0) {
        return FALSE;
    }

    return dissect_jxta_udp(tvb, pinfo, tree) > 0;
}

/**
*   Heuristically dissect a tvbuff containing a JXTA TCP Stream
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return TRUE if the tvb contained JXTA data which was dissected otherwise FALSE
**/
static gboolean dissect_jxta_TCP_heur(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    /* This is a heuristic dissector, which means we get all the TCP
     * traffic not sent to a known dissector and not claimed by
     * a heuristic dissector called before us!
     */

    if (!gTCP_HEUR)
        return FALSE;

    if (tvb_memeql(tvb, 0, JXTA_WELCOME_MSG_SIG, sizeof(JXTA_WELCOME_MSG_SIG)) != 0) {
        return FALSE;
    }

    return dissect_jxta_tcp(tvb, pinfo, tree) > 0;
}

/**
*   Dissect a tvbuff containing a JXTA UDP header, JXTA Message framing and a JXTA Message
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_udp(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    int offset = 0;
    int available;
    int needed = 0;

    conversation_t *conversation =
        find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);

    if (conversation == NULL) {
        /*
         * No conversation exists yet - create one.
         */
        conversation =
            conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
    }

    g_assert(find_dissector("jxta.udp"));

    conversation_set_dissector(conversation, find_dissector("jxta.udp"));

    while (TRUE) {
        tvbuff_t *jxta_message_framing_tvb;
        int processed = 0;
        guint64 content_length = -1;

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(JXTA_UDP_SIG)) {
            needed = available - sizeof(JXTA_UDP_SIG);
            break;
        }

        if (tvb_memeql(tvb, offset, JXTA_UDP_SIG, sizeof(JXTA_UDP_SIG)) != 0) {
            /* not ours */
            return 0;
        }

        offset += sizeof(JXTA_UDP_SIG);

        jxta_message_framing_tvb = tvb_new_subset(tvb, offset, -1, -1);
        processed = dissect_jxta_message_framing(jxta_message_framing_tvb, pinfo, NULL, &content_length, NULL);

        if (0 == processed) {
            /* rejected! */
            return 0;
        }

        if (processed < 0) {
            needed = -processed;
            break;
        }

        offset += processed;

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)content_length) {
            needed = available - (int)content_length;
            break;
        }

        offset += (int)content_length;

        break;
    }

    if ((needed > 0) && gDESEGMENT && pinfo->can_desegment) {
        pinfo->desegment_offset = 0;
        pinfo->desegment_len = needed;
        return -needed;
    }

    if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "JXTA");
    }

    if (check_col(pinfo->cinfo, COL_INFO)) {
        col_set_str(pinfo->cinfo, COL_INFO, "UDP Message");
    }

    col_set_writable(pinfo->cinfo, FALSE);

    if (tree) {
        int tree_offset = 0;
        proto_item *jxta_udp_tree_item = proto_tree_add_item(tree, hf_jxta_udp, tvb, tree_offset, -1, FALSE);
        proto_tree *jxta_udp_tree = proto_item_add_subtree(jxta_udp_tree_item, ett_jxta_udp);
        tvbuff_t *jxta_message_framing_tvb;
        guint64 content_length = -1;
        char *content_type = NULL;
        tvbuff_t *jxta_message_tvb;
        gboolean dissected = FALSE;
        int processed = 0;


        proto_tree_add_item(jxta_udp_tree, hf_jxta_udpsig, tvb, tree_offset, sizeof(JXTA_UDP_SIG), FALSE);
        tree_offset += sizeof(JXTA_UDP_SIG);

        jxta_message_framing_tvb = tvb_new_subset(tvb, tree_offset, -1, -1);
        processed = dissect_jxta_message_framing(jxta_message_framing_tvb, pinfo, jxta_udp_tree, &content_length, &content_type);
        tree_offset += processed;

        jxta_message_tvb = tvb_new_subset(tvb, tree_offset, (gint) content_length, (gint) content_length);
        dissected = dissector_try_string(media_type_dissector_table, content_type, jxta_message_tvb, pinfo, jxta_udp_tree);
        g_free(content_type);

        if (!dissected) {
            dissector_handle_t data_handle = find_dissector("data");
            call_dissector(data_handle, jxta_message_tvb, pinfo, jxta_udp_tree);
        }

        tree_offset += (int)content_length;

        proto_item_set_end(jxta_udp_tree_item, tvb, tree_offset);

        g_assert(offset == tree_offset);
    }

    return offset;
}

/**
*   Dissect a tvbuff containing JXTA TCP PDUs
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_tcp(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    int offset = 0;
    int available = 0;
    int processed;
    gboolean first = TRUE;
    conversation_t *conversation =
        find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);

    if (conversation == NULL) {
        /*
         * No conversation exists yet - create one.
         */
        conversation =
            conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
    }

    conversation_set_dissector(conversation, tcp_jxta_handle);

    while (tvb_reported_length_remaining(tvb, offset) > 0) {
        if (first) {
            if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
                col_set_str(pinfo->cinfo, COL_PROTOCOL, "JXTA");
            }
        }

        if (0 == tvb_memeql(tvb, 0, JXTA_WELCOME_MSG_SIG, sizeof(JXTA_WELCOME_MSG_SIG))) {
            processed = dissect_jxta_welcome(tvb, pinfo, tree);
        } else {
            guint64 content_length = -1;
            char *content_type = NULL;
            gboolean dissected;

            processed = dissect_jxta_message_framing(tvb, pinfo, tree, &content_length, &content_type);


            if ((processed > 0) && (NULL != content_type) && (-1 != (gint) content_length)) {
                int msg_offset = offset + processed;

                available = tvb_reported_length_remaining(tvb, msg_offset);
                if (available >= (int)content_length) {
                    tvbuff_t *jxta_message_tvb = tvb_new_subset(tvb, msg_offset, (gint)content_length, (gint)content_length);

                    if (check_col(pinfo->cinfo, COL_INFO)) {
                        col_set_str(pinfo->cinfo, COL_INFO, "Message");
                    }

                    col_set_writable(pinfo->cinfo, FALSE);

                    dissected = dissector_try_string(media_type_dissector_table, content_type, jxta_message_tvb, pinfo, tree);

                    if (!dissected) {
                        dissector_handle_t data_handle = find_dissector("data");
                        call_dissector(data_handle, jxta_message_tvb, pinfo, tree);
                    }

                    processed += (int)content_length;
                } else {
                    processed = available - (int)content_length;
                }

                g_free(content_type);
            }
        }

        if (processed <= 0) {
            pinfo->desegment_offset = offset;
            pinfo->desegment_len = -processed;
            return processed;
        }

        offset += processed;

        first = FALSE;
    }

    return offset;
}

/**
*   Dissect a tvbuff containing a JXTA Welcome Message
*
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_welcome(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    guint offset = 0;
    int afterwelcome;
    int first_linelen;
    int available = tvb_reported_length_remaining(tvb, offset);

    if (available < (int)sizeof(JXTA_WELCOME_MSG_SIG)) {
        return available - sizeof(JXTA_WELCOME_MSG_SIG);
    }

    if (0 != tvb_memeql(tvb, 0, JXTA_WELCOME_MSG_SIG, sizeof(JXTA_WELCOME_MSG_SIG))) {
        /* not ours! */
        return 0;
    }

    first_linelen = tvb_find_line_end(tvb, offset, -1, &afterwelcome, gDESEGMENT && pinfo->can_desegment);

    if (-1 == first_linelen) {
        if (available > 4096) {
            /* it's too far too be reasonable */
            return 0;
        } else {
            /* ask for more bytes */
            return -1;
        }
    }

    if (check_col(pinfo->cinfo, COL_INFO)) {
        col_set_str(pinfo->cinfo, COL_INFO, "Welcome Message");
    }

    if (tree) {
        /* FIXME bondolo dissect the welcome line */
        proto_tree_add_item(tree, hf_jxta_welcome, tvb, offset, afterwelcome, FALSE);
    }

    return afterwelcome;
}

/**
*   Dissect a tvbuff containing JXTA Message framing.
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_message_framing(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, guint64 * content_length,
                                        char **content_type)
{
    int offset = 0;
    int available;
    int needed = 0;

    /*
     *   First go around. Make sure all of the bytes are there.
     */
    do {
        guint8 headername_len;
        guint8 headername_offset;
        guint16 headervalue_len;
        guint16 headervalue_offset;

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint8)) {
            needed = available - sizeof(guint8);
            break;
        } else {
            headername_len = tvb_get_guint8(tvb, offset);
            offset += sizeof(guint8);
            headername_offset = offset;

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < headername_len) {
                needed = available - headername_len;
                break;
            }

            if (0 == headername_len) {
                break;
            }
            offset += headername_len;
        }

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint16)) {
            needed = available - sizeof(guint16);
            break;
        } else {
            headervalue_len = tvb_get_ntohs(tvb, offset);
            offset += sizeof(guint16);
            headervalue_offset = offset;

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < headervalue_len) {
                needed = available - headervalue_len;
                break;
            }

            offset += headervalue_len;
        }

        if (content_type && (sizeof("content-type") - 1) == headername_len) {
            if (0 == tvb_strncaseeql(tvb, headername_offset, "content-type", sizeof("content-type") - 1)) {
                *content_type = tvb_get_string(tvb, headervalue_offset, headervalue_len);
            }
        }


        if (content_length && (sizeof(guint64) == headervalue_len) && ((sizeof("content-length") - 1) == headername_len)) {
            if (0 == tvb_strncaseeql(tvb, headername_offset, "content-length", sizeof("content-length") - 1)) {
                *content_length = tvb_get_ntoh64(tvb, headervalue_offset);
            }
        }
    } while (TRUE);

    if ((needed > 0) && gDESEGMENT && pinfo->can_desegment) {
        return -needed;
    }

    /*
     *   Second (optional pass) Now that we are sure that all the bytes are there we update the protocol tree.
     */
    if (tree) {
        int tree_offset = 0;
        proto_item *framing_tree_item = proto_tree_add_item(tree, hf_jxta_framing, tvb, tree_offset, -1, FALSE);
        proto_tree *framing_tree = proto_item_add_subtree(framing_tree_item, ett_jxta_framing);

        /* parse framing headers */
        do {
            guint8 headernamelen = tvb_get_guint8(tvb, tree_offset);
            proto_item *framing_header_tree_item =
                proto_tree_add_item(framing_tree, hf_jxta_framing_header, tvb, tree_offset, -1, FALSE);
            proto_tree *framing_header_tree = proto_item_add_subtree(framing_header_tree_item, ett_jxta_framing_header);

            /*
             *   Put header name into the protocol tree
             */
            proto_tree_add_item(framing_header_tree, hf_jxta_framing_header_name, tvb, tree_offset, 1, headernamelen);

            /*
             *   Append header name into the header protocol item. It's a nice hint so you don't have to reveal all headers.
             */
            if (headernamelen > 0) {
                proto_item_append_text(framing_header_tree_item, " \"%s\"",
                                       tvb_format_text(tvb, tree_offset + sizeof(guint8), headernamelen));
            }

            tree_offset += sizeof(guint8) + headernamelen;

            if (headernamelen > 0) {
                guint16 headervaluelen = tvb_get_ntohs(tvb, tree_offset);

                if (tree) {
                    proto_tree_add_uint(framing_header_tree, hf_jxta_framing_header_value_length, tvb, tree_offset,
                                        sizeof(guint16), headervaluelen);

                /** TODO bondolo Add specific handling for known header types */

                    /*
                     * Put header value into protocol tree.
                     */
                    proto_tree_add_item(framing_header_tree, hf_jxta_framing_header_value, tvb, tree_offset + sizeof(guint16),
                                        headervaluelen, FALSE);
                }

                tree_offset += sizeof(guint16) + headervaluelen;
            }

            proto_item_set_end(framing_header_tree_item, tvb, tree_offset);

            if (0 == headernamelen) {
                break;
            }
        } while (TRUE);

        proto_item_set_end(framing_tree_item, tvb, tree_offset);

        g_assert(offset == tree_offset);
    }

    /* return how many bytes we used up. */
    return offset;
}

/**
*   Dissect a tvbuff containing a JXTA Message.
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_message(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
{
    int offset = 0;
    int available;
    int needed = 0;

    while (TRUE) {
        /* First pass. Make sure all of the bytes we need are available */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(JXTA_MSG_SIG)) {
            needed = available - sizeof(JXTA_MSG_SIG);
            break;
        }

        if (tvb_memeql(tvb, offset, JXTA_MSG_SIG, sizeof(JXTA_MSG_SIG)) != 0) {
            /* It is not one of ours */
            return 0;
        }

        offset += sizeof(JXTA_MSG_SIG);

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint8)) {
            needed = available - sizeof(guint8);
            break;
        } else {
            guint8 message_version = tvb_get_guint8(tvb, offset);

            offset += sizeof(guint8);

            if (0 != message_version) {
                /* Sort of a lie, we say that we don't recognize it at all. */
                return 0;
            }
        }

        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint16)) {
            needed = available - sizeof(guint16);
            break;
        } else {
            guint16 msg_ns_count = tvb_get_ntohs(tvb, offset);
            int each_namespace;

            offset += sizeof(guint16);

            for (each_namespace = 0; each_namespace < msg_ns_count; each_namespace++) {
                guint8 namespace_len;

                available = tvb_reported_length_remaining(tvb, offset);
                if (available < (int)sizeof(guint8)) {
                    needed = available - sizeof(guint8);
                    break;
                }

                namespace_len = tvb_get_guint8(tvb, offset);

                available = tvb_reported_length_remaining(tvb, offset + 1);
                if (available < namespace_len) {
                    needed = available - namespace_len;
                    break;
                }

                offset += sizeof(guint8) + namespace_len;
            }
        }

        /* parse element count */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint16)) {
            needed = available - sizeof(guint16);
            break;
        } else {
            guint16 elem_count = tvb_get_ntohs(tvb, offset);
            int each_elem;

            offset += sizeof(guint16);

            /* parse elements */

            for (each_elem = 0; each_elem < elem_count; each_elem++) {
                tvbuff_t *jxta_message_element_tvb = tvb_new_subset(tvb, offset, -1, -1);
                int processed = dissect_jxta_message_element(jxta_message_element_tvb, pinfo, NULL, 0, NULL);

                if (processed <= 0) {
                    return processed;
                }

                offset += processed;
            }
        }

        break;
    }

    if ((needed > 0) && gDESEGMENT && pinfo->can_desegment) {
        return -needed;
    }

    if (tree) {
        int tree_offset = 0;
        proto_item *jxta_msg_tree_item = proto_tree_add_item(tree, hf_jxta_message, tvb, tree_offset, -1, FALSE);
        proto_tree *jxta_msg_tree = proto_item_add_subtree(jxta_msg_tree_item, ett_jxta_msg);

        guint8 message_version;
        const char **namespaces = NULL;
        guint16 msg_ns_count;
        int each_namespace;
        guint16 elem_count;
        int each_elem;

        proto_tree_add_item(jxta_msg_tree, hf_jxta_message_sig, tvb, tree_offset, sizeof(JXTA_MSG_SIG), FALSE);
        tree_offset += sizeof(JXTA_MSG_SIG);

        message_version = tvb_get_guint8(tvb, tree_offset);
        proto_tree_add_uint(jxta_msg_tree, hf_jxta_message_version, tvb, tree_offset, sizeof(guint8), message_version);
        tree_offset += sizeof(guint8);

        msg_ns_count = tvb_get_ntohs(tvb, tree_offset);
        proto_tree_add_uint(jxta_msg_tree, hf_jxta_message_namespaces_count, tvb, tree_offset, sizeof(guint16), msg_ns_count);
        tree_offset += sizeof(guint16);

        namespaces = g_malloc((msg_ns_count + 2) * sizeof(const char *));
        namespaces[0] = "";
        namespaces[1] = "jxta";

        /* parse namespaces */
        for (each_namespace = 0; each_namespace < msg_ns_count; each_namespace++) {
            guint8 namespace_len = tvb_get_guint8(tvb, tree_offset);

            namespaces[2 + each_namespace] = tvb_get_string(tvb, tree_offset + 1, namespace_len);
            proto_tree_add_item(jxta_msg_tree, hf_jxta_message_namespace_name, tvb, tree_offset, sizeof(guint8), FALSE);
            tree_offset += sizeof(guint8) + namespace_len;
        }

        /* parse element count */
        elem_count = tvb_get_ntohs(tvb, tree_offset);
        proto_tree_add_item(jxta_msg_tree, hf_jxta_message_element_count, tvb, tree_offset, sizeof(guint16), FALSE);
        tree_offset += sizeof(guint16);

        /* parse elements */
        for (each_elem = 0; each_elem < elem_count; each_elem++) {
            tvbuff_t *jxta_message_element_tvb = tvb_new_subset(tvb, tree_offset, -1, -1);

            tree_offset +=
                dissect_jxta_message_element(jxta_message_element_tvb, pinfo, jxta_msg_tree, msg_ns_count + 2, namespaces);
        }

        for (each_namespace = 2; each_namespace < msg_ns_count; each_namespace++) {
            g_free((char *)namespaces[each_namespace]);
        }

        g_free((char *)namespaces);

        proto_item_set_end(jxta_msg_tree_item, tvb, tree_offset);

        g_assert(tree_offset == offset);
    }

    return offset;
}

/**
*   Dissect a tvbuff containing a JXTA Message Element.
*    
*   @param  tvb The buffer to dissect.
*   @param  pinfo Packet Info.
*   @param  tree The protocol tree.
*   @return number of bytes from the tvbuff_t which were processed.
**/
static int dissect_jxta_message_element(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, int ns_count,
                                        const char **namespaces)
{
    int offset = 0;
    int available;
    int needed = 0;
    guint8 flags;

    /* First pass. Make sure all of the bytes we need are available */

    /* signature field */
    while (TRUE) {
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(JXTA_MSGELEM_SIG)) {
            needed = available - sizeof(JXTA_MSGELEM_SIG);
        }

        if (tvb_memeql(tvb, offset, JXTA_MSGELEM_SIG, sizeof(JXTA_MSGELEM_SIG)) != 0) {
            /* It is not one of ours */
            return 0;
        }

        offset += sizeof(JXTA_MSGELEM_SIG);

        /* namespace id field */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint8)) {
            needed = available - sizeof(guint8);
            break;
        }

        offset += sizeof(guint8);

        /* flags field */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint8)) {
            needed = available - sizeof(guint8);
            break;
        } else {
            flags = tvb_get_guint8(tvb, offset);
            offset += sizeof(guint8);
        }

        /* name field */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint16)) {
            needed = available - sizeof(guint16);
            break;
        } else {
            guint16 name_len = tvb_get_ntohs(tvb, offset);
            offset += sizeof(guint16);

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < name_len) {
                needed = available - name_len;
                break;
            }

            offset += name_len;
        }

        /* type field */
        if ((flags & 0x01) != 0) {
            guint16 type_len;

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < (int)sizeof(guint16)) {
                needed = available - sizeof(guint16);
                break;
            }

            type_len = tvb_get_ntohs(tvb, offset);
            offset += sizeof(guint16);

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < type_len) {
                needed = available - type_len;
                break;
            }

            offset += type_len;
        }

        /* encoding field */
        if ((flags & 0x02) != 0) {
            guint16 encoding_len;

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < (int)sizeof(guint16)) {
                needed = available - sizeof(guint16);
                break;
            }

            encoding_len = tvb_get_ntohs(tvb, offset);
            offset += sizeof(guint16);

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < encoding_len) {
                needed = available - encoding_len;
                break;
            }

            offset += encoding_len;
        }

        /* content field */
        available = tvb_reported_length_remaining(tvb, offset);
        if (available < (int)sizeof(guint16)) {
            needed = available - sizeof(guint16);
            break;
        } else {
            guint32 content_len = tvb_get_ntohl(tvb, offset);
            offset += sizeof(guint32);

            available = tvb_reported_length_remaining(tvb, offset);
            if (available < (int)content_len) {
                needed = available - content_len;
                break;
            }

            offset += content_len;
        }

        /* signature element field */
        if ((flags & 0x04) != 0) {
            tvbuff_t *jxta_signature_element_tvb;
            int processed;

            jxta_signature_element_tvb = tvb_new_subset(tvb, offset, -1, -1);

            processed = dissect_jxta_message_element(jxta_signature_element_tvb, pinfo, NULL, 0, NULL);

            if (processed == 0) {
                return offset;
            }

            if (processed < 0) {
                needed = -processed;
                break;
            }

            offset += processed;
        }

        break;
    }

    if ((needed > 0) && gDESEGMENT && pinfo->can_desegment) {
        return -needed;
    }

    /* Second (optional) pass : build the proto tree */
    if (tree) {
        int tree_offset = 0;
        proto_item *jxta_elem_tree_item = proto_tree_add_item(tree, hf_jxta_element, tvb, tree_offset, -1, FALSE);
        proto_tree *jxta_elem_tree = proto_item_add_subtree(jxta_elem_tree_item, ett_jxta_elem);
        guint8 namespaceID;
        proto_item *namespace_ti;
        guint16 name_len;
        proto_item *flags_ti;
        proto_tree *jxta_elem_flags_tree = NULL;
        guint32 content_len;
        gchar *mediatype = NULL;
        gboolean media_type_recognized = FALSE;
        tvbuff_t *element_content_tvb;

        proto_tree_add_item(jxta_elem_tree, hf_jxta_element_sig, tvb, tree_offset, sizeof(JXTA_MSGELEM_SIG), FALSE);
        tree_offset += sizeof(JXTA_MSGELEM_SIG);

        namespaceID = tvb_get_guint8(tvb, tree_offset);
        namespace_ti =
            proto_tree_add_uint(jxta_elem_tree, hf_jxta_element_namespaceid, tvb, tree_offset, sizeof(guint8), namespaceID);
        if (namespaceID < ns_count) {
            proto_item_append_text(namespace_ti, " (%s)", namespaces[namespaceID]);
        } else {
            proto_item_append_text(namespace_ti, " * BAD *");
        }
        tree_offset += sizeof(guint8);

        flags = tvb_get_guint8(tvb, tree_offset);
        flags_ti = proto_tree_add_uint(jxta_elem_tree, hf_jxta_element_flags, tvb, tree_offset, sizeof(guint8), flags);
        jxta_elem_flags_tree = proto_item_add_subtree(flags_ti, ett_jxta_elem_flags);
        proto_tree_add_boolean(jxta_elem_flags_tree, hf_jxta_element_flag_hasType, tvb, tree_offset, 1, flags);
        proto_tree_add_boolean(jxta_elem_flags_tree, hf_jxta_element_flag_hasEncoding, tvb, tree_offset, 1, flags);
        proto_tree_add_boolean(jxta_elem_flags_tree, hf_jxta_element_flag_hasSignature, tvb, tree_offset, 1, flags);
        tree_offset += sizeof(guint8);

        name_len = tvb_get_ntohs(tvb, tree_offset);
        proto_item_append_text(jxta_elem_tree_item, " \"%s\"", tvb_format_text(tvb, tree_offset + sizeof(guint16), name_len));
        proto_tree_add_item(jxta_elem_tree, hf_jxta_element_name, tvb, tree_offset, sizeof(guint16), FALSE);
        tree_offset += sizeof(guint16) + name_len;

        /* process type */
        if ((flags & 0x01) != 0) {
            guint16 type_len = tvb_get_ntohs(tvb, tree_offset);
            proto_tree_add_item(jxta_elem_tree, hf_jxta_element_type, tvb, tree_offset, sizeof(guint16), FALSE);
            tree_offset += sizeof(guint16);

            mediatype = tvb_get_string(tvb, tree_offset, type_len);

            /* remove any params */
            {
                gchar *parms_at = strchr(mediatype, ';');

                if (NULL != parms_at) {
                    *parms_at = '\0';
                }
            }

            /* force to lower case */
#if GLIB_MAJOR_VERSION < 2
            g_strdown(mediatype);
#else
            {
                gchar *mediatype_lowercase = g_ascii_strdown(mediatype, -1);
                g_free(mediatype);
                mediatype = mediatype_lowercase;
            }
#endif
            tree_offset += type_len;
        }

        /* process encoding */
        if ((flags & 0x02) != 0) {
            guint16 encoding_len = tvb_get_ntohs(tvb, tree_offset);
            proto_tree_add_item(jxta_elem_tree, hf_jxta_element_encoding, tvb, tree_offset, sizeof(guint16), FALSE);
            tree_offset += sizeof(guint16) + encoding_len;
        }

        /* content */
        content_len = tvb_get_ntohl(tvb, tree_offset);
        proto_tree_add_item(jxta_elem_tree, hf_jxta_element_content_len, tvb, tree_offset, sizeof(guint32), FALSE);
        tree_offset += sizeof(guint32);

        element_content_tvb = tvb_new_subset(tvb, tree_offset, content_len, content_len);

        if (NULL != mediatype) {
            media_type_recognized = dissector_try_string(media_type_dissector_table,
                                                         mediatype, element_content_tvb, pinfo, jxta_elem_tree);
            g_free(mediatype);
        }

        if (!media_type_recognized) {
            dissector_handle_t data_handle = find_dissector("data");
            call_dissector(data_handle, element_content_tvb, pinfo, jxta_elem_tree);
        }
        tree_offset += content_len;

        if ((flags & 0x04) != 0) {
            tvbuff_t *jxta_message_element_tvb = tvb_new_subset(tvb, tree_offset, -1, -1);

            tree_offset += dissect_jxta_message_element(jxta_message_element_tvb, pinfo, jxta_elem_tree, ns_count, namespaces);
        }

        proto_item_set_end(jxta_elem_tree_item, tvb, tree_offset);

        g_assert(tree_offset == offset);
    }

    return offset;
}

/**
*    Register jxta protocol and jxta message protocol, header fields, subtree types, preferences.
**/
void proto_register_jxta(void)
{
    module_t *jxta_module;

    proto_jxta = proto_register_protocol("JXTA P2P", "JXTA", "jxta");

    proto_message_jxta = proto_register_protocol("Media Type: application/x-jxta-msg",
                                                 "application/x-jxta-msg", "application-x-jxta-msg");

    /* Register header fields */
    proto_register_field_array(proto_jxta, hf, array_length(hf));

    /* Register JXTA Sub-tree */
    proto_register_subtree_array(ett, array_length(ett));

    /* Register preferences */
    jxta_module = prefs_register_protocol(proto_jxta, proto_reg_handoff_jxta);

    prefs_register_bool_preference(jxta_module, "desegment",
                                   "Reassemble JXTA messages spanning multiple UDP/TCP/HTTP segments",
                                   "Whether the JXTA dissector should reassemble messages spanning multiple UDP/TCP segments."
                                   " To use this option you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings "
                                   " and enable \"Reassemble fragmented IP datagrams\" in the IP protocol settings.",
                                   &gDESEGMENT);

    prefs_register_bool_preference(jxta_module, "udp.heuristic", "Try to discover JXTA in UDP datagrams",
                                   "Enable to inspect UDP datagrams for JXTA messages.", &gUDP_HEUR);

    prefs_register_bool_preference(jxta_module, "tcp.heuristic", "Try to discover JXTA in TCP connections",
                                   "Enable to inspect TCP connections for JXTA conversations.", &gTCP_HEUR);
}

void proto_reg_handoff_jxta(void)
{
    static gboolean init_done = FALSE;

    if (!init_done) {
        new_register_dissector("jxta.udp", dissect_jxta_udp, proto_jxta);
        heur_dissector_add("udp", dissect_jxta_UDP_heur, proto_jxta);

        new_register_dissector("jxta.tcp", dissect_jxta_tcp, proto_jxta);
        tcp_jxta_handle = find_dissector("jxta.tcp");
        heur_dissector_add("tcp", dissect_jxta_TCP_heur, proto_jxta);

        init_done = TRUE;
    }
}

void proto_reg_handoff_message_jxta(void)
{
    static gboolean init_done = FALSE;
    static dissector_handle_t message_jxta_handle;

    if (!init_done) {
        message_jxta_handle = new_create_dissector_handle(dissect_jxta_message, proto_message_jxta);

        media_type_dissector_table = find_dissector_table("media_type");

        dissector_add_string("media_type", "application/x-jxta-msg", message_jxta_handle);

        init_done = TRUE;
    }
}
