C++程序  |  1533行  |  47.95 KB

/* packet-spdy.c
 * Routines for SPDY packet disassembly
 * For now, the protocol spec can be found at
 * http://dev.chromium.org/spdy/spdy-protocol
 *
 * Copyright 2010, Google Inc.
 * Eric Shienbrood <ers@google.com>
 *
 * $Id$
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * Originally based on packet-http.c
 *
 * 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 <string.h>
#include <ctype.h>

#include <glib.h>
#include <epan/conversation.h>
#include <epan/packet.h>
#include <epan/strutil.h>
#include <epan/base64.h>
#include <epan/emem.h>
#include <epan/stats_tree.h>

#include <epan/req_resp_hdrs.h>
#include "packet-spdy.h"
#include <epan/dissectors/packet-tcp.h>
#include <epan/dissectors/packet-ssl.h>
#include <epan/prefs.h>
#include <epan/expert.h>
#include <epan/uat.h>

#define SPDY_FIN  0x01

/* The types of SPDY control frames */
typedef enum _spdy_type {
	SPDY_DATA,
	SPDY_SYN_STREAM,
	SPDY_SYN_REPLY,
	SPDY_FIN_STREAM,
	SPDY_HELLO,
	SPDY_NOOP,
	SPDY_PING,
	SPDY_INVALID
} spdy_frame_type_t;

static const char *frame_type_names[] = {
  "DATA", "SYN_STREAM", "SYN_REPLY", "FIN_STREAM", "HELLO", "NOOP",
  "PING", "INVALID"
};

/*
 * This structure will be tied to each SPDY frame.
 * Note that there may be multiple SPDY frames
 * in one packet.
 */
typedef struct _spdy_frame_info_t {
    guint32 stream_id;
    guint8 *header_block;
    guint   header_block_len;
    guint16 frame_type;
} spdy_frame_info_t;

/*
 * This structures keeps track of all the data frames
 * associated with a stream, so that they can be
 * reassembled into a single chunk.
 */
typedef struct _spdy_data_frame_t {
    guint8 *data;
    guint32 length;
    guint32 framenum;
} spdy_data_frame_t;

typedef struct _spdy_stream_info_t {
    gchar *content_type;
    gchar *content_type_parameters;
    gchar *content_encoding;
    GSList *data_frames;
    tvbuff_t *assembled_data;
    guint num_data_frames;
} spdy_stream_info_t;

#include <epan/tap.h>


static int spdy_tap = -1;
static int spdy_eo_tap = -1;

static int proto_spdy = -1;
static int hf_spdy_syn_stream = -1;
static int hf_spdy_syn_reply = -1;
static int hf_spdy_control_bit = -1;
static int hf_spdy_version = -1;
static int hf_spdy_type = -1;
static int hf_spdy_flags = -1;
static int hf_spdy_flags_fin = -1;
static int hf_spdy_length = -1;
static int hf_spdy_header = -1;
static int hf_spdy_header_name = -1;
static int hf_spdy_header_name_text = -1;
static int hf_spdy_header_value = -1;
static int hf_spdy_header_value_text = -1;
static int hf_spdy_streamid = -1;
static int hf_spdy_associated_streamid = -1;
static int hf_spdy_priority = -1;
static int hf_spdy_num_headers = -1;
static int hf_spdy_num_headers_string = -1;

static gint ett_spdy = -1;
static gint ett_spdy_syn_stream = -1;
static gint ett_spdy_syn_reply = -1;
static gint ett_spdy_fin_stream = -1;
static gint ett_spdy_flags = -1;
static gint ett_spdy_header = -1;
static gint ett_spdy_header_name = -1;
static gint ett_spdy_header_value = -1;

static gint ett_spdy_encoded_entity = -1;

static dissector_handle_t data_handle;
static dissector_handle_t media_handle;
static dissector_handle_t spdy_handle;

/* Stuff for generation/handling of fields for custom HTTP headers */
typedef struct _header_field_t {
	gchar* header_name;
	gchar* header_desc;
} header_field_t;

/*
 * desegmentation of SPDY control frames
 * (when we are over TCP or another protocol providing the desegmentation API)
 */
static gboolean spdy_desegment_control_frames = TRUE;

/*
 * desegmentation of SPDY data frames bodies
 * (when we are over TCP or another protocol providing the desegmentation API)
 * TODO let the user filter on content-type the bodies he wants desegmented
 */
static gboolean spdy_desegment_data_frames = TRUE;

static gboolean spdy_assemble_entity_bodies = TRUE;

/*
 * Decompression of zlib encoded entities.
 */
#ifdef HAVE_LIBZ
static gboolean spdy_decompress_body = TRUE;
static gboolean spdy_decompress_headers = TRUE;
#else
static gboolean spdy_decompress_body = FALSE;
static gboolean spdy_decompress_headers = FALSE;
#endif
static gboolean spdy_debug = FALSE;

#define TCP_PORT_DAAP			3689

/*
 * SSDP is implemented atop HTTP (yes, it really *does* run over UDP).
 */
#define TCP_PORT_SSDP			1900
#define UDP_PORT_SSDP			1900

/*
 * tcp and ssl ports
 */

#define TCP_DEFAULT_RANGE "80,8080"
#define SSL_DEFAULT_RANGE "443"

static range_t *global_spdy_tcp_range = NULL;
static range_t *global_spdy_ssl_range = NULL;

static range_t *spdy_tcp_range = NULL;
static range_t *spdy_ssl_range = NULL;

