/* packet-jdwp.c
 * Routines for JDWP (Java Debug Wire Protocol) dissection
 * Copyright 2020, Eugene Adell <eugene.adell@gmail.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <stdio.h>

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>
#include <epan/to_str.h>
#include <epan/conversation.h>
#include <epan/wmem/wmem.h>

#include "packet-tcp.h"

void proto_register_jdwp(void);
void proto_reg_handoff_jdwp(void);

/* IMPORTANT IMPLEMENTATION NOTES
 *
 * You need to be looking at:
 *
 *     https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/jdwp-spec.html
 *
 *
 */

#define JDWP_PORT 9009 /* Not IANA registered */
#define FRAME_HEADER_LEN 11
#define JDWP_MIN_LENGTH 11
#define JDWP_HANDSHAKE_LENGTH 14
#define JDWP_HANDSHAKE_MSG "JDWP-Handshake"

#define PACKET_TYPE_COMMAND 0
#define PACKET_TYPE_REPLY 128

#define COMMAND_SET_VIRTUALMACHINE 1
#define COMMAND_SET_REFERENCETYPE 2
#define COMMAND_SET_CLASSTYPE 3
#define COMMAND_SET_ARRAYTYPE 4
#define COMMAND_SET_INTERFACETYPE 5
#define COMMAND_SET_METHOD 6
#define COMMAND_SET_FIELD 8
#define COMMAND_SET_OBJECTREFERENCE 9
#define COMMAND_SET_STRINGREFERENCE 10
#define COMMAND_SET_THREADREFERENCE 11
#define COMMAND_SET_THREADGROUPREFERENCE 12
#define COMMAND_SET_ARRAYREFERENCE 13
#define COMMAND_SET_CLASSLOADERREFERENCE 14
#define COMMAND_SET_EVENTREQUEST 15
#define COMMAND_SET_STACKFRAME 16
#define COMMAND_SET_CLASSOBJECTREFERENCE 17
#define COMMAND_SET_EVENT 64

static int proto_jdwp = -1;

static int hf_jdwp_type = -1;
static int hf_jdwp_length = -1;
static int hf_jdwp_id = -1;
static int hf_jdwp_flags = -1;
static int hf_jdwp_commandset = -1;
static int hf_jdwp_commandset_virtualmachine = -1;
static int hf_jdwp_commandset_referencetype = -1;
static int hf_jdwp_commandset_classtype = -1;
static int hf_jdwp_commandset_arraytype = -1;
static int hf_jdwp_commandset_interfacetype = -1;
static int hf_jdwp_commandset_method = -1;
static int hf_jdwp_commandset_field = -1;
static int hf_jdwp_commandset_objectreference = -1;
static int hf_jdwp_commandset_stringreference = -1;
static int hf_jdwp_commandset_threadreference = -1;
static int hf_jdwp_commandset_threadgroupreference = -1;
static int hf_jdwp_commandset_arrayreference = -1;
static int hf_jdwp_commandset_classloaderreference = -1;
static int hf_jdwp_commandset_eventrequest = -1;
static int hf_jdwp_commandset_stackframe = -1;
static int hf_jdwp_commandset_classobjectreference = -1;
static int hf_jdwp_commandset_event = -1;
static int hf_jdwp_errorcode = -1;
static int hf_jdwp_data = -1;

static gint ett_jdwp = -1;

static expert_field ei_jdwp_hlen_invalid  = EI_INIT;
static expert_field ei_jdwp_flags_invalid  = EI_INIT;

// contains the command set names
static const value_string commandsetnames[] = {
  {1, "VirtualMachine"},
  {2, "ReferenceType"},
  {3, "ClassType"},
  {4, "ArrayType"},
  {5, "InterfaceType"},
  {6, "Method"},
  {8, "Field"},
  {9, "ObjectReference"},
  {10, "StringReference"},
  {11, "ThreadReference"},
  {12, "ThreadGroupReference"},
  {13, "ArrayReference"},
  {14, "ClassLoaderReference"},
  {15, "EventRequest"},
  {16, "StackFrame"},
  {17, "ClassObjectReference"},
  {64, "Event"},
  {0, NULL}
};

