Add quick-and-dirty wireshark packet dissector for GWT code server
protocol.


Public review: http://gwt-code-reviews.appspot.com/191801

Patch by: jat
Review by: zundel


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7729 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/plugins/wireshark/Makefile b/plugins/wireshark/Makefile
new file mode 100644
index 0000000..a1be1f9
--- /dev/null
+++ b/plugins/wireshark/Makefile
@@ -0,0 +1,51 @@
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# Modify to point to your Wireshark and glib include directories
+INCS = -I/usr/include/wireshark -I/usr/include/glib-2.0 \
+       -I/usr/lib/glib-2.0/include -I../common
+
+CC   = gcc
+
+SRCS = packet-gwtcs.c
+OBJS = obj/packet-gwtcs.o
+
+PLUGIN_NAME = packet-gwtcs
+PLUGIN_DIR  = $(HOME)/.wireshark/plugins
+PLUGIN      = $(PLUGIN_NAME).so
+
+CFLAGS = -DHAVE_CONFIG_H $(INCS) -DINET6 -D_U_=__attribute__\(\(unused\)\) \
+	-Wall -Wpointer-arith -g -DXTHREADS -D_REENTRANT -DXUSE_MTSAFE_API \
+	-fPIC -DPIC -O2
+
+all::		obj/$(PLUGIN)
+
+install::	obj/$(PLUGIN)
+	cp obj/$(PLUGIN) $(PLUGIN_DIR)/$(PLUGIN)
+
+obj/$(PLUGIN) : $(OBJS)
+	mkdir -p $(PLUGIN_DIR)
+	$(CC) -shared $(OBJS) -o $@
+
+$(OBJS): obj
+
+obj:
+	mkdir obj
+
+obj/packet-gwtcs.o : packet-gwtcs.c ../common/BrowserChannel.h
+	$(CC) -c $(CFLAGS) $< -o $@
+
+clean:
+	rm -rf obj
diff --git a/plugins/wireshark/README.txt b/plugins/wireshark/README.txt
new file mode 100644
index 0000000..1ed9817
--- /dev/null
+++ b/plugins/wireshark/README.txt
@@ -0,0 +1,12 @@
+This is a quick and dirty Wireshark packet dissector for the GWT Code Server
+protocol.
+
+I have only tested this on Ubuntu Hardy with wireshark 1.0.0 on an x86_64
+machine.  It may require other changes for other platforms, and has only
+light testing.  It is also incomplete but provided enough decoding to be
+useful to me -- YMMV.
+
+The Makefile is very Unix-centric as it installs the library under your home
+directory.
+
+On Linux, you need the wireshark-dev package installed.
diff --git a/plugins/wireshark/packet-gwtcs.c b/plugins/wireshark/packet-gwtcs.c
new file mode 100644
index 0000000..fc12f8c
--- /dev/null
+++ b/plugins/wireshark/packet-gwtcs.c
@@ -0,0 +1,975 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/*
+ * Note that Wireshark dissectors are pure C, not C++ -- there are also
+ * restrictions on various C extensions, including things like all variables
+ * must be declared at the beginning of a block, and only initialized to
+ * scalar constants.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "BrowserChannel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gmodule.h>
+#include <epan/conversation.h>
+#include <epan/prefs.h>
+#include <epan/packet.h>
+
+/*
+ * Default port to follow.
+ */
+#define DEFAULT_GWTCS_PORT 9997
+
+/*
+ * Trim strngs at this length.
+ */
+#define MAX_STRING_LENGTH 100
+
+/* forward reference */
+void proto_register_gwtcs();
+void proto_reg_handoff_gwtcs();
+
+/* Define version if we are not building Wireshark statically */
+#ifndef ENABLE_STATIC
+G_MODULE_EXPORT const gchar version[] = "0.1";
+#endif
+
+/*
+ * Data stored for a GWT-CS conversation.
+ */
+typedef struct _gwtcs_data {
+  address       clientAddress;
+  guint32       clientPort;
+  int           level;
+} gwtcs_data;
+
+/*
+ * Names of packet types -- must be in order.
+ */
+static const value_string packetTypes[] = {
+  { MESSAGE_TYPE_INVOKE,                  "Invoke" },
+  { MESSAGE_TYPE_RETURN,                  "Return" },
+  { MESSAGE_TYPE_OLD_LOAD_MODULE,         "Old Load Module" },
+  { MESSAGE_TYPE_QUIT,                    "Quit" },
+  { MESSAGE_TYPE_LOADJSNI,                "Load JSNI" },
+  { MESSAGE_TYPE_INVOKESPECIAL,           "Invoke Special" },
+  { MESSAGE_TYPE_FREEVALUE,               "Free Value" },
+  { MESSAGE_TYPE_FATAL_ERROR,             "Fatal Error" },
+  { MESSAGE_TYPE_CHECK_VERSIONS,          "Check Versions" },
+  { MESSAGE_TYPE_PROTOCOL_VERSION,        "Protocol Version" },
+  { MESSAGE_TYPE_CHOOSE_TRANSPORT,        "Choose Transport" },
+  { MESSAGE_TYPE_SWITCH_TRANSPORT,        "Switch Transport" },
+  { MESSAGE_TYPE_LOAD_MODULE,             "Load Module" },
+  { 0,  NULL }
+};
+#define MAX_PACKET_TYPE MESSAGE_TYPE_LOAD_MODULE
+
+
+static const value_string valueTypes[] = {
+  {VALUE_TYPE_NULL,        "null" },
+  {VALUE_TYPE_BOOLEAN,     "boolean" },
+  {VALUE_TYPE_BYTE,        "byte" },
+  {VALUE_TYPE_CHAR,        "char" },
+  {VALUE_TYPE_SHORT,       "short" },
+  {VALUE_TYPE_INT,         "int" },
+  {VALUE_TYPE_LONG,        "long" },
+  {VALUE_TYPE_FLOAT,       "float" },
+  {VALUE_TYPE_DOUBLE,      "double" },
+  {VALUE_TYPE_STRING,      "string" },
+  {VALUE_TYPE_JAVA_OBJECT, "Java object" },
+  {VALUE_TYPE_JS_OBJECT,   "JS object" },
+  {VALUE_TYPE_UNDEFINED,   "undefined" },
+  { 0,  NULL }
+};
+
+/*
+ * InvokeSpecial types -- must be in order.
+ */
+static const value_string specialTypes[] = {
+  { SPECIAL_HAS_METHOD,   "hasMethod" },
+  { SPECIAL_HAS_PROPERTY, "hasProperty" },
+  { SPECIAL_GET_PROPERTY, "getProperty" },
+  { SPECIAL_SET_PROPERTY, "setProperty" },
+  { 0, NULL }
+};
+
+/*
+ * Dynamically assigned protocol ID.
+ */
+static int proto_gwtcs = -1;
+
+/*
+ * Dynamically assigned subtree IDs.
+ */
+static gint ett_gwtcs = -1;
+static gint ett_value = -1;
+static gint ett_args = -1;
+
+/*
+ * IDs for displayed values.
+ */
+static int hf_gwtcs_pdu_type = -1;
+static int hf_gwtcs_value_tag = -1;
+static int hf_gwtcs_min_vers = -1;
+static int hf_gwtcs_max_vers = -1;
+static int hf_gwtcs_hosted_vers = -1;
+static int hf_gwtcs_sel_vers = -1;
+static int hf_gwtcs_lm_ua = -1;
+static int hf_gwtcs_lm_tabkey = -1;
+static int hf_gwtcs_lm_seskey = -1;
+static int hf_gwtcs_lm_modname = -1;
+static int hf_gwtcs_lm_url = -1;
+static int hf_gwtcs_methname = -1;
+static int hf_gwtcs_isexc = -1;
+static int hf_gwtcs_dispid = -1;
+static int hf_gwtcs_jsni = -1;
+static int hf_gwtcs_val_hdr = -1;
+static int hf_gwtcs_val_bool = -1;
+static int hf_gwtcs_val_byte = -1;
+static int hf_gwtcs_val_char = -1;
+static int hf_gwtcs_val_short = -1;
+static int hf_gwtcs_val_int = -1;
+static int hf_gwtcs_val_long = -1;
+static int hf_gwtcs_val_float = -1;
+static int hf_gwtcs_val_double = -1;
+static int hf_gwtcs_val_string = -1;
+static int hf_gwtcs_val_javaobj = -1;
+static int hf_gwtcs_val_jsobj = -1;
+static int hf_gwtcs_val_null = -1;
+static int hf_gwtcs_val_undef = -1;
+static int hf_gwtcs_numargs = -1;
+static int hf_gwtcs_spectype = -1;
+static int hf_gwtcs_transport = -1;
+static int hf_gwtcs_transargs = -1;
+
+static dissector_handle_t gwtcs_handle;
+
+static GMemChunk* memChunk = 0;
+
+#ifndef ENABLE_STATIC
+G_MODULE_EXPORT void plugin_register(void)
+{
+   /* register the new protocol, protocol fields, and subtrees */
+   if (proto_gwtcs == -1) { /* execute protocol initialization only once */
+      proto_register_gwtcs();
+   }
+}
+
+G_MODULE_EXPORT void plugin_reg_handoff(void){
+   proto_reg_handoff_gwtcs();
+}
+#endif
+
+/*
+ * Get a string describing a Value from the packet, and return the total length
+ * (including the tag byte).
+ *
+ * ofs - offset into the buffer of the Value's tag byte
+ * buf - buffer to write string into; on return is guaranteed to be null
+ *     terminated
+ * buflen - length of buf
+ *
+ * returns the offset after the last byte of this Value
+ */
+static int getValue(tvbuff_t* tvb, int ofs, char* buf, int buflen) {
+  guint8 tag;
+  tag = tvb_get_guint8(tvb, ofs++);
+  int len = 0;
+  switch (tag) {
+    case VALUE_TYPE_NULL:
+      strncpy(buf, "null", buflen);
+      break;
+    case VALUE_TYPE_UNDEFINED:
+      strncpy(buf, "undef", buflen);
+      break;
+    case VALUE_TYPE_BOOLEAN:
+      {
+        guint8 val;
+        val = tvb_get_guint8(tvb, ofs);
+        len = 1;
+        strncpy(buf, val ? "true" : "false", buflen);
+      }
+      break;
+    case VALUE_TYPE_BYTE:
+      {
+        int val;
+        val = tvb_get_guint8(tvb, ofs);
+        if (val & 128) {
+          val -= 256;
+        }
+        len = 1;
+        snprintf(buf, buflen, "%d", val);
+      }
+      break;
+    case VALUE_TYPE_SHORT:
+      {
+        int val;
+        val = tvb_get_ntohs(tvb, ofs);
+        if (val & 0x8000) {
+          val -= 0x10000;
+        }
+        len = 2;
+        snprintf(buf, buflen, "%d", val);
+      }
+      break;
+    case VALUE_TYPE_CHAR:
+      {
+        int val;
+        val = tvb_get_ntohs(tvb, ofs);
+        len = 2;
+        /* show printable ASCII */
+        if (val >= 0x20 && val < 0x7f) {
+          snprintf(buf, buflen, "%d - %c", val, val);
+        } else {
+          snprintf(buf, buflen, "%d (U+%04x)", val, val);
+        }
+      }
+      break;
+    case VALUE_TYPE_INT:
+      {
+        int val;
+        val = tvb_get_ntohl(tvb, ofs);
+        len = 4;
+        snprintf(buf, buflen, "%d", val);
+      }
+      break;
+    case VALUE_TYPE_FLOAT:
+      {
+        float val;
+        val = tvb_get_ntohieee_float(tvb, ofs);
+        len = 4;
+        snprintf(buf, buflen, "%g", val);
+      }
+      break;
+    case VALUE_TYPE_JAVA_OBJECT:
+      {
+        int val;
+        val = tvb_get_ntohl(tvb, ofs);
+        len = 4;
+        snprintf(buf, buflen, "Java Object %d", val);
+      }
+      break;
+    case VALUE_TYPE_JS_OBJECT:
+      {
+        int val;
+        val = tvb_get_ntohl(tvb, ofs);
+        len = 4;
+        snprintf(buf, buflen, "JS Object %d", val);
+      }
+      break;
+    case VALUE_TYPE_LONG:
+      {
+        guint64 val;
+        val = tvb_get_ntoh64(tvb, ofs);
+        len = 8;
+        /* no portable way to print guint64, so do it in two pieces */
+        snprintf(buf, buflen, "0x%08x%08x", (int) ((val >> 32) & 0xFFFFFFFF),
+            (int) (val & 0xFFFFFFFF));
+      }
+      break;
+    case VALUE_TYPE_DOUBLE:
+      {
+        double val;
+        val = tvb_get_ntohieee_double(tvb, ofs);
+        len = 8;
+        snprintf(buf, buflen, "%lg", val);
+      }
+      break;
+    case VALUE_TYPE_STRING:
+      {
+        guint8* str;
+        len = tvb_get_ntohl(tvb, ofs);
+        ofs += 4;
+        str = tvb_get_ephemeral_string(tvb, ofs, len);
+        if (len > buflen - 3 && buflen >= 6) {
+          snprintf(buf, buflen, "\"%.*s...\"", buflen - 6, (char*) str);
+        } else {
+          snprintf(buf, buflen, "\"%s\"", (char*) str);
+        }
+      }
+      break;
+  }
+  /* ensure the buffer is null-terminated */;
+  buf[buflen - 1] = 0;
+
+  /* point to byte after this Value */
+  return ofs + len;
+}
+
+/*
+ * Show a labelled Value.
+ *
+ * hdr - name of this Value
+ * ofs - offset into buffer where the Value tag byte is located.
+ *
+ * returns the offset after the last byte of this Value
+ */
+static int showValue(proto_tree* tree, char* hdr, tvbuff_t* tvb, int ofs) {
+  proto_tree* subtree;
+  proto_item* ti;
+  guint8 tag;
+  int newOffset;
+  char buf[40];
+  *buf = 0;
+  newOffset = getValue(tvb, ofs, buf, sizeof(buf));
+  tag = tvb_get_guint8(tvb, ofs);
+  ti = proto_tree_add_string_format(tree, hf_gwtcs_val_hdr, tvb, ofs,
+      newOffset - ofs, 0, "%s: %s", hdr, buf);
+  subtree = proto_item_add_subtree(ti, ett_value);
+  proto_tree_add_item(subtree, hf_gwtcs_value_tag, tvb, ofs++, 1, FALSE);
+  switch (tag) {
+    case VALUE_TYPE_NULL:
+      proto_tree_add_item(subtree, hf_gwtcs_val_null, tvb, ofs, 0, FALSE);
+      break;
+    case VALUE_TYPE_UNDEFINED:
+      proto_tree_add_item(subtree, hf_gwtcs_val_undef, tvb, ofs, 0, FALSE);
+      break;
+    case VALUE_TYPE_BOOLEAN:
+      proto_tree_add_item(subtree, hf_gwtcs_val_bool, tvb, ofs, 1, FALSE);
+      break;
+    case VALUE_TYPE_BYTE:
+      proto_tree_add_item(subtree, hf_gwtcs_val_byte, tvb, ofs, 1, FALSE);
+      break;
+    case VALUE_TYPE_CHAR:
+      proto_tree_add_item(subtree, hf_gwtcs_val_char, tvb, ofs, 2, FALSE);
+      break;
+    case VALUE_TYPE_SHORT:
+      proto_tree_add_item(subtree, hf_gwtcs_val_short, tvb, ofs, 2, FALSE);
+      break;
+    case VALUE_TYPE_INT:
+      proto_tree_add_item(subtree, hf_gwtcs_val_int, tvb, ofs, 4, FALSE);
+      break;
+    case VALUE_TYPE_LONG:
+      proto_tree_add_item(subtree, hf_gwtcs_val_long, tvb, ofs, 8, FALSE);
+      break;
+    case VALUE_TYPE_FLOAT:
+      proto_tree_add_item(subtree, hf_gwtcs_val_float, tvb, ofs, 4, FALSE);
+      break;
+    case VALUE_TYPE_DOUBLE:
+      proto_tree_add_item(subtree, hf_gwtcs_val_double, tvb, ofs, 8, FALSE);
+      break;
+    case VALUE_TYPE_STRING:
+      proto_tree_add_item(subtree, hf_gwtcs_val_string, tvb, ofs, 4, FALSE);
+      break;
+    case VALUE_TYPE_JAVA_OBJECT:
+      proto_tree_add_item(subtree, hf_gwtcs_val_javaobj, tvb, ofs, 4, FALSE);
+      break;
+    case VALUE_TYPE_JS_OBJECT:
+      proto_tree_add_item(subtree, hf_gwtcs_val_jsobj, tvb, ofs, 4, FALSE);
+      break;
+  }
+  return newOffset;
+}
+
+/*
+ * Initialize memchunk system.
+ */
+static void init() {
+  if (memChunk) {
+    g_mem_chunk_destroy(memChunk);
+  }
+  memChunk = g_mem_chunk_new("gwtcs data", sizeof(gwtcs_data),
+                             20 * sizeof(gwtcs_data), G_ALLOC_AND_FREE);
+}
+
+/*
+ * Dissect a single packet.
+ */
+static int dissect_gwtcs(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+  guint8 packetType;
+  conversation_t* conv;
+  gwtcs_data* data = 0;
+  int isClient = 0;
+
+  if (tvb_length(tvb) < 1) {
+    return 0;
+  }
+
+  packetType = tvb_get_guint8(tvb, 0);
+  if (packetType > MAX_PACKET_TYPE) {
+    return 0;
+  }
+
+  if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
+    col_set_str(pinfo->cinfo, COL_PROTOCOL, "GWT-CS");
+  }
+
+  /* Clear the info column */
+  if (check_col(pinfo->cinfo, COL_INFO)) {
+    col_set_str(pinfo->cinfo, COL_INFO, "");
+  }
+
+  /* get the conversation */
+  conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
+                           pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+  if (!conv) {
+    conv = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst,
+                            pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+  }
+  data = (gwtcs_data*) conversation_get_proto_data(conv, proto_gwtcs);
+  if (!data) {
+    data = (gwtcs_data*) g_mem_chunk_alloc(memChunk);
+    data->clientPort = -1;
+    data->level = 0;
+    conversation_add_proto_data(conv, proto_gwtcs, data);
+  }
+
+  if (packetType == MESSAGE_TYPE_CHECK_VERSIONS) {
+    data->clientAddress = pinfo->src;
+    data->clientPort = pinfo->srcport;
+  }
+
+  if (data->clientPort == pinfo->srcport) {
+    isClient = 1;
+  }
+
+  /* Set the info column */
+  if (check_col(pinfo->cinfo,COL_INFO)) {
+    gint32 len;
+    guint8* str;
+    int i;
+    gint32 offset = 1;
+    if (data->clientPort != -1) {
+      col_add_str(pinfo->cinfo, COL_INFO, isClient ? "C->S: " : "S->C: ");
+    }
+    for (i = 0; i < data->level; ++i) {
+      col_append_str(pinfo->cinfo, COL_INFO, " ");
+    }
+    col_append_str(pinfo->cinfo, COL_INFO, packetTypes[packetType].strptr);
+    switch (packetType) {
+      case MESSAGE_TYPE_CHECK_VERSIONS:
+        {
+          int minvers, maxvers;
+          minvers = tvb_get_ntohl(tvb, offset);
+          offset += 4;
+          maxvers = tvb_get_ntohl(tvb, offset);
+          col_append_fstr(pinfo->cinfo, COL_INFO, " vers=%d-%d", minvers,
+              maxvers);
+        }
+        break;
+      case MESSAGE_TYPE_PROTOCOL_VERSION:
+        {
+          int vers;
+          vers = tvb_get_ntohl(tvb, offset);
+          col_append_fstr(pinfo->cinfo, COL_INFO, " vers=%d", vers);
+        }
+        break;
+      case MESSAGE_TYPE_LOAD_MODULE:
+        data->level++;
+        // URL
+        len = tvb_get_ntohl(tvb, offset);
+        offset += 4 + len;
+        // tab key
+        len = tvb_get_ntohl(tvb, offset);
+        offset += 4 + len;
+        // session key
+        len = tvb_get_ntohl(tvb, offset);
+        offset += 4 + len;
+        // module name
+        len = tvb_get_ntohl(tvb, offset);
+        offset  += 4;
+        // clip string
+        if (len > MAX_STRING_LENGTH) {
+          len = MAX_STRING_LENGTH;
+        }
+        str = tvb_get_ephemeral_string(tvb, offset, len);
+        col_append_fstr(pinfo->cinfo, COL_INFO, " %s", str);
+        break;
+      case MESSAGE_TYPE_INVOKE:
+        data->level++;
+        if (data->clientPort == -1) {
+          break;
+        }
+        if (isClient) {
+          int dispId;
+          dispId = tvb_get_ntohl(tvb, offset);
+          offset += 4;
+          col_append_fstr(pinfo->cinfo, COL_INFO, " dispid=%d", dispId);
+        } else {
+          // module name
+          len = tvb_get_ntohl(tvb, offset);
+          offset += 4;
+          // clip string
+          if (len > MAX_STRING_LENGTH) {
+            len = MAX_STRING_LENGTH;
+          }
+          str = tvb_get_ephemeral_string(tvb, offset, len);
+          col_append_fstr(pinfo->cinfo, COL_INFO, " %s", str);
+        }
+        break;
+      case MESSAGE_TYPE_RETURN:
+        {
+          char buf[40];
+          guint8 isexc;
+          data->level--;
+          isexc = tvb_get_guint8(tvb, 1);
+          getValue(tvb, 2, buf, sizeof(buf));
+          col_append_fstr(pinfo->cinfo, COL_INFO, " %s%s",
+              isexc ? "** EXCEPTION ** " : "", buf);
+        }
+        break;
+      case MESSAGE_TYPE_INVOKESPECIAL:
+        {
+          guint8 specialType;
+          data->level++;
+          specialType = tvb_get_guint8(tvb, 1);
+          col_append_fstr(pinfo->cinfo, COL_INFO, " %s",
+              specialTypes[specialType].strptr);
+        }
+        break;
+    }
+  }
+
+  /*
+   * If tree is non-null, then we want to get the detailed data.
+   */
+  if (tree) {
+    proto_item *ti = 0;
+    proto_tree *gwtcs_tree = 0;
+    gint32 offset = 1;
+
+    ti = proto_tree_add_item(tree, proto_gwtcs, tvb, 0 , -1, FALSE);
+    gwtcs_tree = proto_item_add_subtree(ti, ett_gwtcs);
+    proto_tree_add_item(gwtcs_tree, hf_gwtcs_pdu_type, tvb, 0, 1, FALSE);
+    switch (packetType) {
+      case MESSAGE_TYPE_CHECK_VERSIONS:
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_min_vers, tvb, 1, 4, FALSE);
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_max_vers, tvb, 5, 4, FALSE);
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_hosted_vers, tvb, 9, 4, FALSE);
+        break;
+      case MESSAGE_TYPE_PROTOCOL_VERSION:
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_sel_vers, tvb, 1, 4, FALSE);
+        break;
+      case MESSAGE_TYPE_LOAD_MODULE:
+        {
+          guint32 len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_lm_url, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_lm_tabkey, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_lm_seskey, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_lm_modname, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_lm_ua, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+        }
+        break;
+      case MESSAGE_TYPE_INVOKE:
+        {
+          int numArgs, len, i;
+          proto_item* ti;
+          proto_tree* subtree;
+          if (data->clientPort == -1) {
+            proto_tree_add_text(gwtcs_tree, tvb, 1, -1,
+                "Can't decode - unknown direction");
+            break;
+          }
+          if (isClient) {
+            proto_tree_add_item(gwtcs_tree, hf_gwtcs_dispid, tvb, offset, 4,
+                FALSE);
+            offset += 4;
+          } else {
+            // method name
+            len = tvb_get_ntohl(tvb, offset);
+            proto_tree_add_item(gwtcs_tree, hf_gwtcs_methname, tvb, offset, 4,
+                FALSE);
+            offset += 4 + len;
+          }
+          offset = showValue(gwtcs_tree, "This Value", tvb, offset);
+          numArgs = tvb_get_ntohl(tvb, offset);
+          ti = proto_tree_add_item(gwtcs_tree, hf_gwtcs_numargs, tvb, offset, 4,
+              FALSE);
+          subtree = proto_item_add_subtree(ti, ett_args);
+          offset += 4;
+          for (i = 0; i < numArgs; ++i) {
+            char argName[10];
+            snprintf(argName, sizeof(argName), "arg%d", i);
+            offset = showValue(subtree, argName, tvb, offset);
+          }
+        }
+        break;
+      case MESSAGE_TYPE_RETURN:
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_isexc, tvb, 1, 1, FALSE);
+        showValue(gwtcs_tree, "Return Value", tvb, 2);
+        break;
+      case MESSAGE_TYPE_LOADJSNI:
+        proto_tree_add_item(gwtcs_tree, hf_gwtcs_jsni, tvb, 1, 4, FALSE);
+        break;
+      case MESSAGE_TYPE_INVOKESPECIAL:
+        {
+          int numArgs, i;
+          proto_item* ti;
+          proto_tree* subtree;
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_spectype, tvb, offset++, 1,
+              FALSE);
+          numArgs = tvb_get_ntohl(tvb, offset);
+          ti = proto_tree_add_item(gwtcs_tree, hf_gwtcs_numargs, tvb, offset, 4,
+              FALSE);
+          offset += 4;
+          subtree = proto_item_add_subtree(ti, ett_args);
+          for (i = 0; i < numArgs; ++i) {
+            char argName[10];
+            snprintf(argName, sizeof(argName), "arg%d", i);
+            offset = showValue(subtree, argName, tvb, offset);
+          }
+        }
+        break;
+      case MESSAGE_TYPE_FREEVALUE:
+        {
+          int numArgs, i, label;
+          proto_item* ti;
+          proto_tree* subtree;
+          numArgs = tvb_get_ntohl(tvb, offset);
+          ti = proto_tree_add_item(gwtcs_tree, hf_gwtcs_numargs, tvb, offset, 4,
+              FALSE);
+          offset += 4;
+          subtree = proto_item_add_subtree(ti, ett_args);
+          label = isClient ? hf_gwtcs_val_jsobj : hf_gwtcs_val_javaobj;
+          for (i = 0; i < numArgs; ++i) {
+            proto_tree_add_item(subtree, label, tvb, offset, 4, FALSE);
+            offset += 4;
+          }
+        }
+        break;
+      case MESSAGE_TYPE_CHOOSE_TRANSPORT:
+        {
+          int numArgs, i, len;
+          proto_item* ti;
+          proto_tree* subtree;
+          numArgs = tvb_get_ntohl(tvb, offset);
+          ti = proto_tree_add_item(gwtcs_tree, hf_gwtcs_numargs, tvb, offset, 4,
+              FALSE);
+          offset += 4;
+          subtree = proto_item_add_subtree(ti, ett_args);
+          for (i = 0; i < numArgs; ++i) {
+            len = tvb_get_ntohl(tvb, offset);
+            proto_tree_add_item(subtree, hf_gwtcs_transport, tvb, offset, 4,
+                FALSE);
+            offset += 4 + len;
+          }
+        }
+        break;
+      case MESSAGE_TYPE_SWITCH_TRANSPORT:
+        {
+          int len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_transport, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+          len = tvb_get_ntohl(tvb, offset);
+          proto_tree_add_item(gwtcs_tree, hf_gwtcs_transargs, tvb, offset, 4,
+              FALSE);
+          offset += 4 + len;
+        }
+        break;
+    }
+  }
+
+  return tvb_length(tvb);
+}
+
+void proto_register_gwtcs(void)
+{
+  /*
+   * List of subtree identifiers to be allocated.
+   */
+  static gint *ett[] = {
+    &ett_gwtcs,
+    &ett_value,
+    &ett_args,
+  };
+
+  /*
+   * List of display identifiers to be allocated.
+   */
+  static hf_register_info hf[] = {
+    {
+      &hf_gwtcs_pdu_type,
+      {
+        "Packet Type", "gwtcs.type",
+        FT_UINT8, BASE_DEC, VALS(packetTypes), 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_spectype,
+      {
+        "Type", "gwtcs.spectype",
+        FT_UINT8, BASE_DEC, VALS(specialTypes), 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_value_tag,
+      {
+        "Value Tag", "gwtcs.value.tag",
+        FT_UINT8, BASE_DEC, VALS(valueTypes), 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_min_vers,
+      {
+        "Minimum version", "gwtcs.minvers",
+        FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_max_vers,
+      {
+        "Maximum version", "gwtcs.maxvers",
+        FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_hosted_vers,
+      {
+        "hosted.html version", "gwtcs.hostedvers",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_sel_vers,
+      {
+        "Selected version", "gwtcs.selvers",
+        FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_lm_url,
+      {
+        "URL", "gwtcs.lm.url",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_lm_modname,
+      {
+        "Module Name", "gwtcs.lm.modname",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_lm_tabkey,
+      {
+        "Tab Key", "gwtcs.lm.tabkey",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_lm_seskey,
+      {
+        "Session Key", "gwtcs.lm.seskey",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_lm_ua,
+      {
+        "User Agent", "gwtcs.lm.ua",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_methname,
+      {
+        "Method Name", "gwtcs.lm.methname",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_isexc,
+      {
+        "Is Exception", "gwtcs.isexc",
+        FT_BOOLEAN, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_dispid,
+      {
+        "Dispatch ID", "gwtcs.dispid",
+        FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_jsni,
+      {
+        "JSNI Source", "gwtcs.jsni",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_hdr,
+      {
+        "Value", "gwtcs.val.hdr",
+        FT_STRINGZ, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_numargs,
+      {
+        "# of Arguments", "gwtcs.val.numargs",
+        FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_null,
+      {
+        "Null Value", "gwtcs.val.null",
+        FT_NONE, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_undef,
+      {
+        "Undef Value", "gwtcs.val.undef",
+        FT_NONE, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_bool,
+      {
+        "Boolean Value", "gwtcs.val.bool",
+        FT_BOOLEAN, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_byte,
+      {
+        "Byte Value", "gwtcs.val.byte",
+        FT_INT8, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_char,
+      {
+        "Char Value", "gwtcs.val.char",
+        FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_short,
+      {
+        "Short Value", "gwtcs.val.short",
+        FT_INT16, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_int,
+      {
+        "Int Value", "gwtcs.val.int",
+        FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_long,
+      {
+        "Long Value", "gwtcs.val.long",
+        FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_float,
+      {
+        "Float Value", "gwtcs.val.float",
+        FT_FLOAT, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_double,
+      {
+        "Double Value", "gwtcs.val.double",
+        FT_DOUBLE, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_javaobj,
+      {
+        "Java Object Id", "gwtcs.val.javaobj",
+        FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_jsobj,
+      {
+        "JS Object Id", "gwtcs.val.jsobj",
+        FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_val_string,
+      {
+        "String Value", "gwtcs.val.string",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_transport,
+      {
+        "Transport Name", "gwtcs.transport",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+    {
+      &hf_gwtcs_transargs,
+      {
+        "Transport Args", "gwtcs.transargs",
+        FT_UINT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL,
+      }
+    },
+  };
+
+  if (proto_gwtcs == -1)
+  {
+     register_init_routine(&init);
+     proto_gwtcs = proto_register_protocol (
+        "GWT Code Server Protocol", /* name */
+        "GWT-CS",          /* short name */
+        "gwtcs"           /* abbrev */
+     );
+
+     proto_register_field_array(proto_gwtcs, hf, array_length(hf));
+     proto_register_subtree_array(ett, array_length(ett));
+  }
+}
+
+void proto_reg_handoff_gwtcs(void)
+{
+   static int Initialized=FALSE;
+
+   /* register with wireshark to dissect tdp packets on port 9997 */
+   if (!Initialized) {
+      gwtcs_handle = new_create_dissector_handle(dissect_gwtcs, proto_gwtcs);
+      dissector_add("tcp.port", DEFAULT_GWTCS_PORT, gwtcs_handle);
+      Initialized = TRUE;
+   }
+}