static const value_string vals_status_code[] = {
	{ 100, "Continue" },
	{ 101, "Switching Protocols" },
	{ 102, "Processing" },
	{ 199, "Informational - Others" },

	{ 200, "OK"},
	{ 201, "Created"},
	{ 202, "Accepted"},
	{ 203, "Non-authoritative Information"},
	{ 204, "No Content"},
	{ 205, "Reset Content"},
	{ 206, "Partial Content"},
	{ 207, "Multi-Status"},
	{ 299, "Success - Others"},

	{ 300, "Multiple Choices"},
	{ 301, "Moved Permanently"},
	{ 302, "Found"},
	{ 303, "See Other"},
	{ 304, "Not Modified"},
	{ 305, "Use Proxy"},
	{ 307, "Temporary Redirect"},
	{ 399, "Redirection - Others"},

	{ 400, "Bad Request"},
	{ 401, "Unauthorized"},
	{ 402, "Payment Required"},
	{ 403, "Forbidden"},
	{ 404, "Not Found"},
	{ 405, "Method Not Allowed"},
	{ 406, "Not Acceptable"},
	{ 407, "Proxy Authentication Required"},
	{ 408, "Request Time-out"},
	{ 409, "Conflict"},
	{ 410, "Gone"},
	{ 411, "Length Required"},
	{ 412, "Precondition Failed"},
	{ 413, "Request Entity Too Large"},
	{ 414, "Request-URI Too Long"},
	{ 415, "Unsupported Media Type"},
	{ 416, "Requested Range Not Satisfiable"},
	{ 417, "Expectation Failed"},
	{ 418, "I'm a teapot"},         /* RFC 2324 */
	{ 422, "Unprocessable Entity"},
	{ 423, "Locked"},
	{ 424, "Failed Dependency"},
	{ 499, "Client Error - Others"},

	{ 500, "Internal Server Error"},
	{ 501, "Not Implemented"},
	{ 502, "Bad Gateway"},
	{ 503, "Service Unavailable"},
	{ 504, "Gateway Time-out"},
	{ 505, "HTTP Version not supported"},
	{ 507, "Insufficient Storage"},
	{ 599, "Server Error - Others"},

	{ 0,	NULL}
};

static const char spdy_dictionary[] =
  "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
  "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
  "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
  "-agent10010120020120220320420520630030130230330430530630740040140240340440"
  "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
  "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
  "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
  "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
  "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
  "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
  "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
  "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
  ".1statusversionurl";

static void reset_decompressors(void)
{
    if (spdy_debug) printf("Should reset SPDY decompressors\n");
}

static spdy_conv_t *
get_spdy_conversation_data(packet_info *pinfo)
{
    conversation_t  *conversation;
    spdy_conv_t *conv_data;
    int retcode;

    conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
    if (spdy_debug) {
        printf("\n===========================================\n\n");
        printf("Conversation for frame #%d is %p\n", pinfo->fd->num, conversation);
        if (conversation)
            printf("  conv_data=%p\n", conversation_get_proto_data(conversation, proto_spdy));
    }

    if(!conversation)  /* Conversation does not exist yet - create it */
	conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);

    /* Retrieve information from conversation
    */
    conv_data = conversation_get_proto_data(conversation, proto_spdy);
    if(!conv_data) {
	/* Setup the conversation structure itself */
	conv_data = se_alloc0(sizeof(spdy_conv_t));

	conv_data->streams = NULL;
	if (spdy_decompress_headers) {
	    conv_data->rqst_decompressor = se_alloc0(sizeof(z_stream));
	    conv_data->rply_decompressor = se_alloc0(sizeof(z_stream));
	    retcode = inflateInit(conv_data->rqst_decompressor);
	    if (retcode == Z_OK)
		retcode = inflateInit(conv_data->rply_decompressor);
	    if (retcode != Z_OK)
		printf("frame #%d: inflateInit() failed: %d\n", pinfo->fd->num, retcode);
	    else if (spdy_debug)
		printf("created decompressor\n");
	    conv_data->dictionary_id = adler32(0L, Z_NULL, 0);
	    conv_data->dictionary_id = adler32(conv_data->dictionary_id,
					       spdy_dictionary,
					       sizeof(spdy_dictionary));
	}

	conversation_add_proto_data(conversation, proto_spdy, conv_data);
	register_postseq_cleanup_routine(reset_decompressors);
    }
    return conv_data;
}

static void
spdy_save_stream_info(spdy_conv_t *conv_data,
		      guint32 stream_id,
		      gchar *content_type,
                      gchar *content_type_params,
		      gchar *content_encoding)
{
    spdy_stream_info_t *si;

    if (conv_data->streams == NULL)
	conv_data->streams = g_array_new(FALSE, TRUE, sizeof(spdy_stream_info_t *));
    if (stream_id < conv_data->streams->len)
	DISSECTOR_ASSERT(g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) == NULL);
    else
        g_array_set_size(conv_data->streams, stream_id+1);
    si = se_alloc(sizeof(spdy_stream_info_t));
    si->content_type = content_type;
    si->content_type_parameters = content_type_params;
    si->content_encoding = content_encoding;
    si->data_frames = NULL;
    si->num_data_frames = 0;
    si->assembled_data = NULL;
    g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) = si;
    if (spdy_debug)
        printf("Saved stream info for ID %u, content type %s\n", stream_id, content_type);
}

static spdy_stream_info_t *
spdy_get_stream_info(spdy_conv_t *conv_data, guint32 stream_id)
{
    if (conv_data->streams == NULL || stream_id >= conv_data->streams->len)
	return NULL;
    else
	return g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id);
}

static void
spdy_add_data_chunk(spdy_conv_t *conv_data, guint32 stream_id, guint32 frame,
		    guint8 *data, guint32 length)
{
    spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);

    if (si == NULL) {
	if (spdy_debug) printf("No stream_info found for stream %d\n", stream_id);
    } else {
	spdy_data_frame_t *df = g_malloc(sizeof(spdy_data_frame_t));
	df->data = data;
	df->length = length;
	df->framenum = frame;
	si->data_frames = g_slist_append(si->data_frames, df);
	++si->num_data_frames;
	if (spdy_debug)
	    printf("Saved %u bytes of data for stream %u frame %u\n",
		    length, stream_id, df->framenum);
    }
}

static void
spdy_increment_data_chunk_count(spdy_conv_t *conv_data, guint32 stream_id)
{
    spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
    if (si != NULL)
	++si->num_data_frames;
}

/*
 * Return the number of data frames saved so far for the specified stream.
 */
static guint
spdy_get_num_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
{
    spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);

    return si == NULL ? 0 : si->num_data_frames;
}

static spdy_stream_info_t *
spdy_assemble_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
{
    spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
    tvbuff_t *tvb;

    if (si == NULL)
	return NULL;

    /*
     * Compute the total amount of data and concatenate the
     * data chunks, if it hasn't already been done.
     */
    if (si->assembled_data == NULL) {
	spdy_data_frame_t *df;
	guint8 *data;
	guint32 datalen;
	guint32 offset;
	guint32 framenum;
	GSList *dflist = si->data_frames;
	if (dflist == NULL)
	    return si;
	dflist = si->data_frames;
	datalen = 0;
	/*
	 * I'd like to use a composite tvbuff here, but since
	 * only a real-data tvbuff can be the child of another
	 * tvb, I can't. It would be nice if this limitation
	 * could be fixed.
	 */
	while (dflist != NULL) {
	    df = dflist->data;
	    datalen += df->length;
	    dflist = g_slist_next(dflist);
	}
	if (datalen != 0) {
	    data = se_alloc(datalen);
	    dflist = si->data_frames;
	    offset = 0;
	    framenum = 0;
	    while (dflist != NULL) {
		df = dflist->data;
		memcpy(data+offset, df->data, df->length);
		offset += df->length;
		dflist = g_slist_next(dflist);
	    }
	    tvb = tvb_new_real_data(data, datalen, datalen);
	    si->assembled_data = tvb;
	}
    }
    return si;
}