// contains the commands for the command set of type Virtual Machine
static const value_string commandset_virtualmachine[] = {
  {1, "Version"},
  {2, "ClassesBySignature"},
  {3, "AllClasses"},
  {4, "AllThreads"},
  {5, "TopLevelThreadGroups"},
  {6, "Dispose"},
  {7, "IDSizes"},
  {8, "Suspend"},
  {9, "Resume"},
  {10, "Exit"},
  {11, "CreateString"},
  {12, "Capabilities"},
  {13, "ClassPaths"},
  {14, "DisposeObjects"},
  {15, "HoldEvents"},
  {16, "ReleaseEvents"},
  {17, "CapabilitiesNew"},
  {18, "RedefineClasses"},
  {19, "SetDefaultStratum"},
  {20, "AllClassesWithGeneric"},
  {21, "InstanceCounts"},
  {0, NULL}
};

// contains the commands for the command set of type Reference
static const value_string commandset_referencetype[] = {
  {1, "Signature"},
  {2, "ClassLoader"},
  {3, "Modifiers"},
  {4, "Fields"},
  {5, "Methods"},
  {6, "GetValues"},
  {7, "SourceFile"},
  {8, "NestedTypes"},
  {9, "Status"},
  {10, "Interfaces"},
  {11, "ClassObject"},
  {12, "SourceDebugExtension"},
  {13, "SignatureWithGeneric"},
  {14, "FieldsWithGeneric"},
  {15, "MethodsWithGeneric"},
  {16, "Instances"},
  {17, "ClassFileVersion"},
  {18, "ConstantPool"},
  {0, NULL}
};

// contains the commands for the command set of type Class
static const value_string commandset_classtype[] = {
  {1, "Superclass"},
  {2, "SetValues"},
  {3, "InvokeMethod"},
  {4, "NewInstance"},
  {0, NULL}
};

// contains the commands for the command set of type Array
static const value_string commandset_arraytype[] = {
  {1, "NewInstance"},
  {0, NULL}
};

// contains the commands for the command set of type Interface
static const value_string commandset_interfacetype[] = {
  {1, "InvokeMethod"},
  {0, NULL}
};

// contains the commands for the command set of type Method
static const value_string commandset_method[] = {
  {1, "LineTable"},
  {2, "VariableTable"},
  {3, "Bytecodes"},
  {4, "IsObsolete"},
  {5, "VariableTableWithGeneric"},
  {0, NULL}
};

// contains the commands for the command set of type Field
static const value_string commandset_field[] = {
  {0, NULL}
};

// contains the commands for the command set of type Object Reference
static const value_string commandset_objectreference[] = {
  {1, "ReferenceType"},
  {2, "GetValues"},
  {3, "SetValues"},
  {5, "MonitorInfo"},
  {6, "InvokeMethod"},
  {7, "DisableCollection"},
  {8, "EnableCollection"},
  {9, "IsCollected"},
  {10, "ReferringObjects"},
  {0, NULL}
};

// contains the commands for the command set of type String Reference
static const value_string commandset_stringreference[] = {
  {1, "Value"},
  {0, NULL}
};

// contains the commands for the command set of type Thread Reference
static const value_string commandset_threadreference[] = {
  {1, "Name"},
  {2, "Suspend"},
  {3, "Resume"},
  {4, "Status"},
  {5, "ThreadGroup"},
  {6, "Frames"},
  {7, "FrameCount"},
  {8, "OwnedMonitors"},
  {9, "CurrentContentedMonitor"},
  {10, "Stop"},
  {11, "Interrupt"},
  {12, "SuspendCount"},
  {13, "OwnedMonitorsStackDepthInfo"},
  {14, "ForceEarlyReturn"},
  {0, NULL}
};

// contains the commands for the command set of type ThreadGroup Reference
static const value_string commandset_threadgroupreference[] = {
  {1, "Name"},
  {2, "Parent"},
  {3, "Children"},
  {0, NULL}
};

// contains the commands for the command set of type Array Reference
static const value_string commandset_arrayreference[] = {
  {1, "Length"},
  {2, "GetValues"},
  {3, "SetValues"},
  {0, NULL}
};

// contains the commands for the command set of type ClassLoader Reference
static const value_string commandset_classloaderreference[] = {
  {1, "VisibleClasses"},
  {0, NULL}
};