static void
spdy_discard_data_frames(spdy_stream_info_t *si)
{
    GSList *dflist = si->data_frames;
    spdy_data_frame_t *df;

    if (dflist == NULL)
	return;
    while (dflist != NULL) {
	df = dflist->data;
	if (df->data != NULL) {
	    g_free(df->data);
	    df->data = NULL;
	}
	dflist = g_slist_next(dflist);
    }
    /*g_slist_free(si->data_frames);
    si->data_frames = NULL; */
}

// TODO(cbentzel): tvb_child_uncompress should be exported by wireshark.
static tvbuff_t* spdy_tvb_child_uncompress(tvbuff_t *parent _U_, tvbuff_t *tvb,
                                           int offset, int comprlen)
{
	tvbuff_t *new_tvb = tvb_uncompress(tvb, offset, comprlen);
	if (new_tvb)
		tvb_set_child_real_data_tvbuff (parent, new_tvb);
	return new_tvb;
}

static int
dissect_spdy_data_frame(tvbuff_t *tvb, int offset,
			packet_info *pinfo,
			proto_tree *top_level_tree,
			proto_tree *spdy_tree,
			proto_item *spdy_proto,
			spdy_conv_t *conv_data)
{
    guint32	stream_id;
    guint8	flags;
    guint32	frame_length;
    proto_item	*ti;
    proto_tree	*flags_tree;
    guint32	reported_datalen;
    guint32	datalen;
    dissector_table_t media_type_subdissector_table;
    dissector_table_t port_subdissector_table;
    dissector_handle_t handle;
    guint	num_data_frames;
    gboolean    dissected;

    stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
    flags = tvb_get_guint8(tvb, offset+4);
    frame_length = tvb_get_ntoh24(tvb, offset+5);

    if (spdy_debug)
	printf("Data frame [stream_id=%u flags=0x%x length=%d]\n",
		stream_id, flags, frame_length);
    if (spdy_tree) proto_item_append_text(spdy_tree, ", data frame");
    col_add_fstr(pinfo->cinfo, COL_INFO, "DATA[%u] length=%d",
	    stream_id, frame_length);

    proto_item_append_text(spdy_proto, ":%s stream=%d length=%d",
	    flags & SPDY_FIN ? " [FIN]" : "",
	    stream_id, frame_length);

    proto_tree_add_boolean(spdy_tree, hf_spdy_control_bit, tvb, offset, 1, 0);
    proto_tree_add_uint(spdy_tree, hf_spdy_streamid, tvb, offset, 4, stream_id);
    ti = proto_tree_add_uint_format(spdy_tree, hf_spdy_flags, tvb, offset+4, 1, flags,
	    "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");

    flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
    proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, offset+4, 1, flags);
    proto_tree_add_uint(spdy_tree, hf_spdy_length, tvb, offset+5, 3, frame_length);

    datalen = tvb_length_remaining(tvb, offset);
    if (datalen > frame_length)
	datalen = frame_length;

    reported_datalen = tvb_reported_length_remaining(tvb, offset);
    if (reported_datalen > frame_length)
	reported_datalen = frame_length;

    num_data_frames = spdy_get_num_data_frames(conv_data, stream_id);
    if (datalen != 0 || num_data_frames != 0) {
	/*
	 * There's stuff left over; process it.
	 */
	tvbuff_t *next_tvb = NULL;
	tvbuff_t    *data_tvb = NULL;
	spdy_stream_info_t *si = NULL;
	void *save_private_data = NULL;
	guint8 *copied_data;
	gboolean private_data_changed = FALSE;
	gboolean is_single_chunk = FALSE;
	gboolean have_entire_body;

	/*
	 * Create a tvbuff for the payload.
	 */
	if (datalen != 0) {
	    next_tvb = tvb_new_subset(tvb, offset+8, datalen,
				      reported_datalen);
            is_single_chunk = num_data_frames == 0 && (flags & SPDY_FIN) != 0;
            if (!pinfo->fd->flags.visited) {
                if (!is_single_chunk) {
                    if (spdy_assemble_entity_bodies) {
                        copied_data = tvb_memdup(next_tvb, 0, datalen);
                        spdy_add_data_chunk(conv_data, stream_id, pinfo->fd->num,
                                copied_data, datalen);
                    } else
                        spdy_increment_data_chunk_count(conv_data, stream_id);
                }
            }
        } else
            is_single_chunk = (num_data_frames == 1);

	if (!(flags & SPDY_FIN)) {
	    col_set_fence(pinfo->cinfo, COL_INFO);
	    col_add_fstr(pinfo->cinfo, COL_INFO, " (partial entity)");
            proto_item_append_text(spdy_proto, " (partial entity body)");
            /* would like the proto item to say */
            /* " (entity body fragment N of M)" */
	    goto body_dissected;
	}
	have_entire_body = is_single_chunk;
	/*
	 * On seeing the last data frame in a stream, we can
	 * reassemble the frames into one data block.
	 */
	si = spdy_assemble_data_frames(conv_data, stream_id);
	if (si == NULL)
	    goto body_dissected;
	data_tvb = si->assembled_data;
	if (spdy_assemble_entity_bodies)
	    have_entire_body = TRUE;

	if (!have_entire_body)
	    goto body_dissected;

	if (data_tvb == NULL)
	    data_tvb = next_tvb;
	else
	    add_new_data_source(pinfo, data_tvb, "Assembled entity body");

	if (have_entire_body && si->content_encoding != NULL &&
	    g_ascii_strcasecmp(si->content_encoding, "identity") != 0) {
	    /*
	     * We currently can't handle, for example, "compress";
	     * just handle them as data for now.
	     *
	     * After July 7, 2004 the LZW patent expires, so support
	     * might be added then.  However, I don't think that
	     * anybody ever really implemented "compress", due to
	     * the aforementioned patent.
	     */
	    tvbuff_t *uncomp_tvb = NULL;
	    proto_item *e_ti = NULL;
	    proto_item *ce_ti = NULL;
	    proto_tree *e_tree = NULL;

	    if (spdy_decompress_body &&
		    (g_ascii_strcasecmp(si->content_encoding, "gzip") == 0 ||
		     g_ascii_strcasecmp(si->content_encoding, "deflate")
		     == 0)) {
              uncomp_tvb = spdy_tvb_child_uncompress(tvb, data_tvb, 0,
                                                     tvb_length(data_tvb));
	    }
	    /*
	     * Add the encoded entity to the protocol tree
	     */
	    e_ti = proto_tree_add_text(top_level_tree, data_tvb,
		    0, tvb_length(data_tvb),
		    "Content-encoded entity body (%s): %u bytes",
		    si->content_encoding,
		    tvb_length(data_tvb));
	    e_tree = proto_item_add_subtree(e_ti, ett_spdy_encoded_entity);
	    if (si->num_data_frames > 1) {
		GSList *dflist;
		spdy_data_frame_t *df;
		guint32 framenum;
		ce_ti = proto_tree_add_text(e_tree, data_tvb, 0,
			tvb_length(data_tvb),
			"Assembled from %d frames in packet(s)", si->num_data_frames);
		dflist = si->data_frames;
		framenum = 0;
		while (dflist != NULL) {
		    df = dflist->data;
		    if (framenum != df->framenum) {
			proto_item_append_text(ce_ti, " #%u", df->framenum);
			framenum = df->framenum;
		    }
		    dflist = g_slist_next(dflist);
		  }
	    }

	    if (uncomp_tvb != NULL) {
		/*
		 * Decompression worked
		 */

		/* XXX - Don't free this, since it's possible
		 * that the data was only partially
		 * decompressed, such as when desegmentation
		 * isn't enabled.
		 *
		 tvb_free(next_tvb);
		 */
		proto_item_append_text(e_ti, " -> %u bytes", tvb_length(uncomp_tvb));
		data_tvb = uncomp_tvb;
		add_new_data_source(pinfo, data_tvb, "Uncompressed entity body");
	    } else {
		if (spdy_decompress_body)
		    proto_item_append_text(e_ti, " [Error: Decompression failed]");
		call_dissector(data_handle, data_tvb, pinfo, e_tree);

		goto body_dissected;
	    }
	}
	if (si != NULL)
	    spdy_discard_data_frames(si);
	/*
	 * Do subdissector checks.
	 *
	 * First, check whether some subdissector asked that they
	 * be called if something was on some particular port.
	 */

	port_subdissector_table = find_dissector_table("http.port");
	media_type_subdissector_table = find_dissector_table("media_type");
	if (have_entire_body && port_subdissector_table != NULL)
	    handle = dissector_get_port_handle(port_subdissector_table,
		    pinfo->match_port);
	else
	    handle = NULL;
	if (handle == NULL && have_entire_body && si->content_type != NULL &&
		media_type_subdissector_table != NULL) {
	    /*
	     * We didn't find any subdissector that
	     * registered for the port, and we have a
	     * Content-Type value.  Is there any subdissector
	     * for that content type?
	     */
	    save_private_data = pinfo->private_data;
	    private_data_changed = TRUE;

	    if (si->content_type_parameters)
		pinfo->private_data = ep_strdup(si->content_type_parameters);
	    else
		pinfo->private_data = NULL;
	    /*
	     * Calling the string handle for the media type
	     * dissector table will set pinfo->match_string
	     * to si->content_type for us.
	     */
	    pinfo->match_string = si->content_type;
	    handle = dissector_get_string_handle(
		    media_type_subdissector_table,
		    si->content_type);
	}
	if (handle != NULL) {
	    /*
	     * We have a subdissector - call it.
	     */
	    dissected = call_dissector(handle, data_tvb, pinfo, top_level_tree);
	} else
	    dissected = FALSE;

	if (dissected) {
	    /*
	     * The subdissector dissected the body.
	     * Fix up the top-level item so that it doesn't
	     * include the stuff for that protocol.
	     */
	    if (ti != NULL)
		proto_item_set_len(ti, offset);
	} else if (have_entire_body && si->content_type != NULL) {
	    /*
	     * Calling the default media handle if there is a content-type that
	     * wasn't handled above.
	     */
	    call_dissector(media_handle, next_tvb, pinfo, top_level_tree);
	} else {
	    /* Call the default data dissector */
	    call_dissector(data_handle, next_tvb, pinfo, top_level_tree);
	}

body_dissected:
	/*
	 * Do *not* attempt at freeing the private data;
	 * it may be in use by subdissectors.
	 */
	if (private_data_changed) /*restore even NULL value*/
	    pinfo->private_data = save_private_data;
	/*
	 * We've processed "datalen" bytes worth of data
	 * (which may be no data at all); advance the
	 * offset past whatever data we've processed.
	 */
    }
    return frame_length + 8;
}