// contains the commands for the command set of type EventRequest
static const value_string commandset_eventrequest[] = {
  {1, "Set"},
  {2, "Clear"},
  {3, "ClearAllBreakpoints"},
  {0, NULL}
};

// contains the commands for the command set of type StackFrame
static const value_string commandset_stackframe[] = {
  {1, "GetValues"},
  {2, "SetValues"},
  {3, "ThisObject"},
  {4, "PopFrames"},
  {0, NULL}
};

// contains the commands for the command set of type ClassObject Reference
static const value_string commandset_classobjectreference[] = {
  {1, "ReflectedType"},
  {0, NULL}
};

// contains the commands for the command set of type Event
static const value_string commandset_event[] = {
  {100, "Composite"},
  {0, NULL}
};

/* translates the error code to human readable value
 * value 0 ("NONE") means SUCCESS, all other values mean FAILURE
 */
static const value_string error_codes[] = {
  {0, "NONE"},
  {10, "INVALID_THREAD"},
  {11, "INVALID_THREAD_GROUP"},
  {12, "INVALID_PRIORITY"},
  {13, "THREAD_NOT_SUSPENDED"},
  {14, "THREAD_SUSPENDED"},
  {20, "INVALID_OBJECT"},
  {21, "INVALID_CLASS"},
  {22, "CLASS_NOT_PREPARED"},
  {23, "INVALID_METHODID"},
  {24, "INVALID_LOCATION"},
  {25, "INVALID_FIELDID"},
  {30, "INVALID_FRAMEID"},
  {31, "NO_MORE_FRAMES"},
  {32, "OPAQUE_FRAME"},
  {33, "NOT_CURRENT_FRAME"},
  {34, "TYPE_MISMATCH"},
  {35, "INVALID_SLOT"},
  {40, "DUPLICATE"},
  {41, "NOT_FOUND"},
  {50, "INVALID_MONITOR"},
  {51, "NOT_MONITOR_OWNER"},
  {52, "INTERRUPT"},
  {60, "INVALID_CLASS_FORMAT"},
  {61, "CIRCULAR_CLASS_DEFINITION"},
  {62, "FAILS_VERIFICATION"},
  {63, "ADD_METHOD_NOT_IMPLEMENTED"},
  {64, "SCHEMA_CHANGE_NOT_IMPLEMENTED"},
  {65, "INVALID_TYPESTATE"},
  {66, "HIERARCHY_CHANGE_NOT_IMPLEMENTED"},
  {67, "DELETE_METHOD_NOT_IMPLEMENTED"},
  {68, "UNSUPPORTED_VERSION"},
  {69, "NAMES_DONT_MATCH"},
  {70, "CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED"},
  {71, "METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED"},
  {99, "NOT_IMPLEMENTED"},
  {100, "NULL_POINTER"},
  {101, "ABSENT_INFORMATION"},
  {102, "INVALID_EVENT_TYPE"},
  {103, "ILLEGAL_ARGUMENT"},
  {110, "OUT_OF_MEMORY"},
  {111, "ACCESS_DENIED"},
  {112, "VM_DEAD"},
  {113, "INTERNAL"},
  {115, "UNATTACHED_THREAD"},
  {500, "INVALID_TAG"},
  {502, "ALREADY_INVOKING"},
  {503, "INVALID_INDEX"},
  {504, "INVALID_LENGTH"},
  {506, "INVALID_STRING"},
  {507, "INVALID_CLASS_LOADER"},
  {508, "INVALID_ARRAY"},
  {509, "TRANSPORT_LOAD"},
  {510, "TRANSPORT_INIT"},
  {511, "NATIVE_METHOD"},
  {512, "INVALID_COUNT"},
  {0, NULL}
};

/* determine PDU length of protocol JDWP */
static guint
get_jdwp_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
{
  /* Handshake messages don't contain the length field and
   * they all are strictly identical in length and content
   */
  if (tvb_reported_length(tvb) == JDWP_HANDSHAKE_LENGTH) {
    if (tvb_strneql(tvb, offset, JDWP_HANDSHAKE_MSG, JDWP_HANDSHAKE_LENGTH) == 0) {
      return JDWP_HANDSHAKE_LENGTH;
    }
  }

  /* All other packets are either a Command or a Reply, of different lengths
   * and this length is indicated on the 4 first bytes
   */
  return (guint)tvb_get_ntohl(tvb, offset);
}