static guint8 *
spdy_decompress_header_block(tvbuff_t *tvb, z_streamp decomp,
			     guint32 dictionary_id, int offset,
			     guint32 length, guint *uncomp_length)
{
    int retcode;
    size_t bufsize = 16384;
    const guint8 *hptr = tvb_get_ptr(tvb, offset, length);
    guint8 *uncomp_block = ep_alloc(bufsize);
    decomp->next_in = (Bytef *)hptr;
    decomp->avail_in = length;
    decomp->next_out = uncomp_block;
    decomp->avail_out = bufsize;
    retcode = inflate(decomp, Z_SYNC_FLUSH);
    if (retcode == Z_NEED_DICT) {
	if (decomp->adler != dictionary_id) {
	    printf("decompressor wants dictionary %#x, but we have %#x\n",
		   (guint)decomp->adler, dictionary_id);
	} else {
	    retcode = inflateSetDictionary(decomp,
					   spdy_dictionary,
					   sizeof(spdy_dictionary));
	    if (retcode == Z_OK)
		retcode = inflate(decomp, Z_SYNC_FLUSH);
	}
    }

    if (retcode != Z_OK) {
	return NULL;
    } else {
	*uncomp_length = bufsize - decomp->avail_out;
        if (spdy_debug)
            printf("Inflation SUCCEEDED. uncompressed size=%d\n", *uncomp_length);
	if (decomp->avail_in != 0)
	    if (spdy_debug)
		printf("  but there were %d input bytes left over\n", decomp->avail_in);
    }
    return se_memdup(uncomp_block, *uncomp_length);
}

/*
 * Try to determine heuristically whether the header block is
 * compressed. For an uncompressed block, the first two bytes
 * gives the number of headers. Each header name and value is
 * a two-byte length followed by ASCII characters.
 */