static int
dissect_jdwp_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
  int offset = 0;

  /* packet type can take 3 values (handshake, command, reply) */
  gint packet_type;

  /* length */
  gint32 hlen = 0;

  /* flag can take 2 values (0, 128) */
  guint32 flags;

  /* fields that need to be remembered */
  guint32 mem_commandset = -1;
  guint32 mem_errorcode = -1;

  /* Check that there's enough data */
  if (tvb_reported_length(tvb) < JDWP_MIN_LENGTH)
    return 0;

  /* Set the Protocol Column */
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "JDWP");
  col_clear(pinfo->cinfo, COL_INFO);

  proto_item *ti, *hlen_item, *flags_item;
  proto_tree *jdwp_tree;

  /* Clear out stuff in the info column */
  col_clear(pinfo->cinfo,COL_INFO);

  ti = proto_tree_add_item(tree, proto_jdwp, tvb, 0, -1, ENC_NA);
  jdwp_tree = proto_item_add_subtree(ti, ett_jdwp);

  /* The two first packets are Handshake packets and
   * their content is always "JDWP-Handshake"
   * All other packets are either a Command or a Reply
   */
  packet_type = 1;
  if (tvb_reported_length(tvb) == JDWP_HANDSHAKE_LENGTH) {
    if (tvb_strneql(tvb, offset, JDWP_HANDSHAKE_MSG, JDWP_HANDSHAKE_LENGTH) == 0) {
      col_append_fstr(pinfo->cinfo, COL_INFO, "JDWP Handshake");
      packet_type = 0;
    }
  }

  if (packet_type == 0) {
    proto_tree_add_item(jdwp_tree, hf_jdwp_type, tvb, offset, 14, ENC_ASCII|ENC_NA);
    return tvb_captured_length(tvb);
  }

  /* LENGTH
   */
  hlen_item = proto_tree_add_item_ret_uint(jdwp_tree, hf_jdwp_length, tvb, offset, 4, ENC_BIG_ENDIAN, &hlen);
  offset += 4;

  /* ID
   */
  proto_tree_add_item(jdwp_tree, hf_jdwp_id, tvb, offset, 4, ENC_BIG_ENDIAN);
  offset += 4;

  /* FLAGS
   */
  flags_item = proto_tree_add_item_ret_uint(jdwp_tree, hf_jdwp_flags, tvb, offset, 1, ENC_BIG_ENDIAN, &flags);
  offset += 1;

  /* COMMAND
   */
  switch (flags) {
    case PACKET_TYPE_COMMAND:
      col_append_fstr(pinfo->cinfo, COL_INFO, "Command");
      proto_tree_add_item_ret_uint(jdwp_tree, hf_jdwp_commandset, tvb, offset, 1, ENC_BIG_ENDIAN, &mem_commandset);
      offset += 1;

      switch (mem_commandset) {

        case COMMAND_SET_VIRTUALMACHINE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_virtualmachine, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_REFERENCETYPE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_referencetype, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_CLASSTYPE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_classtype, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_ARRAYTYPE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_arraytype, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_INTERFACETYPE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_interfacetype, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_METHOD:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_method, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_FIELD:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_field, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_OBJECTREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_objectreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_STRINGREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_stringreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_THREADREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_threadreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_THREADGROUPREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_threadgroupreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_ARRAYREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_arrayreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_CLASSLOADERREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_classloaderreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_EVENTREQUEST:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_eventrequest, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_STACKFRAME:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_stackframe, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_CLASSOBJECTREFERENCE:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_classobjectreference, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        case COMMAND_SET_EVENT:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_event, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        default:
          proto_tree_add_item(jdwp_tree, hf_jdwp_commandset_virtualmachine, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
          break;

        }

      /* command comes with data when the minimal length is 12 */
      if (hlen > 11) {
        proto_tree_add_item(jdwp_tree, hf_jdwp_data, tvb, offset, hlen - 11, ENC_NA);
      } else if (hlen < 11) {
        expert_add_info(pinfo, hlen_item, &ei_jdwp_hlen_invalid);
      }

      break;

    case PACKET_TYPE_REPLY:
      proto_tree_add_item_ret_uint(jdwp_tree, hf_jdwp_errorcode, tvb, offset, 2, ENC_BIG_ENDIAN, &mem_errorcode);
      offset += 2;

      if(mem_errorcode == 0) {
        col_append_fstr(pinfo->cinfo, COL_INFO, "Reply (Success)");
      } else {
        col_append_fstr(pinfo->cinfo, COL_INFO, "Reply (Failure)");
      }

      /* reply comes with data when the minimal length is 12 */
      if (hlen > 11) {
        proto_tree_add_item(jdwp_tree, hf_jdwp_data, tvb, offset, hlen - 11, ENC_NA);
      } else if (hlen < 11) {
        expert_add_info(pinfo, hlen_item, &ei_jdwp_hlen_invalid);
      }

      break;

    default:
      expert_add_info(pinfo, flags_item, &ei_jdwp_flags_invalid);
      break;
  }

  return tvb_captured_length(tvb);
}