static gboolean
spdy_check_header_compression(tvbuff_t *tvb,
				       int offset,
				       guint32 frame_length)
{
    guint16 length;
    if (!tvb_bytes_exist(tvb, offset, 6))
	return 1;
    length = tvb_get_ntohs(tvb, offset);
    if (length > frame_length)
	return 1;
    length = tvb_get_ntohs(tvb, offset+2);
    if (length > frame_length)
	return 1;
    if (spdy_debug) printf("Looks like the header block is not compressed\n");
    return 0;
}

// TODO(cbentzel): Change wireshark to export p_remove_proto_data, rather
// than duplicating code here.
typedef struct _spdy_frame_proto_data {
  int proto;
  void *proto_data;
} spdy_frame_proto_data;

static gint spdy_p_compare(gconstpointer a, gconstpointer b)
{
  const spdy_frame_proto_data *ap = (const spdy_frame_proto_data *)a;
  const spdy_frame_proto_data *bp = (const spdy_frame_proto_data *)b;

  if (ap -> proto > bp -> proto)
    return 1;
  else if (ap -> proto == bp -> proto)
    return 0;
  else
    return -1;

}

static void spdy_p_remove_proto_data(frame_data *fd, int proto)
{
  spdy_frame_proto_data temp;
  GSList *item;

  temp.proto = proto;
  temp.proto_data = NULL;

  item = g_slist_find_custom(fd->pfd, (gpointer *)&temp, spdy_p_compare);

  if (item) {
    fd->pfd = g_slist_remove(fd->pfd, item->data);
  }
}

static spdy_frame_info_t *
spdy_save_header_block(frame_data *fd,
	guint32 stream_id,
	guint frame_type,
	guint8 *header,
	guint length)
{
    GSList *filist = p_get_proto_data(fd, proto_spdy);
    spdy_frame_info_t *frame_info = se_alloc(sizeof(spdy_frame_info_t));
    if (filist != NULL)
      spdy_p_remove_proto_data(fd, proto_spdy);
    frame_info->stream_id = stream_id;
    frame_info->header_block = header;
    frame_info->header_block_len = length;
    frame_info->frame_type = frame_type;
    filist = g_slist_append(filist, frame_info);
    p_add_proto_data(fd, proto_spdy, filist);
    return frame_info;
    /* TODO(ers) these need to get deleted when no longer needed */
}

static spdy_frame_info_t *
spdy_find_saved_header_block(frame_data *fd,
			     guint32 stream_id,
			     guint16 frame_type)
{
    GSList *filist = p_get_proto_data(fd, proto_spdy);
    while (filist != NULL) {
	spdy_frame_info_t *fi = filist->data;
	if (fi->stream_id == stream_id && fi->frame_type == frame_type)
	    return fi;
	filist = g_slist_next(filist);
    }
    return NULL;
}

/*
 * Given a content type string that may contain optional parameters,
 * return the parameter string, if any, otherwise return NULL. This
 * also has the side effect of null terminating the content type
 * part of the original string.
 */
static gchar *
spdy_parse_content_type(gchar *content_type)
{
    gchar *cp = content_type;

    while (*cp != '\0' && *cp != ';' && !isspace(*cp)) {
	*cp = tolower(*cp);
	++cp;
    }
    if (*cp == '\0')
	cp = NULL;

    if (cp != NULL) {
	*cp++ = '\0';
	while (*cp == ';' || isspace(*cp))
	    ++cp;
	if (*cp != '\0')
	    return cp;
    }
    return NULL;
}