static int
dissect_jdwp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
  tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
                   get_jdwp_message_len, dissect_jdwp_message, data);
  return tvb_captured_length(tvb);
}

void
proto_register_jdwp(void)
{

  expert_module_t* expert_jdwp;

  static hf_register_info hf[] = {
    { &hf_jdwp_type,
      { "Packet Type", "jdwp.type", FT_STRING, BASE_NONE, NULL, 0x0, "Type",
        HFILL }
    },
    { &hf_jdwp_length,
      { "Length",  "jdwp.length", FT_UINT32, BASE_DEC, NULL, 0x0, "Data Length",
        HFILL }
    },
    { &hf_jdwp_id,
      { "id",  "jdwp.id", FT_UINT32, BASE_DEC, NULL, 0x0, "unique identifier",
        HFILL }
    },
    { &hf_jdwp_flags,
      { "flags",  "jdwp.flags", FT_UINT8, BASE_HEX, NULL, 0x0, "tag packets as a command or reply",
        HFILL }
    },
    { &hf_jdwp_commandset,
      { "command set",  "jdwp.commandset", FT_UINT8, BASE_DEC, VALS(commandsetnames), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_virtualmachine,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_virtualmachine), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_referencetype,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_referencetype), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_classtype,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_classtype), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_arraytype,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_arraytype), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_interfacetype,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_interfacetype), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_method,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_method), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_field,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_field), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_objectreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_objectreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_stringreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_stringreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_threadreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_threadreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_threadgroupreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_threadgroupreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_arrayreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_arrayreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_classloaderreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_classloaderreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_eventrequest,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_eventrequest), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_stackframe,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_stackframe), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_classobjectreference,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_classobjectreference), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_commandset_event,
      { "command",  "jdwp.command", FT_UINT8, BASE_DEC, VALS(commandset_event), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_errorcode,
      { "error code",  "jdwp.errorcode", FT_UINT8, BASE_DEC, VALS(error_codes), 0x0, NULL,
        HFILL }
    },
    { &hf_jdwp_data,
      { "data",  "jdwp.data", FT_BYTES, BASE_NONE, NULL, 0x0, "details of the command or reply",
        HFILL }
    }
  };

  static ei_register_info ei[] = {
    { &ei_jdwp_hlen_invalid, { "jdwp.hlen.invalid", PI_MALFORMED, PI_ERROR, "Decode aborted: invalid packet length", EXPFILL }},
    { &ei_jdwp_flags_invalid, { "jdwp.flags.invalid", PI_MALFORMED, PI_ERROR, "Decode aborted: invalid flags value", EXPFILL }}
  };

  static gint *ett[] = {
    &ett_jdwp
  };

  proto_jdwp = proto_register_protocol("Java Debug Wire Protocol", "JDWP", "jdwp");
  proto_register_field_array(proto_jdwp, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));
  expert_jdwp = expert_register_protocol(proto_jdwp);
  expert_register_field_array(expert_jdwp, ei, array_length(ei));

}

void
proto_reg_handoff_jdwp(void)
{
  dissector_handle_t jdwp_handle;

  jdwp_handle = create_dissector_handle(dissect_jdwp, proto_jdwp);
  dissector_add_uint_with_preference("tcp.port", JDWP_PORT, jdwp_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 2
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=2 tabstop=8 expandtab:
 * :indentSize=2:tabSize=8:noTabs=true:
 */