static int
dissect_spdy_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
		     proto_tree *tree, spdy_conv_t *conv_data)
{
    guint8		control_bit;
    guint16		version;
    guint16		frame_type;
    guint8		flags;
    guint32		frame_length;
    guint32		stream_id;
    guint32             associated_stream_id;
    gint		priority;
    guint16		num_headers;
    guint32		fin_status;
    guint8		*frame_header;
    const char		*proto_tag;
    const char		*frame_type_name;
    proto_tree		*spdy_tree = NULL;
    proto_item		*ti = NULL;
    proto_item		*spdy_proto = NULL;
    int			orig_offset;
    int			hoffset;
    int			hdr_offset = 0;
    spdy_frame_type_t	spdy_type;
    proto_tree		*sub_tree;
    proto_tree		*flags_tree;
    tvbuff_t		*header_tvb = NULL;
    gboolean		headers_compressed;
    gchar		*hdr_verb = NULL;
    gchar		*hdr_url = NULL;
    gchar		*hdr_version = NULL;
    gchar		*content_type = NULL;
    gchar		*content_encoding = NULL;

    /*
     * Minimum size for a SPDY frame is 8 bytes.
     */
    if (tvb_reported_length_remaining(tvb, offset) < 8)
	return -1;

    proto_tag = "SPDY";

    if (check_col(pinfo->cinfo, COL_PROTOCOL))
	col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_tag);

    /*
     * Is this a control frame or a data frame?
     */
    orig_offset = offset;
    control_bit = tvb_get_bits8(tvb, offset << 3, 1);
    if (control_bit) {
	version = tvb_get_bits16(tvb, (offset << 3) + 1, 15, FALSE);
	frame_type = tvb_get_ntohs(tvb, offset+2);
	if (frame_type >= SPDY_INVALID) {
	    return -1;
	}
	frame_header = ep_tvb_memdup(tvb, offset, 16);
    } else {
        version = 1;  /* avoid gcc warning */
	frame_type = SPDY_DATA;
        frame_header = NULL;    /* avoid gcc warning */
    }
    frame_type_name = frame_type_names[frame_type];
    offset += 4;
    flags = tvb_get_guint8(tvb, offset);
    frame_length = tvb_get_ntoh24(tvb, offset+1);
    offset += 4;
    /*
     * Make sure there's as much data as the frame header says there is.
     */
    if ((guint)tvb_reported_length_remaining(tvb, offset) < frame_length) {
	if (spdy_debug)
	    printf("Not enough header data: %d vs. %d\n",
		    frame_length, tvb_reported_length_remaining(tvb, offset));
	return -1;
    }
    if (tree) {
	spdy_proto = proto_tree_add_item(tree, proto_spdy, tvb, orig_offset, frame_length+8, FALSE);
	spdy_tree = proto_item_add_subtree(spdy_proto, ett_spdy);
    }

    if (control_bit) {
	if (spdy_debug)
	    printf("Control frame [version=%d type=%d flags=0x%x length=%d]\n",
		    version, frame_type, flags, frame_length);
	if (tree) proto_item_append_text(spdy_tree, ", control frame");
    } else {
	return dissect_spdy_data_frame(tvb, orig_offset, pinfo, tree,
				spdy_tree, spdy_proto, conv_data);
    }
    num_headers = 0;
    sub_tree = NULL;    /* avoid gcc warning */
    switch (frame_type) {
	case SPDY_SYN_STREAM:
	case SPDY_SYN_REPLY:
	    if (tree) {
		int hf;
		hf = frame_type == SPDY_SYN_STREAM ? hf_spdy_syn_stream : hf_spdy_syn_reply;
		ti = proto_tree_add_bytes(spdy_tree, hf, tvb,
					  orig_offset, 16, frame_header);
		sub_tree = proto_item_add_subtree(ti, ett_spdy_syn_stream);
	    }
	    stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
	    offset += 4;
            if (frame_type == SPDY_SYN_STREAM) {
                associated_stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
                offset += 4;
                priority = tvb_get_bits8(tvb, offset << 3, 2);
                offset += 2;
            } else {
                // The next two bytes have no meaning in SYN_REPLY
                offset += 2;
            }
            if (tree) {
		proto_tree_add_boolean(sub_tree, hf_spdy_control_bit, tvb, orig_offset, 1, control_bit);
		proto_tree_add_uint(sub_tree, hf_spdy_version, tvb, orig_offset, 2, version);
		proto_tree_add_uint(sub_tree, hf_spdy_type, tvb, orig_offset+2, 2, frame_type);
		ti = proto_tree_add_uint_format(sub_tree, hf_spdy_flags, tvb, orig_offset+4, 1, flags,
						"Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");
		flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
		proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, orig_offset+4, 1, flags);
		proto_tree_add_uint(sub_tree, hf_spdy_length, tvb, orig_offset+5, 3, frame_length);
		proto_tree_add_uint(sub_tree, hf_spdy_streamid, tvb, orig_offset+8, 4, stream_id);
                if (frame_type == SPDY_SYN_STREAM) {
                     proto_tree_add_uint(sub_tree, hf_spdy_associated_streamid, tvb, orig_offset+12, 4, associated_stream_id);
                     proto_tree_add_uint(sub_tree, hf_spdy_priority, tvb, orig_offset+16, 1, priority);
                }
		proto_item_append_text(spdy_proto, ": %s%s stream=%d length=%d",
				       frame_type_name,
				       flags & SPDY_FIN ? " [FIN]" : "",
				       stream_id, frame_length);
                if (spdy_debug)
                    printf("  stream ID=%u priority=%d\n", stream_id, priority);
	    }
	    break;

	case SPDY_FIN_STREAM:
	    stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
	    fin_status = tvb_get_ntohl(tvb, offset);
	    // TODO(ers) fill in tree and summary
	    offset += 8;
	    break;

	case SPDY_HELLO:
	    // TODO(ers) fill in tree and summary
            stream_id = 0;      /* avoid gcc warning */
	    break;

	default:
            stream_id = 0;      /* avoid gcc warning */
	    return -1;
	    break;
    }

    /*
     * Process the name-value pairs one at a time, after possibly
     * decompressing the header block.
     */
    if (frame_type == SPDY_SYN_STREAM || frame_type == SPDY_SYN_REPLY) {
	headers_compressed = spdy_check_header_compression(tvb, offset, frame_length);
	if (!spdy_decompress_headers || !headers_compressed) {
	    header_tvb = tvb;
	    hdr_offset = offset;
	} else {
	    spdy_frame_info_t *per_frame_info =
		    spdy_find_saved_header_block(pinfo->fd,
						 stream_id,
						 frame_type == SPDY_SYN_REPLY);
	    if (per_frame_info == NULL) {
		guint uncomp_length;
		z_streamp decomp = frame_type == SPDY_SYN_STREAM ?
			conv_data->rqst_decompressor : conv_data->rply_decompressor;
		guint8 *uncomp_ptr =
			spdy_decompress_header_block(tvb, decomp,
						     conv_data->dictionary_id,
						     offset,
                                                     frame_length + 8 - (offset - orig_offset),
                                                     &uncomp_length);
		if (uncomp_ptr == NULL) {         /* decompression failed */
                    if (spdy_debug)
                        printf("Frame #%d: Inflation failed\n", pinfo->fd->num);
		    proto_item_append_text(spdy_proto, " [Error: Header decompression failed]");
		    // Should we just bail here?
                } else {
                    if (spdy_debug)
                        printf("Saving %u bytes of uncomp hdr\n", uncomp_length);
                    per_frame_info =
                        spdy_save_header_block(pinfo->fd, stream_id, frame_type == SPDY_SYN_REPLY,
                                uncomp_ptr, uncomp_length);
                }
	    } else if (spdy_debug) {
		printf("Found uncompressed header block len %u for stream %u frame_type=%d\n",
		       per_frame_info->header_block_len,
		       per_frame_info->stream_id,
		       per_frame_info->frame_type);
	    }
            if (per_frame_info != NULL) {
                header_tvb = tvb_new_child_real_data(tvb,
						 per_frame_info->header_block,
						 per_frame_info->header_block_len,
						 per_frame_info->header_block_len);
                add_new_data_source(pinfo, header_tvb, "Uncompressed headers");
                hdr_offset = 0;
            }
	}
        offset = orig_offset + 8 + frame_length;
	num_headers = tvb_get_ntohs(header_tvb, hdr_offset);
	hdr_offset += 2;
	if (header_tvb == NULL ||
                (headers_compressed && !spdy_decompress_headers)) {
	    num_headers = 0;
	    ti = proto_tree_add_string(sub_tree, hf_spdy_num_headers_string,
				  tvb, 
				  frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset + 14, 
				  2,
				  "Unknown (header block is compressed)");
	} else
	    ti = proto_tree_add_uint(sub_tree, hf_spdy_num_headers,
				tvb, 
				frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset +14, 
				2, num_headers);
    }
    spdy_type = SPDY_INVALID;		/* type not known yet */
    if (spdy_debug)
        printf("  %d Headers:\n", num_headers);
    if (num_headers > frame_length) {
	printf("Number of headers is greater than frame length!\n");
        proto_item_append_text(ti, " [Error: Number of headers is larger than frame length]");
	col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
	return frame_length+8;
    }
    hdr_verb = hdr_url = hdr_version = content_type = content_encoding = NULL;
    while (num_headers-- && tvb_reported_length_remaining(header_tvb, hdr_offset) != 0) {
	gchar *header_name;
	gchar *header_value;
	proto_tree *header_tree;
	proto_tree *name_tree;
	proto_tree *value_tree;
	proto_item *header;
	gint16 length;
	gint header_length = 0;

	hoffset = hdr_offset;

	header = proto_tree_add_item(spdy_tree, hf_spdy_header, header_tvb,
				 hdr_offset, frame_length, FALSE);
	header_tree = proto_item_add_subtree(header, ett_spdy_header);

	length = tvb_get_ntohs(header_tvb, hdr_offset);
	hdr_offset += 2;
	header_name = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
	hdr_offset += length;
	header_length += hdr_offset - hoffset;
	if (tree) {
	    ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Name: %s", 
				     header_name);
	    name_tree = proto_item_add_subtree(ti, ett_spdy_header_name);
	    proto_tree_add_uint(name_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
	    proto_tree_add_string_format(name_tree, hf_spdy_header_name_text, header_tvb, hoffset+2, length,
					 header_name, "Text: %s", format_text(header_name, length));
	}

	hoffset = hdr_offset;
	length = tvb_get_ntohs(header_tvb, hdr_offset);
	hdr_offset += 2;
	header_value = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
	hdr_offset += length;
	header_length += hdr_offset - hoffset;
	if (tree) {
	    ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Value: %s", 
				     header_value);
	    value_tree = proto_item_add_subtree(ti, ett_spdy_header_value);
	    proto_tree_add_uint(value_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
	    proto_tree_add_string_format(value_tree, hf_spdy_header_value_text, header_tvb, hoffset+2, length,
					 header_value, "Text: %s", format_text(header_value, length));
	    proto_item_append_text(header, ": %s: %s", header_name, header_value);
	    proto_item_set_len(header, header_length);
	}
	if (spdy_debug) printf("    %s: %s\n", header_name, header_value);
	/*
	 * TODO(ers) check that the header name contains only legal characters.
	 */
	if (g_ascii_strcasecmp(header_name, "method") == 0 ||
	    g_ascii_strcasecmp(header_name, "status") == 0) {
	    hdr_verb = header_value;
	} else if (g_ascii_strcasecmp(header_name, "url") == 0) {
	    hdr_url = header_value;
	} else if (g_ascii_strcasecmp(header_name, "version") == 0) {
	    hdr_version = header_value;
	} else if (g_ascii_strcasecmp(header_name, "content-type") == 0) {
	    content_type = se_strdup(header_value);
	} else if (g_ascii_strcasecmp(header_name, "content-encoding") == 0) {
	    content_encoding = se_strdup(header_value);
	}
    }
    if (hdr_version != NULL) {
	if (hdr_url != NULL) {
	    col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s %s",
			 frame_type_name, stream_id, hdr_verb, hdr_url, hdr_version);
	} else {
	    col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s",
			 frame_type_name, stream_id, hdr_verb, hdr_version);
	}
    } else {
	col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
    }
    /*
     * If we expect data on this stream, we need to remember the content
     * type and content encoding.
     */
    if (content_type != NULL && !pinfo->fd->flags.visited) {
        gchar *content_type_params = spdy_parse_content_type(content_type);
	spdy_save_stream_info(conv_data, stream_id, content_type,
                              content_type_params, content_encoding);
    }

    return offset - orig_offset;
}

static int
dissect_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    spdy_conv_t	*conv_data;
    int		offset = 0;
    int		len;
    int		firstpkt = 1;

    /*
     * The first byte of a SPDY packet must be either 0 or
     * 0x80. If it's not, assume that this is not SPDY.
     * (In theory, a data frame could have a stream ID
     * >= 2^24, in which case it won't have 0 for a first
     * byte, but this is a pretty reliable heuristic for
     * now.)
     */
    guint8 first_byte = tvb_get_guint8(tvb, 0);
    if (first_byte != 0x80 && first_byte != 0x0)
	  return 0;

    conv_data = get_spdy_conversation_data(pinfo);

    while (tvb_reported_length_remaining(tvb, offset) != 0) {
	if (!firstpkt) {
	    col_add_fstr(pinfo->cinfo, COL_INFO, " >> ");
	    col_set_fence(pinfo->cinfo, COL_INFO);
	}
	len = dissect_spdy_message(tvb, offset, pinfo, tree, conv_data);
	if (len <= 0)
	    return 0;
	offset += len;
	/*
	 * OK, we've set the Protocol and Info columns for the
	 * first SPDY message; set a fence so that subsequent
	 * SPDY messages don't overwrite the Info column.
	 */
	col_set_fence(pinfo->cinfo, COL_INFO);
	firstpkt = 0;
    }
    return 1;
}

static gboolean
dissect_spdy_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    if (!value_is_in_range(global_spdy_tcp_range, pinfo->destport) &&
            !value_is_in_range(global_spdy_tcp_range, pinfo->srcport))
        return FALSE;
    return dissect_spdy(tvb, pinfo, tree) != 0;
}

static void reinit_spdy(void)
{
}

// NMAKE complains about flags_set_truth not being constant. Duplicate
// the values inside of it.
static const true_false_string tfs_spdy_set_notset = { "Set", "Not set" };

void
proto_register_spdy(void)
{
    static hf_register_info hf[] = {
	{ &hf_spdy_syn_stream,
	    { "Syn Stream",	"spdy.syn_stream",
		FT_BYTES, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_syn_reply,
	    { "Syn Reply",	"spdy.syn_reply",
		FT_BYTES, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_control_bit,
	    { "Control bit",	"spdy.control_bit",
		FT_BOOLEAN, BASE_NONE, NULL, 0x0,
		"TRUE if SPDY control frame", HFILL }},
	{ &hf_spdy_version,
	    { "Version",	"spdy.version",
		FT_UINT16, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_type,
	    { "Type",		"spdy.type",
		FT_UINT16, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_flags,
	    { "Flags",		"spdy.flags",
		FT_UINT8, BASE_HEX, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_flags_fin,
	    { "Fin",		"spdy.flags.fin",
                FT_BOOLEAN, 8, TFS(&tfs_spdy_set_notset),
                SPDY_FIN, "", HFILL }},
	{ &hf_spdy_length,
	    { "Length",		"spdy.length",
		FT_UINT24, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_header,
	    { "Header",		"spdy.header",
		FT_NONE, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_header_name,
	    { "Name",		"spdy.header.name",
		FT_NONE, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_header_name_text,
	    { "Text",		"spdy.header.name.text",
		FT_STRING, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_header_value,
	    { "Value",		"spdy.header.value",
		FT_NONE, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_header_value_text,
	    { "Text",		"spdy.header.value.text",
		FT_STRING, BASE_NONE, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_streamid,
	    { "Stream ID",	"spdy.streamid",
		FT_UINT32, BASE_DEC, NULL, 0x0,
		"", HFILL }},
        { &hf_spdy_associated_streamid,
	    { "Associated Stream ID",	"spdy.associated.streamid",
		FT_UINT32, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_priority,
	    { "Priority",	"spdy.priority",
		FT_UINT8, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_num_headers,
	    { "Number of headers", "spdy.numheaders",
		FT_UINT16, BASE_DEC, NULL, 0x0,
		"", HFILL }},
	{ &hf_spdy_num_headers_string,
	    { "Number of headers", "spdy.numheaders",
		FT_STRING, BASE_NONE, NULL, 0x0,
		"", HFILL }},
    };
    static gint *ett[] = {
	&ett_spdy,
	&ett_spdy_syn_stream,
	&ett_spdy_syn_reply,
	&ett_spdy_fin_stream,
	&ett_spdy_flags,
	&ett_spdy_header,
	&ett_spdy_header_name,
	&ett_spdy_header_value,
	&ett_spdy_encoded_entity,
    };

    module_t *spdy_module;

    proto_spdy = proto_register_protocol("SPDY", "SPDY", "spdy");
    proto_register_field_array(proto_spdy, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
    new_register_dissector("spdy", dissect_spdy, proto_spdy);
    spdy_module = prefs_register_protocol(proto_spdy, reinit_spdy);
    prefs_register_bool_preference(spdy_module, "desegment_headers",
				   "Reassemble SPDY control frames spanning multiple TCP segments",
				   "Whether the SPDY dissector should reassemble control frames "
				   "spanning multiple TCP segments. "
				   "To use this option, you must also enable "
				   "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
				   &spdy_desegment_control_frames);
    prefs_register_bool_preference(spdy_module, "desegment_body",
				   "Reassemble SPDY bodies spanning multiple TCP segments",
				   "Whether the SPDY dissector should reassemble "
				   "data frames spanning multiple TCP segments. "
				   "To use this option, you must also enable "
				   "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
				   &spdy_desegment_data_frames);
    prefs_register_bool_preference(spdy_module, "assemble_data_frames",
				   "Assemble SPDY bodies that consist of multiple DATA frames",
				   "Whether the SPDY dissector should reassemble multiple "
				   "data frames into an entity body.",
				   &spdy_assemble_entity_bodies);
#ifdef HAVE_LIBZ
    prefs_register_bool_preference(spdy_module, "decompress_headers",
				   "Uncompress SPDY headers",
				   "Whether to uncompress SPDY headers.",
				   &spdy_decompress_headers);
    prefs_register_bool_preference(spdy_module, "decompress_body",
				   "Uncompress entity bodies",
				   "Whether to uncompress entity bodies that are compressed "
				   "using \"Content-Encoding: \"",
				   &spdy_decompress_body);
#endif
    prefs_register_bool_preference(spdy_module, "debug_output",
				   "Print debug info on stdout",
				   "Print debug info on stdout",
				   &spdy_debug);
#if 0
    prefs_register_string_preference(ssl_module, "debug_file", "SPDY debug file",
				     "Redirect SPDY debug to file name; "
				     "leave empty to disable debugging, "
				     "or use \"" SPDY_DEBUG_USE_STDOUT "\""
				     " to redirect output to stdout\n",
				     (const gchar **)&sdpy_debug_file_name);
#endif
    prefs_register_obsolete_preference(spdy_module, "tcp_alternate_port");

    range_convert_str(&global_spdy_tcp_range, TCP_DEFAULT_RANGE, 65535);
    spdy_tcp_range = range_empty();
    prefs_register_range_preference(spdy_module, "tcp.port", "TCP Ports",
				    "TCP Ports range",
				    &global_spdy_tcp_range, 65535);

    range_convert_str(&global_spdy_ssl_range, SSL_DEFAULT_RANGE, 65535);
    spdy_ssl_range = range_empty();
    prefs_register_range_preference(spdy_module, "ssl.port", "SSL/TLS Ports",
				    "SSL/TLS Ports range",
				    &global_spdy_ssl_range, 65535);

    spdy_handle = new_create_dissector_handle(dissect_spdy, proto_spdy);
    /*
     * Register for tapping
     */
    spdy_tap = register_tap("spdy"); /* SPDY statistics tap */
    spdy_eo_tap = register_tap("spdy_eo"); /* SPDY Export Object tap */
}

void
proto_reg_handoff_spdy(void)
{
    data_handle = find_dissector("data");
    media_handle = find_dissector("media");
    heur_dissector_add("tcp", dissect_spdy_heur, proto_spdy);
}

/*
 * Content-Type: message/http
 */

static gint proto_message_spdy = -1;
static gint ett_message_spdy = -1;

static void
dissect_message_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	proto_tree	*subtree;
	proto_item	*ti;
	gint		offset = 0, next_offset;
	gint		len;

	if (check_col(pinfo->cinfo, COL_INFO))
		col_append_str(pinfo->cinfo, COL_INFO, " (message/spdy)");
	if (tree) {
		ti = proto_tree_add_item(tree, proto_message_spdy,
				tvb, 0, -1, FALSE);
		subtree = proto_item_add_subtree(ti, ett_message_spdy);
		while (tvb_reported_length_remaining(tvb, offset) != 0) {
			len = tvb_find_line_end(tvb, offset,
					tvb_ensure_length_remaining(tvb, offset),
					&next_offset, FALSE);
			if (len == -1)
				break;
			proto_tree_add_text(subtree, tvb, offset, next_offset - offset,
					"%s", tvb_format_text(tvb, offset, len));
			offset = next_offset;
		}
	}
}

void
proto_register_message_spdy(void)
{
	static gint *ett[] = {
		&ett_message_spdy,
	};

	proto_message_spdy = proto_register_protocol(
			"Media Type: message/spdy",
			"message/spdy",
			"message-spdy"
	);
	proto_register_subtree_array(ett, array_length(ett));
}

void
proto_reg_handoff_message_spdy(void)
{
	dissector_handle_t message_spdy_handle;

	message_spdy_handle = create_dissector_handle(dissect_message_spdy,
			proto_message_spdy);

	dissector_add_string("media_type", "message/spdy", message_spdy_handle);

	reinit_spdy();
}