/* packet-couchbase.c
 *
 * Routines for Couchbase Protocol
 *
 * Copyright 2019, Trond Norbye <trond@couchbase.com>
 * Copyright 2018, Jim Walker <jim@couchbase.com>
 * Copyright 2015-2016, Dave Rigby <daver@couchbase.com>
 * Copyright 2011, Sergey Avseyev <sergey.avseyev@gmail.com>
 *
 * With contributions from Mark Woosey <mark@markwoosey.com>
 *
 *
 * Based on packet-memcache.c: mecmcache binary protocol.
 *
 * Routines for Memcache Binary Protocol
 * http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol
 *
 * Copyright 2009, Stig Bjorlykke <stig@bjorlykke.org>
 *
 * Routines for Memcache Textual Protocol
 * http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
 *
 * Copyright 2009, Rama Chitta <rama@gear6.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 <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>

#ifdef HAVE_SNAPPY
#include <snappy-c.h>
#endif

#include "packet-tcp.h"
#include "packet-tls.h"

#include <math.h>

#define PNAME  "Couchbase Protocol"
#define PSNAME "Couchbase"
#define PFNAME "couchbase"

#define COUCHBASE_DEFAULT_PORT        "11210"
#define COUCHBASE_HEADER_LEN   24

/* Magic Byte */
enum {
  /**
   * The magic used for a normal request sent from the client to the
   * server. Layout described in:
   * https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#request-header
   */
  MAGIC_CLIENT_REQUEST = 0x80,
  /**
   * The magic used for a normal response sent from the server to
   * the client. Layout described in:
   * https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#response-header
   */
  MAGIC_CLIENT_RESPONSE = 0x81,
  /**
   * The magic used when the client want to inject a set of extensions
   * to the command sent to the server. Layout described in:
   * https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#request-header-with-flexible-framing-extras
   */
  MAGIC_CLIENT_RESPONSE_FLEX = 0x18,
  /**
   * The magic used by the server when the server needs to inject a set of
   * extensions in the response packet. Layout described in:
   * https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#response-header-with-flexible-framing-extras
   */
  MAGIC_CLIENT_REQUEST_FLEX = 0x08,
  /**
   * The magic used for server initiated push requests. These packets
   * use the same layout as client flex request packets (with a different
   * namespace for the frame id's)
   */
  MAGIC_SERVER_REQUEST = 0x82,
  /**
   * The magic used for responses to server initiated push requests. These
   * packets use the same layout as client flex response packet (with
   * a different namespace for the frame id's)
   */
  MAGIC_SERVER_RESPONSE = 0x83
};

/** Does the magic represent a flex encoded packet type */
static bool is_flex_encoded(guint8 magic) {
  switch (magic) {
    case MAGIC_CLIENT_RESPONSE_FLEX:
    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_SERVER_REQUEST:
    case MAGIC_SERVER_RESPONSE:
      return TRUE;

    case MAGIC_CLIENT_REQUEST:
    case MAGIC_CLIENT_RESPONSE:
    default:
      return FALSE;
  }
}

/** Does the magic represent server initiated packet types */
static bool is_server_magic(guint8 magic) {
  switch (magic) {
    case MAGIC_SERVER_REQUEST:
    case MAGIC_SERVER_RESPONSE:
      return TRUE;

    case MAGIC_CLIENT_RESPONSE_FLEX:
    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_CLIENT_REQUEST:
    case MAGIC_CLIENT_RESPONSE:
    default:
      return FALSE;
  }
}

/** Does the magic represent a request or a response */
static bool is_request_magic(guint8 magic) {
  switch (magic) {
    case MAGIC_SERVER_REQUEST:
    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_CLIENT_REQUEST:
      return TRUE;

    case MAGIC_SERVER_RESPONSE:
    case MAGIC_CLIENT_RESPONSE_FLEX:
    case MAGIC_CLIENT_RESPONSE:
    default:
      return FALSE;
  }
}

 /* Response Status */
#define STATUS_SUCCESS            0x00
#define STATUS_KEY_ENOENT         0x01
#define STATUS_KEY_EEXISTS        0x02
#define STATUS_E2BIG              0x03
#define STATUS_EINVAL             0x04
#define STATUS_NOT_STORED         0x05
#define STATUS_DELTA_BADVAL       0x06
#define STATUS_NOT_MY_VBUCKET     0x07
#define STATUS_NO_VBUCKET         0x08
#define STATUS_LOCKED             0x09
#define STATUS_DCP_STREAM_NOT_FOUND 0x0a
#define STATUS_OPAQUE_NO_MATCH    0x0b
#define STATUS_EWOULDTHROTTLE     0x0c
#define STATUS_ECONFIGONLY        0x0d
#define STATUS_AUTH_STALE         0x1f
#define STATUS_AUTH_ERROR         0x20
#define STATUS_AUTH_CONTINUE      0x21
#define STATUS_ERANGE             0x22
#define STATUS_ROLLBACK           0x23
#define STATUS_EACCESS            0x24
#define STATUS_NOT_INITIALIZED    0x25
#define STATUS_RATELIMITED_NETWORK_INGRESS 0x30
#define STATUS_RATELIMITED_NETWORK_EGRESS 0x31
#define STATUS_RATELIMITED_MAX_CONNECTIONS 0x32
#define STATUS_RATELIMITED_MAX_COMMANDS 0x33
#define STATUS_SCOPE_SIZE_LIMIT_EXCEEDED 0x34
#define STATUS_UNKNOWN_COMMAND    0x81
#define STATUS_ENOMEM             0x82
#define STATUS_NOT_SUPPORTED      0x83
#define STATUS_EINTERNAL          0x84
#define STATUS_EBUSY              0x85
#define STATUS_ETMPFAIL           0x86
#define STATUS_XATTR_EINVAL       0x87
#define STATUS_UNKNOWN_COLLECTION         0x88
#define STATUS_NO_COLLECTIONS_MANIFEST    0x89
#define STATUS_CANNOT_APPLY_MANIFEST      0x8a
#define STATUS_MANIFEST_IS_AHEAD          0x8b
#define STATUS_UNKNOWN_SCOPE              0x8c
#define STATUS_DCP_STREAMID_INVALID       0x8d
#define STATUS_DURABILITY_INVALID_LEVEL         0xa0
#define STATUS_DURABILITY_IMPOSSIBLE            0xa1
#define STATUS_SYNC_WRITE_IN_PROGRESS           0xa2
#define STATUS_SYNC_WRITE_AMBIGUOUS             0xa3
#define STATUS_SYNC_WRITE_RECOMMIT_IN_PROGRESS  0xa4
#define STATUS_RANGE_SCAN_CANCELLED 0xa5
#define STATUS_RANGE_SCAN_MORE 0xa6
#define STATUS_RANGE_SCAN_COMPLETE 0xa7
#define STATUS_VBUUID_NOT_EQUAL 0xa8
#define STATUS_SUBDOC_PATH_ENOENT         0xc0
#define STATUS_SUBDOC_PATH_MISMATCH       0xc1
#define STATUS_SUBDOC_PATH_EINVAL         0xc2
#define STATUS_SUBDOC_PATH_E2BIG          0xc3
#define STATUS_SUBDOC_DOC_E2DEEP          0xc4
#define STATUS_SUBDOC_VALUE_CANTINSERT    0xc5
#define STATUS_SUBDOC_DOC_NOTJSON         0xc6
#define STATUS_SUBDOC_NUM_ERANGE          0xc7
#define STATUS_SUBDOC_DELTA_ERANGE        0xc8
#define STATUS_SUBDOC_PATH_EEXISTS        0xc9
#define STATUS_SUBDOC_VALUE_ETOODEEP      0xca
#define STATUS_SUBDOC_INVALID_COMBO       0xcb
#define STATUS_SUBDOC_MULTI_PATH_FAILURE  0xcc
#define STATUS_SUBDOC_SUCCESS_DELETED            0xcd
#define STATUS_SUBDOC_XATTR_INVALID_FLAG_COMBO   0xce
#define STATUS_SUBDOC_XATTR_INVALID_KEY_COMBO    0xcf
#define STATUS_SUBDOC_XATTR_UNKNOWN_MACRO        0xd0
#define STATUS_SUBDOC_XATTR_UNKNOWN_VATTR        0xd1
#define STATUS_SUBDOC_XATTR_CANT_MODIFY_VATTR    0xd2
#define STATUS_SUBDOC_MULTI_PATH_FAILURE_DELETED 0xd3
#define STATUS_SUBDOC_INVALID_XATTR_ORDER        0xd4
#define STATUS_SUBDOC_XATTR_UNKNOWN_VATTR_MACRO  0xd5
#define STATUS_SUBDOC_CAN_ONLY_REVIVE_DELETED_DOCUMENTS 0xd6
#define STATUS_SUBDOC_DELETED_DOCUMENT_CANT_HAVE_VALUE  0xd7

 /* Command Opcodes */
#define CLIENT_OPCODE_GET                     0x00
#define CLIENT_OPCODE_SET                     0x01
#define CLIENT_OPCODE_ADD                     0x02
#define CLIENT_OPCODE_REPLACE                 0x03
#define CLIENT_OPCODE_DELETE                  0x04
#define CLIENT_OPCODE_INCREMENT               0x05
#define CLIENT_OPCODE_DECREMENT               0x06
#define CLIENT_OPCODE_QUIT                    0x07
#define CLIENT_OPCODE_FLUSH                   0x08
#define CLIENT_OPCODE_GETQ                    0x09
#define CLIENT_OPCODE_NOOP                    0x0a
#define CLIENT_OPCODE_VERSION                 0x0b
#define CLIENT_OPCODE_GETK                    0x0c
#define CLIENT_OPCODE_GETKQ                   0x0d
#define CLIENT_OPCODE_APPEND                  0x0e
#define CLIENT_OPCODE_PREPEND                 0x0f
#define CLIENT_OPCODE_STAT                    0x10
#define CLIENT_OPCODE_SETQ                    0x11
#define CLIENT_OPCODE_ADDQ                    0x12
#define CLIENT_OPCODE_REPLACEQ                0x13
#define CLIENT_OPCODE_DELETEQ                 0x14
#define CLIENT_OPCODE_INCREMENTQ              0x15
#define CLIENT_OPCODE_DECREMENTQ              0x16
#define CLIENT_OPCODE_QUITQ                   0x17
#define CLIENT_OPCODE_FLUSHQ                  0x18
#define CLIENT_OPCODE_APPENDQ                 0x19
#define CLIENT_OPCODE_PREPENDQ                0x1a
#define CLIENT_OPCODE_VERBOSITY               0x1b
#define CLIENT_OPCODE_TOUCH                   0x1c
#define CLIENT_OPCODE_GAT                     0x1d
#define CLIENT_OPCODE_GATQ                    0x1e
#define CLIENT_OPCODE_HELLO                   0x1f

 /* SASL operations */
#define CLIENT_OPCODE_SASL_LIST_MECHS         0x20
#define CLIENT_OPCODE_SASL_AUTH               0x21
#define CLIENT_OPCODE_SASL_STEP               0x22

/* Control */
#define CLIENT_OPCODE_IOCTL_GET               0x23
#define CLIENT_OPCODE_IOCTL_SET               0x24
#define CLIENT_OPCODE_CONFIG_VALIDATE         0x25
#define CLIENT_OPCODE_CONFIG_RELOAD           0x26
#define CLIENT_OPCODE_AUDIT_PUT               0x27
#define CLIENT_OPCODE_AUDIT_CONFIG_RELOAD     0x28
#define CLIENT_OPCODE_SHUTDOWN                0x29

 /* Range operations.
  * These commands are used for range operations and exist within
  * protocol_binary.h for use in other projects. Range operations are
  * not expected to be implemented in the memcached server itself.
  */
#define CLIENT_OPCODE_RGET                    0x30
#define CLIENT_OPCODE_RSET                    0x31
#define CLIENT_OPCODE_RSETQ                   0x32
#define CLIENT_OPCODE_RAPPEND                 0x33
#define CLIENT_OPCODE_RAPPENDQ                0x34
#define CLIENT_OPCODE_RPREPEND                0x35
#define CLIENT_OPCODE_RPREPENDQ               0x36
#define CLIENT_OPCODE_RDELETE                 0x37
#define CLIENT_OPCODE_RDELETEQ                0x38
#define CLIENT_OPCODE_RINCR                   0x39
#define CLIENT_OPCODE_RINCRQ                  0x3a
#define CLIENT_OPCODE_RDECR                   0x3b
#define CLIENT_OPCODE_RDECRQ                  0x3c


 /* VBucket commands */
#define CLIENT_OPCODE_SET_VBUCKET             0x3d
#define CLIENT_OPCODE_GET_VBUCKET             0x3e
#define CLIENT_OPCODE_DEL_VBUCKET             0x3f

 /* TAP commands */
#define CLIENT_OPCODE_TAP_CONNECT             0x40
#define CLIENT_OPCODE_TAP_MUTATION            0x41
#define CLIENT_OPCODE_TAP_DELETE              0x42
#define CLIENT_OPCODE_TAP_FLUSH               0x43
#define CLIENT_OPCODE_TAP_OPAQUE              0x44
#define CLIENT_OPCODE_TAP_VBUCKET_SET         0x45
#define CLIENT_OPCODE_TAP_CHECKPOINT_START    0x46
#define CLIENT_OPCODE_TAP_CHECKPOINT_END      0x47

#define CLIENT_OPCODE_GET_ALL_VB_SEQNOS       0x48

/* DCP commands */
#define CLIENT_OPCODE_DCP_OPEN_CONNECTION         0x50
#define CLIENT_OPCODE_DCP_ADD_STREAM              0x51
#define CLIENT_OPCODE_DCP_CLOSE_STREAM            0x52
#define CLIENT_OPCODE_DCP_STREAM_REQUEST          0x53
#define CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST    0x54
#define CLIENT_OPCODE_DCP_STREAM_END              0x55
#define CLIENT_OPCODE_DCP_SNAPSHOT_MARKER         0x56
#define CLIENT_OPCODE_DCP_MUTATION                0x57
#define CLIENT_OPCODE_DCP_DELETION                0x58
#define CLIENT_OPCODE_DCP_EXPIRATION              0x59
#define CLIENT_OPCODE_DCP_FLUSH                   0x5a
#define CLIENT_OPCODE_DCP_SET_VBUCKET_STATE       0x5b
#define CLIENT_OPCODE_DCP_NOOP                    0x5c
#define CLIENT_OPCODE_DCP_BUFFER_ACKNOWLEDGEMENT  0x5d
#define CLIENT_OPCODE_DCP_CONTROL                 0x5e
#define CLIENT_OPCODE_DCP_SYSTEM_EVENT            0x5f
#define CLIENT_OPCODE_DCP_PREPARE                 0x60
#define CLIENT_OPCODE_DCP_SEQNO_ACK               0x61
#define CLIENT_OPCODE_DCP_COMMIT                  0x62
#define CLIENT_OPCODE_DCP_ABORT                   0x63
#define CLIENT_OPCODE_DCP_SEQNO_ADVANCED          0x64
#define CLIENT_OPCODE_DCP_OSO_SNAPSHOT            0x65

 /* Commands from EP (eventually persistent) and bucket engines */
#define CLIENT_OPCODE_STOP_PERSISTENCE        0x80
#define CLIENT_OPCODE_START_PERSISTENCE       0x81
#define CLIENT_OPCODE_SET_PARAM               0x82
#define CLIENT_OPCODE_GET_REPLICA             0x83
#define CLIENT_OPCODE_CREATE_BUCKET           0x85
#define CLIENT_OPCODE_DELETE_BUCKET           0x86
#define CLIENT_OPCODE_LIST_BUCKETS            0x87
#define CLIENT_OPCODE_EXPAND_BUCKET           0x88
#define CLIENT_OPCODE_SELECT_BUCKET           0x89
#define CLIENT_OPCODE_START_REPLICATION       0x90
#define CLIENT_OPCODE_OBSERVE_SEQNO           0x91
#define CLIENT_OPCODE_OBSERVE                 0x92
#define CLIENT_OPCODE_EVICT_KEY               0x93
#define CLIENT_OPCODE_GET_LOCKED              0x94
#define CLIENT_OPCODE_UNLOCK_KEY              0x95
#define CLIENT_OPCODE_SYNC                    0x96
#define CLIENT_OPCODE_LAST_CLOSED_CHECKPOINT  0x97
#define CLIENT_OPCODE_RESTORE_FILE            0x98
#define CLIENT_OPCODE_RESTORE_ABORT           0x99
#define CLIENT_OPCODE_RESTORE_COMPLETE        0x9a
#define CLIENT_OPCODE_ONLINE_UPDATE_START     0x9b
#define CLIENT_OPCODE_ONLINE_UPDATE_COMPLETE  0x9c
#define CLIENT_OPCODE_ONLINE_UPDATE_REVERT    0x9d
#define CLIENT_OPCODE_DEREGISTER_TAP_CLIENT   0x9e
#define CLIENT_OPCODE_RESET_REPLICATION_CHAIN 0x9f
#define CLIENT_OPCODE_GET_META                0xa0
#define CLIENT_OPCODE_GETQ_META               0xa1
#define CLIENT_OPCODE_SET_WITH_META           0xa2
#define CLIENT_OPCODE_SETQ_WITH_META          0xa3
#define CLIENT_OPCODE_ADD_WITH_META           0xa4
#define CLIENT_OPCODE_ADDQ_WITH_META          0xa5
#define CLIENT_OPCODE_SNAPSHOT_VB_STATES      0xa6
#define CLIENT_OPCODE_VBUCKET_BATCH_COUNT     0xa7
#define CLIENT_OPCODE_DEL_WITH_META           0xa8
#define CLIENT_OPCODE_DELQ_WITH_META          0xa9
#define CLIENT_OPCODE_CREATE_CHECKPOINT       0xaa
#define CLIENT_OPCODE_NOTIFY_VBUCKET_UPDATE   0xac
#define CLIENT_OPCODE_ENABLE_TRAFFIC          0xad
#define CLIENT_OPCODE_DISABLE_TRAFFIC         0xae
#define CLIENT_OPCODE_IFCONFIG                0xaf
#define CLIENT_OPCODE_CHANGE_VB_FILTER        0xb0
#define CLIENT_OPCODE_CHECKPOINT_PERSISTENCE  0xb1
#define CLIENT_OPCODE_RETURN_META             0xb2
#define CLIENT_OPCODE_COMPACT_DB              0xb3


#define CLIENT_OPCODE_SET_CLUSTER_CONFIG      0xb4
#define CLIENT_OPCODE_GET_CLUSTER_CONFIG      0xb5
#define CLIENT_OPCODE_GET_RANDOM_KEY          0xb6
#define CLIENT_OPCODE_SEQNO_PERSISTENCE       0xb7
#define CLIENT_OPCODE_GET_KEYS                0xb8
#define CLIENT_OPCODE_COLLECTIONS_SET_MANIFEST 0xb9
#define CLIENT_OPCODE_COLLECTIONS_GET_MANIFEST 0xba
#define CLIENT_OPCODE_COLLECTIONS_GET_ID       0xbb
#define CLIENT_OPCODE_COLLECTIONS_GET_SCOPE_ID 0xbc

#define CLIENT_OPCODE_SET_DRIFT_COUNTER_STATE 0xc1
#define CLIENT_OPCODE_GET_ADJUSTED_TIME       0xc2

/* Sub-document API commands */
#define CLIENT_OPCODE_SUBDOC_GET              0xc5
#define CLIENT_OPCODE_SUBDOC_EXISTS           0xc6
#define CLIENT_OPCODE_SUBDOC_DICT_ADD         0xc7
#define CLIENT_OPCODE_SUBDOC_DICT_UPSERT      0xc8
#define CLIENT_OPCODE_SUBDOC_DELETE           0xc9
#define CLIENT_OPCODE_SUBDOC_REPLACE          0xca
#define CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_LAST  0xcb
#define CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_FIRST 0xcc
#define CLIENT_OPCODE_SUBDOC_ARRAY_INSERT     0xcd
#define CLIENT_OPCODE_SUBDOC_ARRAY_ADD_UNIQUE 0xce
#define CLIENT_OPCODE_SUBDOC_COUNTER          0xcf
#define CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP     0xd0
#define CLIENT_OPCODE_SUBDOC_MULTI_MUTATION   0xd1
#define CLIENT_OPCODE_SUBDOC_GET_COUNT        0xd2
#define CLIENT_OPCODE_SUBDOC_REPLACE_BODY_WITH_XATTR 0xd3

#define CLIENT_OPCODE_RANGE_SCAN_CREATE       0xda
#define CLIENT_OPCODE_RANGE_SCAN_CONTINUE     0xdb
#define CLIENT_OPCODE_RANGE_SCAN_CANCEL       0xdc

#define CLIENT_OPCODE_SCRUB                   0xf0
#define CLIENT_OPCODE_ISASL_REFRESH           0xf1
#define CLIENT_OPCODE_SSL_CERTS_REFRESH       0xf2
#define CLIENT_OPCODE_GET_CMD_TIMER           0xf3
#define CLIENT_OPCODE_SET_CTRL_TOKEN          0xf4
#define CLIENT_OPCODE_GET_CTRL_TOKEN          0xf5
#define CLIENT_OPCODE_UPDATE_EXTERNAL_USER_PERMISSIONS 0xf6
#define CLIENT_OPCODE_RBAC_REFRESH            0xf7
#define CLIENT_OPCODE_AUTH_PROVIDER           0xf8
#define CLIENT_OPCODE_DROP_PRIVILEGE          0xfb
#define CLIENT_OPCODE_ADJUST_TIMEOFDAY        0xfc
#define CLIENT_OPCODE_EWOULDBLOCK_CTL         0xfd
#define CLIENT_OPCODE_GET_ERROR_MAP           0xfe

 /* vBucket states */
#define VBUCKET_ACTIVE                              0x01
#define VBUCKET_PENDING                             0x02
#define VBUCKET_REPLICA                             0x03
#define VBUCKET_DEAD                                0x04

 /* Data Types */
#define DT_RAW_BYTES          0x00
#define DT_JSON               0x01
#define DT_SNAPPY             0x02
#define DT_XATTR              0x04

void proto_register_couchbase(void);
void proto_reg_handoff_couchbase(void);

static int proto_couchbase = -1;

static int hf_magic = -1;
static int hf_opcode = -1;
static int hf_server_opcode = -1;
static int hf_extlength = -1;
static int hf_keylength = -1;
static int hf_value_length = -1;
static int hf_datatype = -1;
static int hf_datatype_json = -1;
static int hf_datatype_snappy = -1;
static int hf_datatype_xattr = -1;
static int hf_vbucket = -1;
static int hf_status = -1;
static int hf_total_bodylength = -1;
static int hf_opaque = -1;
static int hf_cas = -1;
static int hf_ttp = -1;
static int hf_ttr = -1;
static int hf_collection_key_id = -1;
static int hf_collection_key_logical = -1;
static int hf_collection_manifest_id = -1;

static int hf_flex_extras_length = -1;
static int hf_flex_keylength = -1;
static int hf_extras = -1;
static int hf_extras_flags = -1;
static int hf_extras_flags_backfill = -1;
static int hf_extras_flags_dump = -1;
static int hf_extras_flags_list_vbuckets = -1;
static int hf_extras_flags_takeover_vbuckets = -1;
static int hf_extras_flags_support_ack = -1;
static int hf_extras_flags_request_keys_only = -1;
static int hf_extras_flags_checkpoint = -1;
static int hf_extras_flags_dcp_connection_type = -1;
static int hf_extras_flags_dcp_add_stream_takeover = -1;
static int hf_extras_flags_dcp_add_stream_diskonly = -1;
static int hf_extras_flags_dcp_add_stream_latest = -1;
static int hf_extras_flags_dcp_snapshot_marker_memory = -1;
static int hf_extras_flags_dcp_snapshot_marker_disk = -1;
static int hf_extras_flags_dcp_snapshot_marker_chk = -1;
static int hf_extras_flags_dcp_snapshot_marker_ack = -1;
static int hf_extras_flags_dcp_snapshot_marker_history = -1;
static int hf_extras_flags_dcp_snapshot_marker_may_contain_dups = -1;
static int hf_extras_flags_dcp_include_xattrs = -1;
static int hf_extras_flags_dcp_no_value = -1;
static int hf_extras_flags_dcp_include_delete_times = -1;
static int hf_extras_flags_dcp_collections = -1;
static int hf_extras_flags_dcp_oso_snapshot_begin = -1;
static int hf_extras_flags_dcp_oso_snapshot_end = -1;
static int hf_subdoc_doc_flags = -1;
static int hf_subdoc_doc_flags_mkdoc = -1;
static int hf_subdoc_doc_flags_add = -1;
static int hf_subdoc_doc_flags_accessdeleted = -1;
static int hf_subdoc_doc_flags_createasdeleted = -1;
static int hf_subdoc_doc_flags_revivedocument = -1;
static int hf_subdoc_doc_flags_replicaread = -1;
static int hf_subdoc_doc_flags_reserved = -1;
static int hf_subdoc_flags = -1;
static int hf_subdoc_flags_mkdirp = -1;
static int hf_subdoc_flags_xattrpath = -1;
static int hf_subdoc_flags_expandmacros = -1;
static int hf_subdoc_flags_reserved = -1;
static int hf_extras_seqno = -1;
static int hf_extras_mutation_seqno = -1;
static int hf_extras_opaque = -1;
static int hf_extras_reserved = -1;
static int hf_extras_start_seqno = -1;
static int hf_extras_end_seqno = -1;
static int hf_extras_high_completed_seqno = -1;
static int hf_extras_max_visible_seqno = -1;
static int hf_extras_timestamp = -1;
static int hf_extras_marker_version = -1;
static int hf_extras_vbucket_uuid = -1;
static int hf_extras_snap_start_seqno = -1;
static int hf_extras_snap_end_seqno = -1;
static int hf_extras_expiration = -1;
static int hf_extras_delta = -1;
static int hf_extras_initial = -1;
static int hf_extras_unknown = -1;
static int hf_extras_by_seqno = -1;
static int hf_extras_rev_seqno = -1;
static int hf_extras_prepared_seqno = -1;
static int hf_extras_commit_seqno = -1;
static int hf_extras_abort_seqno = -1;
static int hf_extras_deleted = -1;
static int hf_extras_lock_time = -1;
static int hf_extras_nmeta = -1;
static int hf_extras_nru = -1;
static int hf_extras_bytes_to_ack = -1;
static int hf_extras_delete_time = -1;
static int hf_extras_delete_unused = -1;
static int hf_extras_system_event_id = -1;
static int hf_extras_system_event_version = -1;
static int hf_extras_pathlen = -1;
static int hf_extras_dcp_oso_snapshot_flags = -1;
static int hf_server_extras_cccp_epoch = -1;
static int hf_server_extras_cccp_revno = -1;
static int hf_server_clustermap_value = -1;
static int hf_server_authentication = -1;
static int hf_server_external_users = -1;
static int hf_server_get_authorization = -1;

static int hf_key = -1;
static int hf_path = -1;
static int hf_value = -1;
static int hf_uint64_response = -1;
static int hf_observe = -1;
static int hf_observe_vbucket = -1;
static int hf_observe_keylength = -1;
static int hf_observe_key = -1;
static int hf_observe_status = -1;
static int hf_observe_cas = -1;
static int hf_observe_vbucket_uuid = -1;
static int hf_observe_failed_over = -1;
static int hf_observe_last_persisted_seqno = -1;
static int hf_observe_current_seqno = -1;
static int hf_observe_old_vbucket_uuid = -1;
static int hf_observe_last_received_seqno = -1;

static int hf_get_errmap_version = -1;

static int hf_failover_log = -1;
static int hf_failover_log_size = -1;
static int hf_failover_log_vbucket_uuid = -1;
static int hf_failover_log_vbucket_seqno = -1;

static int hf_vbucket_states = -1;
static int hf_vbucket_states_state = -1;
static int hf_vbucket_states_size = -1;
static int hf_vbucket_states_id = -1;
static int hf_vbucket_states_seqno = -1;

static int hf_bucket_type = -1;
static int hf_bucket_config = -1;
static int hf_config_key = -1;
static int hf_config_value = -1;

static int hf_multipath_opcode = -1;
static int hf_multipath_index = -1;
static int hf_multipath_pathlen = -1;
static int hf_multipath_path = -1;
static int hf_multipath_valuelen = -1;
static int hf_multipath_value = -1;

static int hf_meta_flags = -1;
static int hf_meta_expiration = -1;
static int hf_meta_revseqno = -1;
static int hf_meta_cas = -1;
static int hf_skip_conflict = -1;
static int hf_force_accept = -1;
static int hf_regenerate_cas = -1;
static int hf_force_meta = -1;
static int hf_is_expiration = -1;
static int hf_meta_options = -1;
static int hf_metalen = -1;
static int hf_meta_reqextmeta = -1;
static int hf_meta_deleted = -1;
static int hf_exptime = -1;
static int hf_extras_meta_seqno = -1;
static int hf_confres = -1;
static int hf_hello_features = -1;
static int hf_hello_features_feature = -1;

static int hf_xattr_length = -1;
static int hf_xattr_pair_length = -1;
static int hf_xattr_key = -1;
static int hf_xattr_value = -1;
static int hf_xattrs = -1;

static int hf_flex_extras = -1;
static int hf_flex_extras_n = -1;
static int hf_flex_frame_id_byte0 = -1;
static int hf_flex_frame_id_req = -1;
static int hf_flex_frame_id_res = -1;
static int hf_flex_frame_id_req_esc = -1;
static int hf_flex_frame_id_res_esc = -1;
static int hf_flex_frame_len = -1;
static int hf_flex_frame_len_esc = -1;
static int hf_flex_frame_tracing_duration = -1;
static int hf_flex_frame_ru_count = -1;
static int hf_flex_frame_wu_count = -1;
static int hf_flex_frame_durability_req = -1;
static int hf_flex_frame_dcp_stream_id = -1;
static int hf_flex_frame_impersonated_user = -1;

static int hf_range_scan_uuid  = -1;
static int hf_range_scan_item_limit = -1;
static int hf_range_scan_time_limit = -1;
static int hf_range_scan_byte_limit = -1;

static expert_field ef_warn_shall_not_have_value = EI_INIT;
static expert_field ef_warn_shall_not_have_extras = EI_INIT;
static expert_field ef_warn_shall_not_have_key = EI_INIT;
static expert_field ef_compression_error = EI_INIT;
static expert_field ef_warn_unknown_flex_unsupported = EI_INIT;
static expert_field ef_warn_unknown_flex_id = EI_INIT;
static expert_field ef_warn_unknown_flex_len = EI_INIT;

static expert_field ei_value_missing = EI_INIT;
static expert_field ef_warn_must_have_extras = EI_INIT;
static expert_field ef_warn_must_have_key = EI_INIT;
static expert_field ef_warn_illegal_extras_length = EI_INIT;
static expert_field ef_warn_illegal_value_length = EI_INIT;
static expert_field ef_warn_unknown_magic_byte = EI_INIT;
static expert_field ef_warn_unknown_opcode = EI_INIT;
static expert_field ef_warn_unknown_extras = EI_INIT;
static expert_field ef_note_status_code = EI_INIT;
static expert_field ef_separator_not_found = EI_INIT;
static expert_field ef_illegal_value = EI_INIT;

static gint ett_couchbase = -1;
static gint ett_extras = -1;
static gint ett_extras_flags = -1;
static gint ett_observe = -1;
static gint ett_failover_log = -1;
static gint ett_vbucket_states = -1;
static gint ett_multipath = -1;
static gint ett_config = -1;
static gint ett_config_key = -1;
static gint ett_hello_features = -1;
static gint ett_datatype = -1;
static gint ett_xattrs = -1;
static gint ett_xattr_pair = -1;
static gint ett_flex_frame_extras = -1;
static gint ett_collection_key = -1;

static const value_string magic_vals[] = {
  { MAGIC_CLIENT_REQUEST, "Request" },
  { MAGIC_CLIENT_RESPONSE, "Response" },
  { MAGIC_CLIENT_RESPONSE_FLEX, "Response with flexible framing extras" },
  { MAGIC_CLIENT_REQUEST_FLEX, "Request with flexible framing extras" },
  { MAGIC_SERVER_REQUEST, "Server Request"},
  { MAGIC_SERVER_RESPONSE, "Server Response"},
  { 0, NULL }
};

#define FLEX_ESCAPE 0x0F

/*
  The flex extension identifiers are different for request/response
  i.e. 0 in a response is not 0 in a request
  Response IDs
 */
#define FLEX_RESPONSE_ID_RX_TX_DURATION 0
#define FLEX_RESPONSE_ID_RU_USAGE 1
#define FLEX_RESPONSE_ID_WU_USAGE 2

/* Request IDs */
#define FLEX_REQUEST_ID_REORDER 0
#define FLEX_REQUEST_ID_DURABILITY 1
#define FLEX_REQUEST_ID_DCP_STREAM_ID 2
#define FLEX_REQUEST_ID_OPEN_TRACING 3
#define FLEX_REQUEST_ID_IMPERSONATE 4
#define FLEX_REQUEST_ID_PRESERVE_TTL 5

static const value_string flex_frame_response_ids[] = {
  { FLEX_RESPONSE_ID_RX_TX_DURATION, "Server Recv->Send duration"},
  { FLEX_RESPONSE_ID_RU_USAGE, "Read units"},
  { FLEX_RESPONSE_ID_WU_USAGE, "Write units"},
  { 0, NULL }
};

static const value_string flex_frame_request_ids[] = {
  { FLEX_REQUEST_ID_REORDER, "Out of order Execution"},
  { FLEX_REQUEST_ID_DURABILITY, "Durability Requirements"},
  { FLEX_REQUEST_ID_DCP_STREAM_ID, "DCP Stream Identifier"},
  { FLEX_REQUEST_ID_OPEN_TRACING, "Open Tracing"},
  { FLEX_REQUEST_ID_IMPERSONATE, "Impersonate User"},
  { FLEX_REQUEST_ID_PRESERVE_TTL, "Preserve TTL"},
  { 0, NULL }
};

static const value_string flex_frame_durability_req[] = {
  { 1, "Majority"},
  { 2, "Majority and persist on active"},
  { 3, "Persist to majority"},
  { 0, NULL }
};

static const value_string status_vals[] = {
  { STATUS_SUCCESS,           "Success"                 },
  { STATUS_KEY_ENOENT,        "Key not found"           },
  { STATUS_KEY_EEXISTS,       "Key exists"              },
  { STATUS_E2BIG,             "Value too big"           },
  { STATUS_EINVAL,            "Invalid arguments"       },
  { STATUS_NOT_STORED,        "Key not stored"          },
  { STATUS_DELTA_BADVAL,      "Bad value to incr/decr"  },
  { STATUS_NOT_MY_VBUCKET,    "Not my vBucket"          },
  { STATUS_NO_VBUCKET,        "Not connected to a bucket" },
  { STATUS_LOCKED,            "The requested resource is locked" },
  { STATUS_DCP_STREAM_NOT_FOUND, "No DCP Stream for this request" },
  { STATUS_OPAQUE_NO_MATCH,   "Opaque does not match" },
  { STATUS_EWOULDTHROTTLE,    "Command would have been throttled" },
  { STATUS_ECONFIGONLY,       "Command can't be executed in config-only bucket" },
  { STATUS_AUTH_STALE,        "Authentication context is stale. Should reauthenticate." },
  { STATUS_AUTH_ERROR,        "Authentication error"    },
  { STATUS_AUTH_CONTINUE,     "Authentication continue" },
  { STATUS_ERANGE,            "Range error"             },
  { STATUS_ROLLBACK,          "Rollback"                },
  { STATUS_EACCESS,           "Access error"            },
  { STATUS_NOT_INITIALIZED,
    "The Couchbase cluster is currently initializing this node, and "
    "the Cluster manager has not yet granted all users access to the cluster."},
  { STATUS_RATELIMITED_NETWORK_INGRESS, "Rate limit: Network ingress"},
  { STATUS_RATELIMITED_NETWORK_EGRESS, "Rate limit: Network Egress"},
  { STATUS_RATELIMITED_MAX_CONNECTIONS, "Rate limit: Max Connections"},
  { STATUS_RATELIMITED_MAX_COMMANDS, "Rate limit: Max Commands"},
  {STATUS_SCOPE_SIZE_LIMIT_EXCEEDED, "To much data in Scope"},
  { STATUS_UNKNOWN_COMMAND,   "Unknown command"         },
  { STATUS_ENOMEM,            "Out of memory"           },
  { STATUS_NOT_SUPPORTED,     "Command isn't supported" },
  { STATUS_EINTERNAL,         "Internal error"          },
  { STATUS_EBUSY,             "Server is busy"          },
  { STATUS_ETMPFAIL,          "Temporary failure"       },
  { STATUS_XATTR_EINVAL,
    "There is something wrong with the syntax of the provided XATTR."},
  { STATUS_UNKNOWN_COLLECTION,
    "Operation attempted with an unknown collection."},
  { STATUS_NO_COLLECTIONS_MANIFEST,
    "No collections manifest has been set"},
  { STATUS_CANNOT_APPLY_MANIFEST,
    "Cannot apply the given manifest"},
  { STATUS_MANIFEST_IS_AHEAD,
    "Operation attempted with a manifest ahead of the server"},
  { STATUS_UNKNOWN_SCOPE,
    "Operation attempted with an unknown scope."},
  { STATUS_DCP_STREAMID_INVALID,
    "DCP Stream ID is invalid"},
  { STATUS_DURABILITY_INVALID_LEVEL,
    "The specified durability level is invalid" },
  { STATUS_DURABILITY_IMPOSSIBLE,
    "The specified durability requirements are not currently possible" },
  { STATUS_SYNC_WRITE_IN_PROGRESS,
    "A SyncWrite is already in progress on the specified key"},
  { STATUS_SYNC_WRITE_AMBIGUOUS,
    "The SyncWrite request has not completed in the specified time and has ambiguous result"},
  { STATUS_SYNC_WRITE_RECOMMIT_IN_PROGRESS,
    "The SyncWrite is being re-committed after a change in active node"},
  { STATUS_RANGE_SCAN_CANCELLED, "RangeScan was cancelled"},
  { STATUS_RANGE_SCAN_MORE, "RangeScan has more data available"},
  { STATUS_RANGE_SCAN_COMPLETE, "RangeScan has completed"},
  { STATUS_VBUUID_NOT_EQUAL, "VB UUID does not equal server value"},
  { STATUS_SUBDOC_PATH_ENOENT,
    "Subdoc: Path not does not exist"},
  { STATUS_SUBDOC_PATH_MISMATCH,
    "Subdoc: Path mismatch"},
  { STATUS_SUBDOC_PATH_EINVAL,
    "Subdoc: Invalid path"},
  { STATUS_SUBDOC_PATH_E2BIG,
    "Subdoc: Path too large"},
  { STATUS_SUBDOC_DOC_E2DEEP,
    "Subdoc: Document too deep"},
  { STATUS_SUBDOC_VALUE_CANTINSERT,
    "Subdoc: Cannot insert specified value"},
  { STATUS_SUBDOC_DOC_NOTJSON,
    "Subdoc: Existing document not JSON"},
  { STATUS_SUBDOC_NUM_ERANGE,
    "Subdoc: Existing number outside valid arithmetic range"},
  { STATUS_SUBDOC_DELTA_ERANGE,
    "Subdoc: Delta outside valid arithmetic range"},
  { STATUS_SUBDOC_PATH_EEXISTS,
    "Subdoc: Document path already exists"},
  { STATUS_SUBDOC_VALUE_ETOODEEP,
    "Subdoc: Inserting value would make document too deep"},
  { STATUS_SUBDOC_INVALID_COMBO,
    "Subdoc: Invalid combination for multi-path command"},
  { STATUS_SUBDOC_MULTI_PATH_FAILURE,
    "Subdoc: One or more paths in a multi-path command failed"},
  { STATUS_SUBDOC_SUCCESS_DELETED,
    "Subdoc: The operation completed successfully, but operated on a deleted document."},
  { STATUS_SUBDOC_XATTR_INVALID_FLAG_COMBO,
    "Subdoc: The combination of the subdoc flags for the xattrs doesn't make any sense."},
  { STATUS_SUBDOC_XATTR_INVALID_KEY_COMBO,
    "Subdoc: Only a single xattr key may be accessed at the same time."},
  { STATUS_SUBDOC_XATTR_UNKNOWN_MACRO,
    "Subdoc: The server has no knowledge of the requested macro."},
  { STATUS_SUBDOC_XATTR_UNKNOWN_VATTR,
    "Subdoc: The server has no knowledge of the requested virtual xattr."},
  { STATUS_SUBDOC_XATTR_CANT_MODIFY_VATTR,
    "Subdoc: Virtual xattrs can't be modified."},
  { STATUS_SUBDOC_MULTI_PATH_FAILURE_DELETED,
    "Subdoc: Specified key was found as a deleted document, but one or more path operations failed."},
  { STATUS_SUBDOC_INVALID_XATTR_ORDER,
    "Subdoc: According to the spec all xattr commands should come first, followed by the commands for the document body."},
  { STATUS_SUBDOC_XATTR_UNKNOWN_VATTR_MACRO,
    "Subdoc: The server does not know about this virtual macro."},
  { STATUS_SUBDOC_CAN_ONLY_REVIVE_DELETED_DOCUMENTS,
    "Subdoc: The document isn't dead (and we wanted to revive the document)."},
  { STATUS_SUBDOC_DELETED_DOCUMENT_CANT_HAVE_VALUE,
    "Subdoc: A deleted document can't have a user value."},
  { 0, NULL }
};

static value_string_ext status_vals_ext = VALUE_STRING_EXT_INIT(status_vals);

static const value_string client_opcode_vals[] = {
  { CLIENT_OPCODE_GET,                        "Get"                      },
  { CLIENT_OPCODE_SET,                        "Set"                      },
  { CLIENT_OPCODE_ADD,                        "Add"                      },
  { CLIENT_OPCODE_REPLACE,                    "Replace"                  },
  { CLIENT_OPCODE_DELETE,                     "Delete"                   },
  { CLIENT_OPCODE_INCREMENT,                  "Increment"                },
  { CLIENT_OPCODE_DECREMENT,                  "Decrement"                },
  { CLIENT_OPCODE_QUIT,                       "Quit"                     },
  { CLIENT_OPCODE_FLUSH,                      "Flush"                    },
  { CLIENT_OPCODE_GETQ,                       "Get Quietly"              },
  { CLIENT_OPCODE_NOOP,                       "NOOP"                     },
  { CLIENT_OPCODE_VERSION,                    "Version"                  },
  { CLIENT_OPCODE_GETK,                       "Get Key"                  },
  { CLIENT_OPCODE_GETKQ,                      "Get Key Quietly"          },
  { CLIENT_OPCODE_APPEND,                     "Append"                   },
  { CLIENT_OPCODE_PREPEND,                    "Prepend"                  },
  { CLIENT_OPCODE_STAT,                       "Statistics"               },
  { CLIENT_OPCODE_SETQ,                       "Set Quietly"              },
  { CLIENT_OPCODE_ADDQ,                       "Add Quietly"              },
  { CLIENT_OPCODE_REPLACEQ,                   "Replace Quietly"          },
  { CLIENT_OPCODE_DELETEQ,                    "Delete Quietly"           },
  { CLIENT_OPCODE_INCREMENTQ,                 "Increment Quietly"        },
  { CLIENT_OPCODE_DECREMENTQ,                 "Decrement Quietly"        },
  { CLIENT_OPCODE_QUITQ,                      "Quit Quietly"             },
  { CLIENT_OPCODE_FLUSHQ,                     "Flush Quietly"            },
  { CLIENT_OPCODE_APPENDQ,                    "Append Quietly"           },
  { CLIENT_OPCODE_PREPENDQ,                   "Prepend Quietly"          },
  { CLIENT_OPCODE_VERBOSITY,                  "Verbosity"                },
  { CLIENT_OPCODE_TOUCH,                      "Touch"                    },
  { CLIENT_OPCODE_GAT,                        "Get and Touch"            },
  { CLIENT_OPCODE_GATQ,                       "Gat and Touch Quietly"    },
  { CLIENT_OPCODE_HELLO,                      "Hello"                    },
  { CLIENT_OPCODE_SASL_LIST_MECHS,            "List SASL Mechanisms"     },
  { CLIENT_OPCODE_SASL_AUTH,                  "SASL Authenticate"        },
  { CLIENT_OPCODE_SASL_STEP,                  "SASL Step"                },
  { CLIENT_OPCODE_IOCTL_GET,                  "IOCTL Get"                },
  { CLIENT_OPCODE_IOCTL_SET,                  "IOCTL Set"                },
  { CLIENT_OPCODE_CONFIG_VALIDATE,            "Config Validate"          },
  { CLIENT_OPCODE_CONFIG_RELOAD,              "Config Reload"            },
  { CLIENT_OPCODE_AUDIT_PUT,                  "Audit Put"                },
  { CLIENT_OPCODE_AUDIT_CONFIG_RELOAD,        "Audit Config Reload"      },
  { CLIENT_OPCODE_SHUTDOWN,                   "Shutdown"                 },
  { CLIENT_OPCODE_RGET,                       "Range Get"                },
  { CLIENT_OPCODE_RSET,                       "Range Set"                },
  { CLIENT_OPCODE_RSETQ,                      "Range Set Quietly"        },
  { CLIENT_OPCODE_RAPPEND,                    "Range Append"             },
  { CLIENT_OPCODE_RAPPENDQ,                   "Range Append Quietly"     },
  { CLIENT_OPCODE_RPREPEND,                   "Range Prepend"            },
  { CLIENT_OPCODE_RPREPENDQ,                  "Range Prepend Quietly"    },
  { CLIENT_OPCODE_RDELETE,                    "Range Delete"             },
  { CLIENT_OPCODE_RDELETEQ,                   "Range Delete Quietly"     },
  { CLIENT_OPCODE_RINCR,                      "Range Increment"          },
  { CLIENT_OPCODE_RINCRQ,                     "Range Increment Quietly"  },
  { CLIENT_OPCODE_RDECR,                      "Range Decrement"          },
  { CLIENT_OPCODE_RDECRQ,                     "Range Decrement Quietly"  },
  { CLIENT_OPCODE_SET_VBUCKET,                "Set VBucket"              },
  { CLIENT_OPCODE_GET_VBUCKET,                "Get VBucket"              },
  { CLIENT_OPCODE_DEL_VBUCKET,                "Delete VBucket"           },
  { CLIENT_OPCODE_TAP_CONNECT,                "TAP Connect"              },
  { CLIENT_OPCODE_TAP_MUTATION,               "TAP Mutation"             },
  { CLIENT_OPCODE_TAP_DELETE,                 "TAP Delete"               },
  { CLIENT_OPCODE_TAP_FLUSH,                  "TAP Flush"                },
  { CLIENT_OPCODE_TAP_OPAQUE,                 "TAP Opaque"               },
  { CLIENT_OPCODE_TAP_VBUCKET_SET,            "TAP VBucket Set"          },
  { CLIENT_OPCODE_TAP_CHECKPOINT_START,       "TAP Checkpoint Start"     },
  { CLIENT_OPCODE_TAP_CHECKPOINT_END,         "TAP Checkpoint End"       },
  { CLIENT_OPCODE_GET_ALL_VB_SEQNOS,          "Get All VBucket Seqnos"   },
  { CLIENT_OPCODE_DCP_OPEN_CONNECTION,        "DCP Open Connection"      },
  { CLIENT_OPCODE_DCP_ADD_STREAM,             "DCP Add Stream"           },
  { CLIENT_OPCODE_DCP_CLOSE_STREAM,           "DCP Close Stream"         },
  { CLIENT_OPCODE_DCP_STREAM_REQUEST,         "DCP Stream Request"       },
  { CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST,   "DCP Get Failover Log"     },
  { CLIENT_OPCODE_DCP_STREAM_END,             "DCP Stream End"           },
  { CLIENT_OPCODE_DCP_SNAPSHOT_MARKER,        "DCP Snapshot Marker"      },
  { CLIENT_OPCODE_DCP_MUTATION,               "DCP (Key) Mutation"       },
  { CLIENT_OPCODE_DCP_DELETION,               "DCP (Key) Deletion"       },
  { CLIENT_OPCODE_DCP_EXPIRATION,             "DCP (Key) Expiration"     },
  { CLIENT_OPCODE_DCP_FLUSH,                  "DCP Flush"                },
  { CLIENT_OPCODE_DCP_SET_VBUCKET_STATE,      "DCP Set VBucket State"    },
  { CLIENT_OPCODE_DCP_NOOP,                   "DCP NOOP"                 },
  { CLIENT_OPCODE_DCP_BUFFER_ACKNOWLEDGEMENT, "DCP Buffer Acknowledgement"},
  { CLIENT_OPCODE_DCP_CONTROL,                "DCP Control"              },
  { CLIENT_OPCODE_DCP_SYSTEM_EVENT,           "DCP System Event"         },
  { CLIENT_OPCODE_DCP_PREPARE,                "DCP Prepare"              },
  { CLIENT_OPCODE_DCP_SEQNO_ACK,              "DCP Seqno Acknowledgement"},
  { CLIENT_OPCODE_DCP_COMMIT,                 "DCP Commit"               },
  { CLIENT_OPCODE_DCP_ABORT,                  "DCP Abort"                },
  { CLIENT_OPCODE_DCP_SEQNO_ADVANCED,         "DCP Seqno Advanced"       },
  { CLIENT_OPCODE_DCP_OSO_SNAPSHOT,           "DCP Out of Sequence Order Snapshot"},
  { CLIENT_OPCODE_STOP_PERSISTENCE,           "Stop Persistence"         },
  { CLIENT_OPCODE_START_PERSISTENCE,          "Start Persistence"        },
  { CLIENT_OPCODE_SET_PARAM,                  "Set Parameter"            },
  { CLIENT_OPCODE_GET_REPLICA,                "Get Replica"              },
  { CLIENT_OPCODE_CREATE_BUCKET,              "Create Bucket"            },
  { CLIENT_OPCODE_DELETE_BUCKET,              "Delete Bucket"            },
  { CLIENT_OPCODE_LIST_BUCKETS,               "List Buckets"             },
  { CLIENT_OPCODE_EXPAND_BUCKET,              "Expand Bucket"            },
  { CLIENT_OPCODE_SELECT_BUCKET,              "Select Bucket"            },
  { CLIENT_OPCODE_START_REPLICATION,          "Start Replication"        },
  { CLIENT_OPCODE_OBSERVE_SEQNO,              "Observe Sequence Number"  },
  { CLIENT_OPCODE_OBSERVE,                    "Observe"                  },
  { CLIENT_OPCODE_EVICT_KEY,                  "Evict Key"                },
  { CLIENT_OPCODE_GET_LOCKED,                 "Get Locked"               },
  { CLIENT_OPCODE_UNLOCK_KEY,                 "Unlock Key"               },
  { CLIENT_OPCODE_SYNC,                       "Sync"                     },
  { CLIENT_OPCODE_LAST_CLOSED_CHECKPOINT,     "Last Closed Checkpoint"   },
  { CLIENT_OPCODE_RESTORE_FILE,               "Restore File"             },
  { CLIENT_OPCODE_RESTORE_ABORT,              "Restore Abort"            },
  { CLIENT_OPCODE_RESTORE_COMPLETE,           "Restore Complete"         },
  { CLIENT_OPCODE_ONLINE_UPDATE_START,        "Online Update Start"      },
  { CLIENT_OPCODE_ONLINE_UPDATE_COMPLETE,     "Online Update Complete"   },
  { CLIENT_OPCODE_ONLINE_UPDATE_REVERT,       "Online Update Revert"     },
  { CLIENT_OPCODE_DEREGISTER_TAP_CLIENT,      "Deregister TAP Client"    },
  { CLIENT_OPCODE_RESET_REPLICATION_CHAIN,    "Reset Replication Chain"  },
  { CLIENT_OPCODE_GET_META,                   "Get Meta"                 },
  { CLIENT_OPCODE_GETQ_META,                  "Get Meta Quietly"         },
  { CLIENT_OPCODE_SET_WITH_META,              "Set with Meta"            },
  { CLIENT_OPCODE_SETQ_WITH_META,             "Set with Meta Quietly"    },
  { CLIENT_OPCODE_ADD_WITH_META,              "Add with Meta"            },
  { CLIENT_OPCODE_ADDQ_WITH_META,             "Add with Meta Quietly"    },
  { CLIENT_OPCODE_SNAPSHOT_VB_STATES,         "Snapshot VBuckets States" },
  { CLIENT_OPCODE_VBUCKET_BATCH_COUNT,        "VBucket Batch Count"      },
  { CLIENT_OPCODE_DEL_WITH_META,              "Delete with Meta"         },
  { CLIENT_OPCODE_DELQ_WITH_META,             "Delete with Meta Quietly" },
  { CLIENT_OPCODE_CREATE_CHECKPOINT,          "Create Checkpoint"        },
  { CLIENT_OPCODE_NOTIFY_VBUCKET_UPDATE,      "Notify VBucket Update"    },
  { CLIENT_OPCODE_ENABLE_TRAFFIC,             "Enable Traffic"           },
  { CLIENT_OPCODE_DISABLE_TRAFFIC,            "Disable Traffic"          },
  { CLIENT_OPCODE_IFCONFIG,                   "Ifconfig"                 },
  { CLIENT_OPCODE_CHANGE_VB_FILTER,           "Change VBucket Filter"    },
  { CLIENT_OPCODE_CHECKPOINT_PERSISTENCE,     "Checkpoint Persistence"   },
  { CLIENT_OPCODE_RETURN_META,                "Return Meta"              },
  { CLIENT_OPCODE_COMPACT_DB,                 "Compact Database"         },
  { CLIENT_OPCODE_SET_CLUSTER_CONFIG,         "Set Cluster Config"       },
  { CLIENT_OPCODE_GET_CLUSTER_CONFIG,         "Get Cluster Config"       },
  { CLIENT_OPCODE_GET_RANDOM_KEY,             "Get Random Key"           },
  { CLIENT_OPCODE_SEQNO_PERSISTENCE,          "Seqno Persistence"        },
  { CLIENT_OPCODE_GET_KEYS,                   "Get Keys"                 },
  { CLIENT_OPCODE_COLLECTIONS_SET_MANIFEST,   "Set Collection's Manifest" },
  { CLIENT_OPCODE_COLLECTIONS_GET_MANIFEST,   "Get Collection's Manifest" },
  { CLIENT_OPCODE_COLLECTIONS_GET_ID,         "Get Collection ID"        },
  { CLIENT_OPCODE_COLLECTIONS_GET_SCOPE_ID,   "Get Scope ID"             },
  { CLIENT_OPCODE_SET_DRIFT_COUNTER_STATE,    "Set Drift Counter State"  },
  { CLIENT_OPCODE_GET_ADJUSTED_TIME,          "Get Adjusted Time"        },
  { CLIENT_OPCODE_SUBDOC_GET,                 "Subdoc Get"               },
  { CLIENT_OPCODE_SUBDOC_EXISTS,              "Subdoc Exists"            },
  { CLIENT_OPCODE_SUBDOC_DICT_ADD,            "Subdoc Dictionary Add"    },
  { CLIENT_OPCODE_SUBDOC_DICT_UPSERT,         "Subdoc Dictionary Upsert" },
  { CLIENT_OPCODE_SUBDOC_DELETE,              "Subdoc Delete"            },
  { CLIENT_OPCODE_SUBDOC_REPLACE,             "Subdoc Replace"           },
  { CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_LAST,     "Subdoc Array Push Last"   },
  { CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_FIRST,    "Subdoc Array Push First"  },
  { CLIENT_OPCODE_SUBDOC_ARRAY_INSERT,        "Subdoc Array Insert"      },
  { CLIENT_OPCODE_SUBDOC_ARRAY_ADD_UNIQUE,    "Subdoc Array Add Unique"  },
  { CLIENT_OPCODE_SUBDOC_COUNTER,             "Subdoc Counter"           },
  { CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP,        "Subdoc Multipath Lookup"  },
  { CLIENT_OPCODE_SUBDOC_MULTI_MUTATION,      "Subdoc Multipath Mutation"},
  { CLIENT_OPCODE_SUBDOC_GET_COUNT,           "Subdoc Get Count"         },
  { CLIENT_OPCODE_SUBDOC_REPLACE_BODY_WITH_XATTR, "Subdoc Replace Body With Xattr"},
  { CLIENT_OPCODE_RANGE_SCAN_CREATE,          "RangeScan Create"         },
  { CLIENT_OPCODE_RANGE_SCAN_CONTINUE,        "RangeScan Continue"       },
  { CLIENT_OPCODE_RANGE_SCAN_CANCEL,          "RangeScan Cancel"         },
  { CLIENT_OPCODE_SCRUB,                      "Scrub"                    },
  { CLIENT_OPCODE_ISASL_REFRESH,              "isasl Refresh"            },
  { CLIENT_OPCODE_SSL_CERTS_REFRESH,          "SSL Certificates Refresh" },
  { CLIENT_OPCODE_GET_CMD_TIMER,              "Internal Timer Control"   },
  { CLIENT_OPCODE_SET_CTRL_TOKEN,             "Set Control Token"        },
  { CLIENT_OPCODE_GET_CTRL_TOKEN,             "Get Control Token"        },
  { CLIENT_OPCODE_UPDATE_EXTERNAL_USER_PERMISSIONS, "Update External User Permissions"},
  { CLIENT_OPCODE_RBAC_REFRESH,               "RBAC Refresh"             },
  { CLIENT_OPCODE_AUTH_PROVIDER,              "Auth Provider"            },
  { CLIENT_OPCODE_DROP_PRIVILEGE,             "Drop Privilege"           },
  { CLIENT_OPCODE_ADJUST_TIMEOFDAY,           "Adjust Timeofday"         },
  { CLIENT_OPCODE_EWOULDBLOCK_CTL,            "EWOULDBLOCK Control"      },
  { CLIENT_OPCODE_GET_ERROR_MAP,              "Get Error Map"            },

  /* Internally defined values not valid here */
  { 0, NULL }
};

static value_string_ext client_opcode_vals_ext = VALUE_STRING_EXT_INIT(client_opcode_vals);

typedef enum {
    SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION = 0x01,
    SERVER_OPCODE_AUTHENTICATE = 0x02,
    SERVER_OPCODE_ACTIVE_EXTERNAL_USERS = 0x03,
    SERVER_OPCODE_GET_AUTHORIZATION = 0x04
} server_opcode_t;

static const value_string server_opcode_vals[] = {
        { SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION, "ClustermapChangeNotification"},
        { SERVER_OPCODE_AUTHENTICATE, "Authenticate"},
        { SERVER_OPCODE_ACTIVE_EXTERNAL_USERS, "ActiveExternalUsers"},
        { SERVER_OPCODE_GET_AUTHORIZATION, "GetAuthorization"},
        {0, NULL}
};
static value_string_ext server_opcode_vals_ext = VALUE_STRING_EXT_INIT(server_opcode_vals);

static const value_string dcp_connection_type_vals[] = {
  {0, "Consumer"},
  {1, "Producer"},
  {2, "Notifier"},
  {0, NULL}
};

static const value_string vbucket_states_vals[] = {
  {1, "Active"},
  {2, "Replica"},
  {3, "Pending"},
  {4, "Dead"},
  {0, NULL}
};

static int * const datatype_vals[] = {
  &hf_datatype_json,
  &hf_datatype_snappy,
  &hf_datatype_xattr,
  NULL
};

static int * const subdoc_flags[] = {
  &hf_subdoc_flags_mkdirp,
  &hf_subdoc_flags_xattrpath,
  &hf_subdoc_flags_expandmacros,
  &hf_subdoc_flags_reserved,
  NULL
};

static int * const subdoc_doc_flags[] = {
  &hf_subdoc_doc_flags_mkdoc,
  &hf_subdoc_doc_flags_add,
  &hf_subdoc_doc_flags_accessdeleted,
  &hf_subdoc_doc_flags_createasdeleted,
  &hf_subdoc_doc_flags_revivedocument,
  &hf_subdoc_doc_flags_replicaread,
  &hf_subdoc_doc_flags_reserved,
  NULL
};

static int * const set_with_meta_extra_flags[] = {
        &hf_force_meta,
        &hf_force_accept,
        &hf_regenerate_cas,
        &hf_skip_conflict,
        NULL
};

static int * const del_with_meta_extra_flags[] = {
        &hf_force_meta,
        &hf_force_accept,
        &hf_regenerate_cas,
        &hf_skip_conflict,
        &hf_is_expiration,
        NULL
};

static const value_string feature_vals[] = {
  {0x01, "Datatype (deprecated)"},
  {0x02, "TLS"},
  {0x03, "TCP Nodelay"},
  {0x04, "Mutation Seqno"},
  {0x05, "TCP Delay"},
  {0x06, "XATTR"},
  {0x07, "Error Map"},
  {0x08, "Select Bucket"},
  {0x09, "Collections (deprecated)"},
  {0x0a, "Snappy"},
  {0x0b, "JSON"},
  {0x0c, "Duplex"},
  {0x0d, "Clustermap Change Notification"},
  {0x0e, "Unordered Execution"},
  {0x0f, "Tracing"},
  {0x10, "AltRequestSupport"},
  {0x11, "SyncReplication"},
  {0x12, "Collections"},
  {0x13, "OpenTracing"},
  {0x14, "PreserveTtl"},
  {0x15, "VAttr"},
  {0x16, "Point in Time Recovery"},
  {0x17, "SubdocCreateAsDeleted"},
  {0x18, "SubdocDocumentMacroSupport"},
  {0x19, "SubdocReplaceBodyWithXattr"},
  {0x1a, "ReportUnitUsage"},
  {0x1b, "NonBlockingThrottlingMode"},
  {0, NULL}
};

static const value_string dcp_system_event_id_vals [] = {
    {0, "CreateCollection"},
    {1, "DropCollection"},
    {2, "FlushCollection"},
    {3, "CreateScope"},
    {4, "DropScope"},
    {0, NULL}
};

static int * const snapshot_marker_flags [] = {
    &hf_extras_flags_dcp_snapshot_marker_memory,
    &hf_extras_flags_dcp_snapshot_marker_disk,
    &hf_extras_flags_dcp_snapshot_marker_chk,
    &hf_extras_flags_dcp_snapshot_marker_ack,
    &hf_extras_flags_dcp_snapshot_marker_history,
    &hf_extras_flags_dcp_snapshot_marker_may_contain_dups,
    NULL
};

static dissector_handle_t couchbase_handle;
static dissector_handle_t json_handle;

/* desegmentation of COUCHBASE payload */
static gboolean couchbase_desegment_body = TRUE;
static guint couchbase_ssl_port = 11207;
static guint couchbase_ssl_port_pref = 11207;

/** Read out the magic byte (located at offset 0 in the header) */
static guint8 get_magic(tvbuff_t *tvb) {
  return tvb_get_guint8(tvb, 0);
}

/** Read out the opcode (located at offset 1 in the header) */
static guint8 get_opcode(tvbuff_t *tvb) {
  return tvb_get_guint8(tvb, 1);
}

/** Read out the status code from the header (only "valid" for response packets) */
static guint16 get_status(tvbuff_t *tvb) {
  return tvb_get_ntohs(tvb, 6);
}

/** Read out flex size (using the upper bits of the key length when using flex encoding) */
static guint8 get_flex_framing_extras_length(tvbuff_t *tvb) {
  if (is_flex_encoded(get_magic(tvb))) {
    return tvb_get_guint8(tvb, 2);
  }
  return 0;
}

/** Read out the size of the extras section (located at offset 4) */
static guint8 get_extras_length(tvbuff_t *tvb) {
  return tvb_get_guint8(tvb, 4);
}

/** Read out the datatype section (located at offset 5) */
static guint8 get_datatype(tvbuff_t *tvb) {
  return tvb_get_guint8(tvb, 5);
}

/** Read out the length of the key (1 or 2 bytes depending on the encoding) */
static guint16 get_key_length(tvbuff_t *tvb) {
  if (is_flex_encoded(get_magic(tvb))) {
    return tvb_get_guint8(tvb, 3);
  }
  return tvb_get_ntohs(tvb, 2);
}

/** Read out the size for the rest of the frame data */
static guint32 get_body_length(tvbuff_t *tvb) {
  return tvb_get_ntohl(tvb, 8);
}

/* Returns true if the specified opcode's response value is JSON. */
static gboolean
has_json_value(gboolean is_request, guint8 opcode)
{
  if (is_request) {
    switch (opcode) {
    case CLIENT_OPCODE_AUDIT_PUT:
    case CLIENT_OPCODE_RANGE_SCAN_CREATE:
      return TRUE;

    default:
      return FALSE;
    }
  } else {
    switch (opcode) {
    case CLIENT_OPCODE_GET_CLUSTER_CONFIG:
    case CLIENT_OPCODE_SUBDOC_GET:
    case CLIENT_OPCODE_COLLECTIONS_GET_MANIFEST:
    case CLIENT_OPCODE_COLLECTIONS_SET_MANIFEST:
      return TRUE;

    default:
      return FALSE;
    }
  }
}

static void dissect_dcp_xattrs(tvbuff_t *tvb, proto_tree *tree,
                               guint32 value_len, gint offset,
                               packet_info *pinfo) {
  guint32 xattr_size, pair_len;
  gint mark;
  proto_tree *xattr_tree, *pair_tree;
  proto_item *ti;

  proto_tree_add_item_ret_uint(tree, hf_xattr_length, tvb, offset, 4, ENC_BIG_ENDIAN, &xattr_size);
  value_len = value_len - (xattr_size + 4);
  offset += 4;

  ti = proto_tree_add_item(tree, hf_xattrs, tvb, offset, xattr_size, ENC_NA);
  xattr_tree = proto_item_add_subtree(ti, ett_xattrs);

  while (xattr_size > 0) {

    ti = proto_tree_add_item_ret_uint(xattr_tree, hf_xattr_pair_length, tvb, offset, 4, ENC_BIG_ENDIAN, &pair_len);
    pair_tree = proto_item_add_subtree(ti, ett_xattr_pair);
    offset += 4;
    xattr_size -= 4;

    mark = tvb_find_guint8(tvb, offset, pair_len, 0x00);
    if (mark == -1) {
      expert_add_info_format(pinfo, ti, &ef_separator_not_found, "Null byte not found");
      return;
    }

    ti = proto_tree_add_item(pair_tree, hf_xattr_key, tvb, offset, mark - offset, ENC_ASCII | ENC_NA);
    xattr_size -= (mark - offset) + 1;
    pair_len -= (mark - offset) + 1;
    offset = mark + 1;

    mark = tvb_find_guint8(tvb, offset, pair_len, 0x00);
    if (mark == -1) {
      expert_add_info_format(pinfo, ti, &ef_separator_not_found, "Null byte not found");
      return;
    }

    proto_tree_add_item(pair_tree, hf_xattr_value, tvb, offset, mark - offset, ENC_ASCII | ENC_NA);
    xattr_size -= (mark - offset) + 1;
    offset = mark + 1;
  }

  //The regular value
  proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
}

/* Dissects the required extras for subdoc single-path packets */
static void
dissect_subdoc_spath_required_extras(tvbuff_t *tvb, proto_tree *extras_tree,
                                     guint8 extlen, gboolean request, gint* offset,
                                     guint16 *path_len, gboolean *illegal)
{
  if (request) {
    if (extlen >= 3) {
      *path_len = tvb_get_ntohs(tvb, *offset);
      proto_tree_add_item(extras_tree, hf_extras_pathlen, tvb, *offset, 2,
                          ENC_BIG_ENDIAN);
      *offset += 2;

      proto_tree_add_bitmask(extras_tree, tvb, *offset, hf_subdoc_flags,
                             ett_extras_flags, subdoc_flags, ENC_BIG_ENDIAN);
      *offset += 1;
    } else {
      /* Must always have at least 3 bytes of extras */
      *illegal = TRUE;
    }
  }
}

static void dissect_server_request_extras(tvbuff_t *tvb _U_, packet_info *pinfo _U_, proto_tree *tree, gint offset, guint8 extlen, guint8 opcode) {
  if (extlen == 0) {
    switch (opcode) {
      case SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION:
        proto_tree_add_expert_format(tree, pinfo, &ef_warn_must_have_extras, tvb, offset, 0,
                                     "ClustermapChangeNotification request must have extras");
        return;

      case SERVER_OPCODE_GET_AUTHORIZATION:
      case SERVER_OPCODE_AUTHENTICATE:
      case SERVER_OPCODE_ACTIVE_EXTERNAL_USERS:
        // Success! none of these commands use extras

      default:
        // Probably ok as we don't know about the opcode
        return;
    }
  }


  proto_item *extras_item = proto_tree_add_item(tree, hf_extras, tvb, offset, extlen, ENC_NA);
  proto_tree *extras_tree = proto_item_add_subtree(extras_item, ett_extras);

  if (opcode == SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION) {
    // Expected 16 bytes of extras!
    if (extlen < 16) {
      proto_tree_add_expert_format(extras_tree, pinfo,
                                   &ef_warn_illegal_extras_length, tvb,
                                   offset, extlen,
                                   "ClustermapChangeNotification should have 16 bytes of extras");
      return;
    }

    proto_tree_add_item(extras_tree, hf_server_extras_cccp_epoch, tvb, offset, 8, ENC_BIG_ENDIAN);
    offset += 8;
    proto_tree_add_item(extras_tree, hf_server_extras_cccp_revno, tvb, offset, 8, ENC_BIG_ENDIAN);

    if (extlen > 16) {
      proto_tree_add_expert_format(extras_tree, pinfo,
                                   &ef_warn_illegal_extras_length, tvb,
                                   offset + 16, extlen - 16,
                                   "Unexpected amount of extras");
    }
    return;
  }

  // we don't know how to decode this!
  proto_tree_add_item(extras_tree, hf_extras_unknown, tvb, offset, extlen, ENC_NA);
}

static void
dissect_server_response_extras(tvbuff_t *tvb, packet_info *pinfo,
                               proto_tree *tree, gint offset, guint8 extlen,
                               guint8 opcode _U_) {
  if (extlen == 0) {
    // Success! none of the known commands use extras
    return;
  }

  proto_item *extras_item = proto_tree_add_item(tree, hf_extras, tvb, offset,
                                                extlen, ENC_NA);
  proto_tree *extras_tree = proto_item_add_subtree(extras_item, ett_extras);
  proto_tree_add_expert_format(extras_tree, pinfo,
                               &ef_warn_illegal_extras_length, tvb,
                               offset, extlen,
                               "Unexpected amount of extras");
}

static void
dissect_client_extras(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                      gint offset, guint8 extlen, guint8 opcode, gboolean request,
                      guint16 *path_len)
{
  proto_tree *extras_tree = NULL;
  proto_item *extras_item = NULL;
  gint        save_offset = offset, ii;
  guint       bpos;
  gboolean    illegal = FALSE;  /* Set when extras shall not be present */
  gboolean    missing = FALSE;  /* Set when extras is missing */
  gboolean    first_flag;
  guint32     flags;
  proto_item *tf;
  const gchar   *tap_connect_flags[] = {
    "BACKFILL", "DUMP", "LIST_VBUCKETS", "TAKEOVER_VBUCKETS",
    "SUPPORT_ACK", "REQUEST_KEYS_ONLY", "CHECKPOINT", "REGISTERED_CLIENT"
  };

  *path_len = 0;

  if (extlen) {
    extras_item = proto_tree_add_item(tree, hf_extras, tvb, offset, extlen, ENC_NA);
    extras_tree = proto_item_add_subtree(extras_item, ett_extras);
  }

  switch (opcode) {

  case CLIENT_OPCODE_GET:
  case CLIENT_OPCODE_GETQ:
  case CLIENT_OPCODE_GETK:
  case CLIENT_OPCODE_GETKQ:
    if (extlen) {
      if (request) {
        /* Request shall not have extras */
        illegal = TRUE;
      } else {
        proto_tree_add_item(extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      }
    } else if (!request) {
      /* Response must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_SET:
  case CLIENT_OPCODE_SETQ:
  case CLIENT_OPCODE_ADD:
  case CLIENT_OPCODE_ADDQ:
  case CLIENT_OPCODE_REPLACE:
  case CLIENT_OPCODE_REPLACEQ:
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;

        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        proto_tree_add_item(extras_tree, hf_extras_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        proto_tree_add_item(extras_tree, hf_extras_mutation_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_INCREMENT:
  case CLIENT_OPCODE_INCREMENTQ:
  case CLIENT_OPCODE_DECREMENT:
  case CLIENT_OPCODE_DECREMENTQ:
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_delta, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        proto_tree_add_item(extras_tree, hf_extras_initial, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        proto_tree_add_item(extras_tree, hf_extras_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        proto_tree_add_item(extras_tree, hf_extras_mutation_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_FLUSH:
  case CLIENT_OPCODE_FLUSHQ:
    if (extlen) {
      proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
    }
    break;

  case CLIENT_OPCODE_DELETE:
  case CLIENT_OPCODE_DELETEQ:
  case CLIENT_OPCODE_APPEND:
  case CLIENT_OPCODE_APPENDQ:
  case CLIENT_OPCODE_PREPEND:
  case CLIENT_OPCODE_PREPENDQ:
    if (extlen) {
      if (request) {
        /* Must not have extras */
        illegal = TRUE;
      } else {
        proto_tree_add_item(extras_tree, hf_extras_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        proto_tree_add_item(extras_tree, hf_extras_mutation_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      }
    }
    break;

  case CLIENT_OPCODE_QUIT:
  case CLIENT_OPCODE_QUITQ:
  case CLIENT_OPCODE_VERSION:
  case CLIENT_OPCODE_STAT:
  case CLIENT_OPCODE_OBSERVE:
  case CLIENT_OPCODE_OBSERVE_SEQNO:
    /* Must not have extras */
    if (extlen) {
      illegal = TRUE;
    }
    break;

  case CLIENT_OPCODE_GET_ALL_VB_SEQNOS:
    if (extlen) {
      if (request) {
        /* May have extras */
        proto_tree_add_item(extras_tree, hf_vbucket_states_state, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        /* Must not have extras */
        illegal = TRUE;
      }
    }
    break;

  case CLIENT_OPCODE_TAP_CONNECT:
    {
    static int * const extra_flags[] = {
        &hf_extras_flags_backfill,
        &hf_extras_flags_dump,
        &hf_extras_flags_list_vbuckets,
        &hf_extras_flags_takeover_vbuckets,
        &hf_extras_flags_support_ack,
        &hf_extras_flags_request_keys_only,
        &hf_extras_flags_checkpoint,
        NULL
    };

    /* TODO: extra_flags fields are 16-bits wide, whereas flags is 4 bytes? */
    tf = proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);

    flags = tvb_get_ntohl(tvb, offset);
    first_flag = TRUE;
    for (ii = 0; ii < 8; ii++) {
      bpos = 1 << ii;
      if (flags & bpos) {
        if (first_flag) {
          proto_item_append_text(tf, " (");
        }
        proto_item_append_text(tf, "%s%s",
                                  first_flag ? "" : ", ",
                                  tap_connect_flags[ii]);
        first_flag = FALSE;
      }
    }
    if (first_flag == TRUE) {
      proto_item_append_text(tf, " <None>");
    } else {
      proto_item_append_text(tf, ")");
    }

    offset += 4;
    }
    break;

  case CLIENT_OPCODE_TAP_MUTATION:
  case CLIENT_OPCODE_TAP_DELETE:
  case CLIENT_OPCODE_TAP_FLUSH:
  case CLIENT_OPCODE_TAP_OPAQUE:
  case CLIENT_OPCODE_TAP_VBUCKET_SET:
  case CLIENT_OPCODE_TAP_CHECKPOINT_START:
  case CLIENT_OPCODE_TAP_CHECKPOINT_END:
    break;

  case CLIENT_OPCODE_DCP_OPEN_CONNECTION:
    if (extlen) {
      if (request) {
        static int * const extra_flags[] = {
          &hf_extras_flags_dcp_connection_type,
          &hf_extras_flags_dcp_include_xattrs,
          &hf_extras_flags_dcp_no_value,
          &hf_extras_flags_dcp_collections,
          &hf_extras_flags_dcp_include_delete_times,
          NULL
        };

        proto_tree_add_item(extras_tree, hf_extras_seqno, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        /* Response must not have extras (response is in Value) */
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_ADD_STREAM:
    if (extlen) {
      if (request) {
        static int * const extra_flags[] = {
          &hf_extras_flags_dcp_add_stream_takeover,
          &hf_extras_flags_dcp_add_stream_diskonly,
          &hf_extras_flags_dcp_add_stream_latest,
          NULL
        };

        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        proto_tree_add_item(extras_tree, hf_extras_opaque, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      }
    } else {
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_STREAM_REQUEST:
    if (extlen) {
      if (request) {
        /* No extra_flags and proto_tree_add_bitmask don't work with empty flags See Bug:17890
        static int * const extra_flags[] = {
          NULL
        };

        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);
        */
        proto_tree_add_item(extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_reserved, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_start_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_end_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_snap_start_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_snap_end_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_SNAPSHOT_MARKER:
    if (extlen) {
      if (request) {
        // Two formats exist and the extlen allows us to know which is which
        if (extlen == 1) {
          proto_tree_add_item(extras_tree, hf_extras_marker_version, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
        } else if (extlen == 20){
          proto_tree_add_item(extras_tree, hf_extras_start_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
          offset += 8;
          proto_tree_add_item(extras_tree, hf_extras_end_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
          offset += 8;
          proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, snapshot_marker_flags, ENC_BIG_ENDIAN);
          offset += 4;
        } else {
          illegal = TRUE;
        }
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_MUTATION:
    if (extlen) {
      if (request) {
        /* No extra_flags and proto_tree_add_bitmask don't work with empty flags See Bug:17890
        static int * const extra_flags[] = {
          NULL
        };
        */

        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_rev_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        /*
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);
        */
        proto_tree_add_item(extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_lock_time, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_nmeta, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        proto_tree_add_item(extras_tree, hf_extras_nru, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_DELETION:
    if (request) {
      if (extlen == 18 || extlen == 21) {
        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_rev_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;

        // Is this a delete with delete_time (21 bytes) or not (18 bytes)?
        if (extlen == 18) {
          proto_tree_add_item(extras_tree, hf_extras_nmeta, tvb, offset, 2, ENC_BIG_ENDIAN);
          offset += 2;
        } else if (extlen == 21) {
          proto_tree_add_item(extras_tree, hf_extras_delete_time, tvb, offset, 4, ENC_BIG_ENDIAN);
          offset += 4;
          proto_tree_add_item(extras_tree, hf_extras_delete_unused, tvb, offset, 1, ENC_BIG_ENDIAN);
          offset += 1;
        }
      } else if (extlen == 0) {
        missing = TRUE; // request with no extras
      }
    } else if (extlen) {
        illegal = TRUE; // response with extras
    }
    break;
  case CLIENT_OPCODE_DCP_EXPIRATION:
    if (extlen) {
        if (request) {
            proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
            offset += 8;
            proto_tree_add_item(extras_tree, hf_extras_rev_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
            offset += 8;
            if (extlen == 20) {
                proto_tree_add_item(extras_tree, hf_extras_delete_time, tvb, offset, 4, ENC_BIG_ENDIAN);
                offset += 4;
            } else {
                // Handle legacy expiration packet (despite its lack of use)
                proto_tree_add_item(extras_tree, hf_extras_nmeta, tvb, offset, 2, ENC_BIG_ENDIAN);
                offset += 2;
            }
        } else {
            illegal = TRUE;
        }
    } else if (request) {
        /* Request must have extras */
        missing = TRUE;
    }
    break;
  case CLIENT_OPCODE_DCP_FLUSH:
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_rev_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_nmeta, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;

  case CLIENT_OPCODE_DCP_BUFFER_ACKNOWLEDGEMENT:
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_bytes_to_ack, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  case CLIENT_OPCODE_DCP_SYSTEM_EVENT: {
    if (request && extlen == 13) {
      proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
      offset += 8;
      proto_tree_add_item(extras_tree, hf_extras_system_event_id, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_extras_system_event_version, tvb, offset, 1, ENC_BIG_ENDIAN);
      offset += 1;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_PREPARE: {
    if (extlen) {
      if (request) {
        /* No extra_flags and proto_tree_add_bitmask don't work with empty flags See Bug:17890
        static int * const extra_flags[] = {
          NULL
        };
        */

        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_rev_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        /*
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_extras_flags, ett_extras_flags, extra_flags, ENC_BIG_ENDIAN);
        */
        proto_tree_add_item(extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_lock_time, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
        proto_tree_add_item(extras_tree, hf_extras_nru, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(extras_tree, hf_extras_deleted, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(extras_tree, hf_flex_frame_durability_req, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_SEQNO_ACK: {
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_COMMIT: {
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_prepared_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_ABORT: {
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_prepared_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(extras_tree, hf_extras_abort_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_SEQNO_ADVANCED: {
    if (extlen) {
      if (request) {
        proto_tree_add_item(extras_tree, hf_extras_by_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_DCP_OSO_SNAPSHOT: {
    if (extlen) {
      if (request) {
        static int * const extra_flags[] = {
          &hf_extras_flags_dcp_oso_snapshot_begin,
          &hf_extras_flags_dcp_oso_snapshot_end,
          NULL
        };
        proto_tree_add_bitmask(extras_tree,
                               tvb,
                               offset,
                               hf_extras_dcp_oso_snapshot_flags,
                               ett_extras_flags,
                               extra_flags,
                               ENC_BIG_ENDIAN);
        offset += 4;
      } else {
        illegal = TRUE;
      }
    } else if (request) {
      /* Request must have extras */
      missing = TRUE;
    }
    break;
  }
  case CLIENT_OPCODE_SUBDOC_GET:
  case CLIENT_OPCODE_SUBDOC_EXISTS:
    dissect_subdoc_spath_required_extras(tvb, extras_tree, extlen, request,
                                         &offset, path_len, &illegal);
    if (extlen == 4) {
      proto_tree_add_bitmask(extras_tree, tvb, offset, hf_subdoc_doc_flags,
                             ett_extras_flags, subdoc_doc_flags, ENC_BIG_ENDIAN);
      offset += 1;
    }
    break;

  case CLIENT_OPCODE_SUBDOC_DICT_ADD:
  case CLIENT_OPCODE_SUBDOC_DICT_UPSERT:
  case CLIENT_OPCODE_SUBDOC_DELETE:
  case CLIENT_OPCODE_SUBDOC_REPLACE:
  case CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_LAST:
  case CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_FIRST:
  case CLIENT_OPCODE_SUBDOC_ARRAY_INSERT:
  case CLIENT_OPCODE_SUBDOC_ARRAY_ADD_UNIQUE:
  case CLIENT_OPCODE_SUBDOC_COUNTER:
    dissect_subdoc_spath_required_extras(tvb, extras_tree, extlen, request,
                                         &offset, path_len, &illegal);
    if (request) {
      /* optional expiry only permitted for mutation requests,
         if and only if (extlen == 7 || extlen == 8) */
      if (extlen == 7 || extlen == 8) {
        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset,
                            4, ENC_BIG_ENDIAN);
        offset += 4;
      }
      /* optional doc flags only permitted if and only if
         (extlen == 4 || extlen == 8) */
      if (extlen == 4 || extlen == 8) {
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_subdoc_doc_flags,
                               ett_extras_flags, subdoc_doc_flags,
                               ENC_BIG_ENDIAN);
        offset += 1;
      }
      if (extlen != 3 && extlen != 7 && extlen != 4 && extlen != 8) {
        illegal = TRUE;
      }
    }
    break;

  case CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP:
    if (request) {
      if (extlen == 1) {
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_subdoc_doc_flags,
                               ett_extras_flags, subdoc_doc_flags,
                               ENC_BIG_ENDIAN);
        offset += 1;
      } else {
        illegal = TRUE;
      }
    }
    break;

  case CLIENT_OPCODE_SUBDOC_MULTI_MUTATION:
    if (request) {
      if (extlen == 4 || extlen == 5) {
        proto_tree_add_item(extras_tree, hf_extras_expiration, tvb, offset, 4,
                            ENC_BIG_ENDIAN);
        offset += 4;
      }
      if (extlen == 1 || extlen == 5) {
        proto_tree_add_bitmask(extras_tree, tvb, offset, hf_subdoc_doc_flags,
                               ett_extras_flags, subdoc_doc_flags, ENC_BIG_ENDIAN);
        offset += 1;
      }
      if (extlen != 1 && extlen != 4 && extlen != 5) {
        illegal = TRUE;
      }
    }
    break;

  case CLIENT_OPCODE_DEL_WITH_META:
  case CLIENT_OPCODE_SET_WITH_META:
    if (request) {
      proto_tree_add_item(extras_tree, hf_meta_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_meta_expiration, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_meta_revseqno, tvb, offset, 8, ENC_BIG_ENDIAN);
      offset += 8;
      proto_tree_add_item(extras_tree, hf_meta_cas, tvb, offset, 8, ENC_BIG_ENDIAN);
      offset += 8;

      /*The previous 24 bytes are required. The next two fields are optional,
       * hence we are checking the extlen to see what fields we have. As they
       * are different lengths we can do this by just checking the length.*/

      // Options field (4 bytes)
      if (extlen == 28 || extlen == 30) {
          /* TODO: these fields are all 16 bits wide, but field is 32 bits? */
          proto_tree_add_bitmask(
                  extras_tree,
                  tvb,
                  offset,
                  hf_meta_options,
                  ett_extras_flags,
                  (opcode == CLIENT_OPCODE_DEL_WITH_META) ?
                      del_with_meta_extra_flags : set_with_meta_extra_flags,
                  ENC_BIG_ENDIAN);
          offset += 4;
      }
      // Meta Length field (2 bytes)
      if (extlen == 26 || extlen == 30) {
        proto_tree_add_item(extras_tree, hf_metalen, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
      }
    }
    break;

  case CLIENT_OPCODE_GET_META:
    if (request) {
      if(extlen) {
        proto_tree_add_item(extras_tree, hf_meta_reqextmeta, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
      }
    } else {
      proto_tree_add_item(extras_tree, hf_meta_deleted, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_meta_flags, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_exptime, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_extras_meta_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
      offset += 8;
      if (extlen == 21) {
        proto_tree_add_item(extras_tree, hf_confres, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
      }
    }
    break;
  case CLIENT_OPCODE_COLLECTIONS_GET_ID:
    if (!request) {
      proto_tree_add_item(extras_tree, hf_collection_manifest_id, tvb, offset, 8, ENC_BIG_ENDIAN);
      offset += 8;
      proto_tree_add_item(extras_tree, hf_collection_key_id, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
    }
    break;
  case CLIENT_OPCODE_RANGE_SCAN_CONTINUE:
    // https://github.com/couchbase/kv_engine/blob/master/docs/range_scans/range_scan_continue.md
    if (request) {
      proto_tree_add_item(extras_tree, hf_range_scan_uuid, tvb, offset, 16, ENC_BIG_ENDIAN);
      offset += 16;
      proto_tree_add_item(extras_tree, hf_range_scan_item_limit, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_range_scan_time_limit, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
      proto_tree_add_item(extras_tree, hf_range_scan_byte_limit, tvb, offset, 4, ENC_BIG_ENDIAN);
      offset += 4;
    }
    break;
  case CLIENT_OPCODE_RANGE_SCAN_CANCEL:
    // https://github.com/couchbase/kv_engine/blob/master/docs/range_scans/range_scan_cancel.md
    if (request) {
      proto_tree_add_item(extras_tree, hf_range_scan_uuid, tvb, offset, 16, ENC_BIG_ENDIAN);
      offset += 16;
    }
    break;
  default:
    if (extlen) {
      /* Decode as unknown extras */
      proto_tree_add_item(extras_tree, hf_extras_unknown, tvb, offset, extlen, ENC_NA);
      offset += extlen;
    }
    break;
  }
  if (illegal) {
    proto_tree_add_expert_format(extras_tree, pinfo, &ef_warn_shall_not_have_extras, tvb, offset, 0,
                           "%s %s should not have extras",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode 0x%x"),
                           request ? "Request" : "Response");
    offset += extlen;
  } else if (missing) {

    proto_tree_add_expert_format(tree, pinfo, &ef_warn_must_have_extras, tvb, offset, 0,
                           "%s %s must have Extras",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode Ox%x"),
                           request ? "Request" : "Response");
}

  if ((offset - save_offset) != extlen) {
    expert_add_info_format(pinfo, extras_item, &ef_warn_illegal_extras_length,
                           "Illegal Extras length, should be %d", offset - save_offset);
  }
}

/*
  Decode an unsigned leb128 int from a slice within a tvbuff_t
  @param tvb buffer to read from
  @param start index of the first byte of 'slice'
  @param end index of the last byte of the buffer 'slice'
  @param [out] value the decoded value
  @returns next byte after the leb128 bytes or -1 if we failed to decode
*/
static gint
dissect_unsigned_leb128(tvbuff_t *tvb, gint start, gint end, guint32* value) {
    guint8 byte = tvb_get_guint8(tvb, start);
    *value = byte & 0x7f;


    if ((byte & 0x80) == 0x80) {
        guint32 shift = 7;
        gint byte_idx;
        for (byte_idx = start+1; byte_idx < end; byte_idx++) {
            byte = tvb_get_guint8(tvb, byte_idx);
            /* Ensure we are using a valid shift */
            if (shift > 32)
                return -1;
            *value |= (byte & 0x7f) << shift;
            if ((byte & 0x80) == 0) {
                break;
            }
            shift += 7;
        }
        return (byte_idx == end) ? -1 : byte_idx + 1;
    }
    return start + 1;
}

static void dissect_server_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, int keylen, guint8 opcode, bool request) {
  if (keylen == 0) {
    switch (opcode) {
      case SERVER_OPCODE_GET_AUTHORIZATION:
        if (request) {
          proto_tree_add_expert_format(tree, pinfo, &ef_warn_must_have_key,
                                       tvb, offset, 0,
                                       "GetAuthorization request must have key");
        }
        return;
      case SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION:
        if (request) {
          proto_tree_add_expert_format(tree, pinfo, &ef_warn_must_have_key,
                                       tvb, offset, 0,
                                       "ClustermapChangeNotification request must have key");
        }
      case SERVER_OPCODE_AUTHENTICATE:
      case SERVER_OPCODE_ACTIVE_EXTERNAL_USERS:
        // Success! none of these commands want a key
      default:
        // Probably ok as we don't know about the opcode
        return;
    }
  }

  proto_item *ti = proto_tree_add_item(tree, hf_key, tvb, offset, keylen, ENC_UTF_8 | ENC_STR_HEX);

  switch (opcode) {
    case SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION:
      if (!request) {
        expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_key,
                               "ClustermapChangeNotification response shall not have key");
      }
      break;

    case SERVER_OPCODE_AUTHENTICATE:
    case SERVER_OPCODE_ACTIVE_EXTERNAL_USERS:
        expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_key,
                               "%s %s shall not have Key",
                               val_to_str_ext(opcode,
                                              &server_opcode_vals_ext,
                                              "Opcode 0x%x"),
                               request ? "Request" : "Response");
      break;

    case SERVER_OPCODE_GET_AUTHORIZATION:
      if (!request) {
          expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_key,
                                 "GetAuthorization response shall not have key");
      }
      break;
    default:
      break;
  }
}

static void
dissect_client_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                   gint offset, int keylen, guint8 opcode, gboolean request)
{
  proto_item *ti = NULL;
  gboolean    illegal = FALSE;  /* Set when key shall not be present */
  gboolean    missing = FALSE;  /* Set when key is missing */

  if (keylen) {
    bool collection_encoded_key = TRUE;
    switch (opcode) {
      case CLIENT_OPCODE_STAT:
      case CLIENT_OPCODE_HELLO:
      case CLIENT_OPCODE_SASL_AUTH:
      case CLIENT_OPCODE_SASL_STEP:
      case CLIENT_OPCODE_IOCTL_GET:
      case CLIENT_OPCODE_IOCTL_SET:
      case CLIENT_OPCODE_DCP_CONTROL:
      case CLIENT_OPCODE_SET_PARAM:
      case CLIENT_OPCODE_CREATE_BUCKET:
      case CLIENT_OPCODE_DELETE_BUCKET:
      case CLIENT_OPCODE_SELECT_BUCKET:
      case CLIENT_OPCODE_IFCONFIG:
        collection_encoded_key = FALSE;
        break;
      default:
        break;
    }

    ti = proto_tree_add_item(tree, hf_key, tvb, offset, keylen, ENC_UTF_8|ENC_STR_HEX);

    if (collection_encoded_key) {
      /* assume collections are enabled and add a field for the CID */
      guint32 cid = 0;
      gint ok = dissect_unsigned_leb128(tvb, offset, offset + keylen, &cid);

      /* Add collection info to a subtree */
      proto_tree *cid_tree = proto_item_add_subtree(ti, ett_collection_key);

      if (ok == -1) {
        /* cid decode issue, could just be a non-collection stream, don't warn
           just add some info */
        proto_tree_add_string_format(cid_tree,
                                     hf_collection_key_logical,
                                     tvb,
                                     offset,
                                     keylen,
                                     NULL,
                                     "Collection ID didn't decode, maybe no CID.");
      } else {
        proto_tree_add_uint(cid_tree, hf_collection_key_id, tvb, offset,
                            (ok - offset), cid);
        proto_tree_add_item(cid_tree, hf_collection_key_logical, tvb,
                            ok, keylen - (ok - offset), ENC_UTF_8 | ENC_STR_HEX);
      }
    }
    offset += keylen;
  }

  /* inSanity check */
  if (keylen) {
    switch (opcode) {
    case CLIENT_OPCODE_QUIT:
    case CLIENT_OPCODE_QUITQ:
    case CLIENT_OPCODE_NOOP:
    case CLIENT_OPCODE_VERSION:
    case CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST:
    case CLIENT_OPCODE_DCP_BUFFER_ACKNOWLEDGEMENT:
    case CLIENT_OPCODE_GET_ALL_VB_SEQNOS:
      /* Request and Response must not have key */
      illegal = TRUE;
      break;

    case CLIENT_OPCODE_SET:
    case CLIENT_OPCODE_ADD:
    case CLIENT_OPCODE_REPLACE:
    case CLIENT_OPCODE_DELETE:
    case CLIENT_OPCODE_SETQ:
    case CLIENT_OPCODE_ADDQ:
    case CLIENT_OPCODE_REPLACEQ:
    case CLIENT_OPCODE_DELETEQ:
    case CLIENT_OPCODE_FLUSH:
    case CLIENT_OPCODE_APPEND:
    case CLIENT_OPCODE_PREPEND:
    case CLIENT_OPCODE_FLUSHQ:
    case CLIENT_OPCODE_APPENDQ:
    case CLIENT_OPCODE_PREPENDQ:
      /* Response must not have a key */
      if (!request) {
        illegal = TRUE;
      }
      break;

    case CLIENT_OPCODE_DCP_ADD_STREAM:
    case CLIENT_OPCODE_DCP_CLOSE_STREAM:
    case CLIENT_OPCODE_DCP_STREAM_END:
    case CLIENT_OPCODE_DCP_SNAPSHOT_MARKER:
    case CLIENT_OPCODE_DCP_FLUSH:
    case CLIENT_OPCODE_DCP_SET_VBUCKET_STATE:
      /* Request must not have a key */
      if (request) {
        illegal = TRUE;
      }
      break;
    }
  } else {
    switch (opcode) {
    case CLIENT_OPCODE_GET:
    case CLIENT_OPCODE_GETQ:
    case CLIENT_OPCODE_GETK:
    case CLIENT_OPCODE_GETKQ:
    case CLIENT_OPCODE_SET:
    case CLIENT_OPCODE_ADD:
    case CLIENT_OPCODE_REPLACE:
    case CLIENT_OPCODE_DELETE:
    case CLIENT_OPCODE_SETQ:
    case CLIENT_OPCODE_ADDQ:
    case CLIENT_OPCODE_REPLACEQ:
    case CLIENT_OPCODE_DELETEQ:
    case CLIENT_OPCODE_INCREMENT:
    case CLIENT_OPCODE_DECREMENT:
    case CLIENT_OPCODE_INCREMENTQ:
    case CLIENT_OPCODE_DECREMENTQ:
    case CLIENT_OPCODE_DCP_OPEN_CONNECTION:
    case CLIENT_OPCODE_DCP_MUTATION:
    case CLIENT_OPCODE_DCP_DELETION:
    case CLIENT_OPCODE_DCP_EXPIRATION:
    case CLIENT_OPCODE_DCP_SYSTEM_EVENT:
      /* Request must have key */
      if (request) {
        missing = TRUE;
      }
      break;
    }
  }

  if (illegal) {
    expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_key, "%s %s shall not have Key",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode 0x%x"),
                           request ? "Request" : "Response");
  } else if (missing) {
    proto_tree_add_expert_format(tree, pinfo, &ef_warn_must_have_key, tvb, offset, 0,
                           "%s %s must have Key",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode Ox%x"),
                           request ? "Request" : "Response");
  }
}

static void
dissect_multipath_lookup_response(tvbuff_t *tvb, packet_info *pinfo,
                                  proto_tree *tree, gint offset, guint32 value_len)
{
  gint end = offset + value_len;
  int spec_idx = 0;

  while (offset < end) {
    proto_item *ti;
    proto_tree *multipath_tree;
    tvbuff_t *json_tvb;
    guint32 result_len;
    gint start_offset = offset;

    ti = proto_tree_add_subtree_format(tree, tvb, offset, -1, ett_multipath,
                                       &multipath_tree, "Lookup Result [ %u ]",
                                       spec_idx);

    proto_tree_add_item(multipath_tree, hf_status, tvb, offset, 2,
                        ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item_ret_uint(multipath_tree, hf_value_length, tvb, offset,
                                 4, ENC_BIG_ENDIAN, &result_len);
    offset += 4;

    proto_tree_add_item(multipath_tree, hf_value, tvb, offset, result_len,
                        ENC_ASCII | ENC_NA);
    if (result_len > 0) {
        json_tvb = tvb_new_subset_length(tvb, offset, result_len);
        call_dissector(json_handle, json_tvb, pinfo, multipath_tree);
    }
    offset += result_len;

    proto_item_set_len(ti, offset - start_offset);

    spec_idx++;
  }
}

static void
dissect_multipath_mutation_response(tvbuff_t *tvb, packet_info *pinfo,
                                    proto_tree *tree, gint offset, guint32 value_len)
{
  gint end = offset + value_len;
  int spec_idx = 0;

  /* Expect a variable number of mutation responses:
   * - If response.status == SUCCESS, zero to N responses, one for each mutation
   *   spec which returns a value.
   * - If response.status != SUCCESS, exactly 1 response, for first failing
   *   spec.
   */
  while (offset < end) {
    proto_item *ti;
    proto_tree *multipath_tree;
    tvbuff_t *json_tvb;
    guint32 status;
    gint start_offset = offset;

    ti = proto_tree_add_subtree_format(tree, tvb, offset, -1, ett_multipath,
                                       &multipath_tree, "Mutation Result [ %u ]",
                                       spec_idx);

    proto_tree_add_item(multipath_tree, hf_multipath_index, tvb, offset, 1,
                        ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item_ret_uint(multipath_tree, hf_status, tvb, offset, 2,
                                 ENC_BIG_ENDIAN, &status);
    offset += 2;
    if (status == STATUS_SUCCESS) {
      guint32 result_len;
      proto_tree_add_item_ret_uint(multipath_tree, hf_value_length, tvb,
                                   offset, 4, ENC_BIG_ENDIAN, &result_len);
      offset += 4;

      proto_tree_add_item(multipath_tree, hf_value, tvb, offset, result_len,
                          ENC_ASCII | ENC_NA);
      if (result_len > 0) {
        json_tvb = tvb_new_subset_length(tvb, offset, result_len);
        call_dissector(json_handle, json_tvb, pinfo, multipath_tree);
      }
      offset += result_len;
    }
    proto_item_set_len(ti, offset - start_offset);

    spec_idx++;
  }
}

static void
dissect_multipath_value(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                        gint offset, guint32 value_len, gboolean is_mutation,
                        gboolean request)
{
  gint end = offset + value_len;
  int spec_idx = 0;
  proto_item *ti;
  proto_tree *multipath_tree;

  if (request) {
    gint min_spec_size;

    /* Minimum size is the fixed header. */
    min_spec_size = (is_mutation ? 8 : 4);

    while (offset + min_spec_size <= end) {
      guint32 path_len;
      guint32 spec_value_len = 0;
      gint start_offset = offset;

      ti = proto_tree_add_subtree_format(tree, tvb, offset, -1, ett_multipath,
                                         &multipath_tree,
                                         (is_mutation ? "Mutation spec [ %u ]"
                                                      : "Lookup spec [ %u ]"),
                                         spec_idx);

      proto_tree_add_item(multipath_tree, hf_multipath_opcode, tvb, offset, 1,
                          ENC_BIG_ENDIAN);
      offset += 1;
      proto_tree_add_bitmask(multipath_tree, tvb, offset, hf_subdoc_flags,
                             ett_extras_flags, subdoc_flags, ENC_BIG_ENDIAN);
      offset += 1;

      proto_tree_add_item_ret_uint(multipath_tree, hf_multipath_pathlen, tvb,
                                   offset, 2, ENC_BIG_ENDIAN, &path_len);
      offset += 2;

      if (is_mutation) {
        proto_tree_add_item_ret_uint(multipath_tree, hf_multipath_valuelen,
                                     tvb, offset, 4, ENC_BIG_ENDIAN,
                                     &spec_value_len);
        offset += 4;
      }

      if (path_len) {
        proto_tree_add_item(multipath_tree, hf_multipath_path, tvb, offset, path_len,
                            ENC_ASCII | ENC_NA);
        offset += path_len;
      }

      if (spec_value_len > 0) {
        proto_tree_add_item(multipath_tree, hf_multipath_value, tvb, offset,
                            spec_value_len, ENC_ASCII | ENC_NA);
        offset += spec_value_len;
      }

      proto_item_set_len(ti, offset - start_offset);

      spec_idx++;
    }
  } else {
    if (is_mutation) {
      dissect_multipath_mutation_response(tvb, pinfo, tree, offset, value_len);
    } else {
      dissect_multipath_lookup_response(tvb, pinfo, tree, offset, value_len);
    }
  }
}

static void
dissect_value(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
              gint offset, guint32 value_len, guint16 path_len, guint8 opcode,
              gboolean request, guint8 datatype)
{
  proto_item *ti = NULL;
  gboolean    illegal = FALSE;  /* Set when value shall not be present */
  gboolean    missing = FALSE;  /* Set when value is missing */

  if (value_len > 0) {
    if (opcode == CLIENT_OPCODE_OBSERVE) {
      proto_tree *observe_tree;
      gint oo = offset, end = offset + value_len;
      ti = proto_tree_add_item(tree, hf_observe, tvb, offset, value_len, ENC_ASCII);
      observe_tree = proto_item_add_subtree(ti, ett_observe);
      while (oo < end) {
        guint16 kl; /* keylength */
        proto_tree_add_item(observe_tree, hf_observe_vbucket, tvb, oo, 2, ENC_BIG_ENDIAN);
        oo += 2;
        kl = tvb_get_ntohs(tvb, oo);
        proto_tree_add_item(observe_tree, hf_observe_keylength, tvb, oo, 2, ENC_BIG_ENDIAN);
        oo += 2;
        proto_tree_add_item(observe_tree, hf_observe_key, tvb, oo, kl, ENC_ASCII);
        oo += kl;
        if (!request) {
          proto_tree_add_item(observe_tree, hf_observe_status, tvb, oo, 1, ENC_BIG_ENDIAN);
          oo++;
          proto_tree_add_item(observe_tree, hf_observe_cas, tvb, oo, 8, ENC_BIG_ENDIAN);
          oo += 8;
        }
      }
    } else if (opcode == CLIENT_OPCODE_OBSERVE_SEQNO) {
      if (request) {
        ti = proto_tree_add_item(tree, hf_observe_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        if (value_len != 8) {
          expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be 8");
        }
      } else {
        /*
         * <format_type, vbucket id, vbucket uuid, last_persisted_seqno, current_seqno>
         *
         * - format_type is of type uint8_t and it describes whether
         *   the vbucket has failed over or not. 1 indicates a hard
         *   failover, 0 indicates otherwise.
         * - vbucket id is of type uint16_t and it is the identifier for
         *   the vbucket.
         * - vbucket uuid is of type uint64_t and it represents a UUID for
         *    the vbucket.
         * - last_persisted_seqno is of type uint64_t and it is the
         *   last sequence number that was persisted for this
         *   vbucket.
         * - current_seqno is of the type uint64_t and it is the
         *   sequence number of the latest mutation in the vbucket.
         *
         * In the case of a hard failover, the tuple is of the form
         * <format_type, vbucket id, vbucket uuid, last_persisted_seqno, current_seqno,
         * old vbucket uuid, last_received_seqno>
         *
         * - old vbucket uuid is of type uint64_t and it is the
         *   vbucket UUID of the vbucket prior to the hard failover.
         *
         * - last_received_seqno is of type uint64_t and it is the
         *   last received sequence number in the old vbucket uuid.
         *
         * The other fields are the same as that mentioned in the normal case.
         */
        guint8 failed_over;

        proto_tree_add_item(tree, hf_observe_failed_over, tvb, offset, 1, ENC_BIG_ENDIAN);
        failed_over = tvb_get_guint8(tvb, offset);
        offset++;
        proto_tree_add_item(tree, hf_observe_vbucket, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        proto_tree_add_item(tree, hf_observe_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(tree, hf_observe_last_persisted_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(tree, hf_observe_current_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        if (failed_over) {
          proto_tree_add_item(tree, hf_observe_old_vbucket_uuid, tvb, offset, 8, ENC_BIG_ENDIAN);
          offset += 8;
          proto_tree_add_item(tree, hf_observe_last_received_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        }
      }
    } else if (!request && (opcode == CLIENT_OPCODE_DCP_STREAM_REQUEST || opcode == CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST)) {
      if (value_len % 16 != 0) {
        expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Response with bad failover log length");
      } else {
        proto_tree *failover_log_tree;
        gint cur = offset, end = offset + value_len;
        ti = proto_tree_add_item(tree, hf_failover_log, tvb, offset, value_len, ENC_ASCII);
        failover_log_tree = proto_item_add_subtree(ti, ett_failover_log);
        ti = proto_tree_add_uint(failover_log_tree, hf_failover_log_size, tvb, offset, 0, (end - cur) / 16);
        proto_item_set_generated(ti);
        while (cur < end) {
          proto_tree_add_item(failover_log_tree, hf_failover_log_vbucket_uuid, tvb, cur, 8, ENC_BIG_ENDIAN);
          cur += 8;
          proto_tree_add_item(failover_log_tree, hf_failover_log_vbucket_seqno, tvb, cur, 8, ENC_BIG_ENDIAN);
          cur += 8;
        }
      }
    } else if (!request && opcode == CLIENT_OPCODE_GET_ALL_VB_SEQNOS) {
      if (value_len % 10 != 0) {
        expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Response with bad body length");
      } else {
        proto_tree *vbucket_states_tree;
        gint cur = offset, end = offset + value_len;
        ti = proto_tree_add_item(tree, hf_vbucket_states, tvb, offset, value_len, ENC_ASCII);
        vbucket_states_tree = proto_item_add_subtree(ti, ett_vbucket_states);
        ti = proto_tree_add_uint(vbucket_states_tree, hf_vbucket_states_size, tvb, offset, 0, (end - cur) / 10);
        proto_item_set_generated(ti);
        while (cur < end) {
          proto_tree_add_item(vbucket_states_tree, hf_vbucket_states_id, tvb, cur, 2, ENC_BIG_ENDIAN);
          cur += 2;
          proto_tree_add_item(vbucket_states_tree, hf_vbucket_states_seqno, tvb, cur, 8, ENC_BIG_ENDIAN);
          cur += 8;
        }
      }
    } else if (!request && (opcode == CLIENT_OPCODE_INCREMENT || opcode == CLIENT_OPCODE_DECREMENT)) {
      ti = proto_tree_add_item(tree, hf_uint64_response, tvb, offset, 8, ENC_BIG_ENDIAN);
      if (value_len != 8) {
        expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be 8");
      }
    } else if (has_json_value(request, opcode)) {
      tvbuff_t *json_tvb;
      ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
      json_tvb = tvb_new_subset_length(tvb, offset, value_len);
      call_dissector(json_handle, json_tvb, pinfo, tree);

    } else if (opcode == CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP ||
               opcode == CLIENT_OPCODE_SUBDOC_MULTI_MUTATION) {
      dissect_multipath_value(tvb, pinfo, tree, offset, value_len,
                              (opcode == CLIENT_OPCODE_SUBDOC_MULTI_MUTATION),
                              request);

    } else if (opcode == CLIENT_OPCODE_HELLO) {
      gint curr = offset, end = offset + value_len;
      proto_tree *hello_features_tree;
      ti = proto_tree_add_item(tree, hf_hello_features, tvb, offset, value_len, ENC_ASCII);
      hello_features_tree = proto_item_add_subtree(ti, ett_hello_features);
      while (curr < end) {
        proto_tree_add_item(hello_features_tree, hf_hello_features_feature, tvb, curr, 2, ENC_BIG_ENDIAN);
        curr += 2;
      }
    } else if (!request && opcode == CLIENT_OPCODE_RANGE_SCAN_CREATE) {
      proto_tree_add_item(tree, hf_range_scan_uuid, tvb, offset, 16, ENC_BIG_ENDIAN);
    } else if (path_len != 0) {
        ti = proto_tree_add_item(tree, hf_path, tvb, offset, path_len, ENC_ASCII | ENC_NA);
        value_len -= path_len;
        if (value_len > 0) {
            ti = proto_tree_add_item(tree, hf_value, tvb, offset + path_len,
                                     value_len, ENC_ASCII | ENC_NA);
        }
    } else if (request && opcode == CLIENT_OPCODE_CREATE_BUCKET) {
      gint sep, equals_pos, sep_pos, config_len;
      proto_tree *key_tree, *config_tree = NULL;

      /* There are 2 main items stored in the value. The bucket type (represented by a path to the engine) and the
       * bucket config. These are separated by a NULL byte with the bucket type coming first.*/

      sep = tvb_find_guint8(tvb, offset, value_len, 0x00);
      if (sep == -1) {
        ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII);
        expert_add_info_format(pinfo, ti, &ef_separator_not_found, "Null byte not found");
      } else {
        proto_tree_add_item(tree, hf_bucket_type, tvb, offset, sep - offset, ENC_ASCII | ENC_NA);
        config_len = value_len - (sep - offset) - 1; //Don't include NULL byte in length
        if(config_len <= 0) {
          expert_add_info_format(pinfo, ti, &ef_separator_not_found, "Separator not found in expected location");
        } else {
          offset = sep + 1;// Ignore NULL byte

          ti = proto_tree_add_item(tree, hf_bucket_config, tvb, offset, config_len, ENC_ASCII | ENC_NA);
          config_tree = proto_item_add_subtree(ti, ett_config);
        }

        /* The config is arranged as "key=value;key=value..."*/
        while (config_len > 0) {
          // Get the key
          equals_pos = tvb_find_guint8(tvb, offset, config_len, 0x3d);
          if (equals_pos == -1) {
            expert_add_info_format(pinfo, ti, &ef_illegal_value, "Each key needs a value");
            break; // Break out the while loop
          }
          ti = proto_tree_add_item(config_tree, hf_config_key, tvb, offset, equals_pos - offset, ENC_ASCII | ENC_NA);
          key_tree = proto_item_add_subtree(ti, ett_config_key);
          config_len -= (equals_pos - offset + 1);
          offset = equals_pos + 1;
          if (config_len <= 0) {
            expert_add_info_format(pinfo, ti, &ef_illegal_value, "Corresponding value missing");
            break;//Break out of while loop
          }

          // Get the value
          sep_pos = tvb_find_guint8(tvb, offset, config_len, 0x3b);
          if (sep_pos == -1) {
            expert_add_info_format(pinfo, ti, &ef_separator_not_found, "Each key-value pair must be terminated by semi-colon");
            break; // Break out the while loop
          }
          proto_tree_add_item(key_tree, hf_config_value, tvb, offset, sep_pos - offset, ENC_ASCII | ENC_NA);
          config_len -= (sep_pos - offset + 1);
          offset = sep_pos + 1;
        }
      }
    } else if ((datatype & DT_XATTR) && (opcode == CLIENT_OPCODE_SET_WITH_META ||
      opcode == CLIENT_OPCODE_DCP_MUTATION || opcode == CLIENT_OPCODE_DCP_DELETION ||
      opcode == CLIENT_OPCODE_DCP_EXPIRATION || opcode == CLIENT_OPCODE_DCP_PREPARE ||
      opcode == CLIENT_OPCODE_DEL_WITH_META || opcode == CLIENT_OPCODE_ADD_WITH_META ||
      opcode == CLIENT_OPCODE_SETQ_WITH_META || opcode == CLIENT_OPCODE_DELQ_WITH_META ||
      opcode == CLIENT_OPCODE_ADDQ_WITH_META )) {

      dissect_dcp_xattrs(tvb, tree, value_len, offset, pinfo);
    } else if (request && opcode == CLIENT_OPCODE_GET_ERROR_MAP) {
      if (value_len != 2) {
        expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be 2");
        ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
      } else {
        ti = proto_tree_add_item(tree, hf_get_errmap_version, tvb, offset, value_len, ENC_BIG_ENDIAN);
      }
    } else if (request && opcode == CLIENT_OPCODE_DCP_SNAPSHOT_MARKER) {
        if (value_len < 20) {
            expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be at least 20");
            ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
        }

        proto_tree_add_item(tree, hf_extras_start_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_item(tree, hf_extras_end_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
        offset += 8;
        proto_tree_add_bitmask(tree, tvb, offset, hf_extras_flags, ett_extras_flags, snapshot_marker_flags, ENC_BIG_ENDIAN);
        offset += 4;

        if (value_len > 20) {
            if (value_len < 36) {
                expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be at least 36");
                ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
            }

            proto_tree_add_item(tree, hf_extras_max_visible_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
            offset += 8;
            proto_tree_add_item(tree, hf_extras_high_completed_seqno, tvb, offset, 8, ENC_BIG_ENDIAN);
            offset += 8;

            if (value_len > 36) {
                if (value_len != 44) {
                    expert_add_info_format(pinfo, ti, &ef_warn_illegal_value_length, "Illegal Value length, should be 44");
                    ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
                }

                proto_tree_add_item(tree, hf_extras_timestamp, tvb, offset, 8, ENC_BIG_ENDIAN);
            }
        }
    } else {
      ti = proto_tree_add_item(tree, hf_value, tvb, offset, value_len, ENC_ASCII | ENC_NA);
#ifdef HAVE_SNAPPY
      if (datatype & DT_SNAPPY) {
        size_t orig_size = 0;
        snappy_status ret;
        guchar *decompressed_buffer = NULL;
        tvbuff_t* compressed_tvb = NULL;

        ret = snappy_uncompressed_length(tvb_get_ptr(tvb, offset, -1),
                                         tvb_captured_length_remaining(tvb, offset),
                                         &orig_size);
        if (ret == SNAPPY_OK) {
          decompressed_buffer = (guchar*)wmem_alloc(pinfo->pool, orig_size);
          ret = snappy_uncompress(tvb_get_ptr(tvb, offset, -1),
                                  tvb_captured_length_remaining(tvb, offset),
                                  decompressed_buffer,
                                  &orig_size);
          if (ret == SNAPPY_OK) {
            compressed_tvb = tvb_new_child_real_data(tvb, decompressed_buffer, (guint32)orig_size, (guint32)orig_size);
            add_new_data_source(pinfo, compressed_tvb, "Decompressed Data");
            if (datatype & DT_JSON) {
              call_dissector(json_handle, compressed_tvb, pinfo, tree);
            }
          } else {
            expert_add_info_format(pinfo, ti, &ef_compression_error, "Error uncompressing snappy data");
          }
        } else {
            expert_add_info_format(pinfo, ti, &ef_compression_error, "Error uncompressing snappy data");
        }
      }
#endif
    }
  }

  /* Sanity check */
  if (value_len) {
    switch (opcode) {
    case CLIENT_OPCODE_GET:
    case CLIENT_OPCODE_GETQ:
    case CLIENT_OPCODE_GETK:
    case CLIENT_OPCODE_GETKQ:
    case CLIENT_OPCODE_INCREMENT:
    case CLIENT_OPCODE_DECREMENT:
    case CLIENT_OPCODE_VERSION:
    case CLIENT_OPCODE_INCREMENTQ:
    case CLIENT_OPCODE_DECREMENTQ:
    case CLIENT_OPCODE_DCP_OPEN_CONNECTION:
    case CLIENT_OPCODE_DCP_ADD_STREAM:
    case CLIENT_OPCODE_DCP_CLOSE_STREAM:
    case CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST:
    case CLIENT_OPCODE_DCP_STREAM_END:
    case CLIENT_OPCODE_DCP_DELETION:
    case CLIENT_OPCODE_DCP_EXPIRATION:
    case CLIENT_OPCODE_DCP_FLUSH:
    case CLIENT_OPCODE_DCP_SET_VBUCKET_STATE:
      /* Request must not have value */
      if (request) {
        illegal = TRUE;
      }
      break;
    case CLIENT_OPCODE_DELETE:
    case CLIENT_OPCODE_QUIT:
    case CLIENT_OPCODE_FLUSH:
    case CLIENT_OPCODE_NOOP:
    case CLIENT_OPCODE_DELETEQ:
    case CLIENT_OPCODE_QUITQ:
    case CLIENT_OPCODE_FLUSHQ:
      /* Request and Response must not have value */
      illegal = TRUE;
      break;
    case CLIENT_OPCODE_SET:
    case CLIENT_OPCODE_ADD:
    case CLIENT_OPCODE_REPLACE:
    case CLIENT_OPCODE_SETQ:
    case CLIENT_OPCODE_ADDQ:
    case CLIENT_OPCODE_REPLACEQ:
    case CLIENT_OPCODE_APPEND:
    case CLIENT_OPCODE_PREPEND:
    case CLIENT_OPCODE_APPENDQ:
    case CLIENT_OPCODE_PREPENDQ:
      /* Response must not have value */
      if (!request) {
        illegal = TRUE;
      }
      break;
    }
  } else {
    switch (opcode) {
    case CLIENT_OPCODE_DCP_FAILOVER_LOG_REQUEST:
      /* Successful response must have value */
      if (!request) {
        missing = TRUE;
      }
      break;
    }
  }

  if (illegal) {
    expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_value, "%s %s shall not have Value",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode 0x%x"),
                           request ? "Request" : "Response");
  } else if (missing) {
    expert_add_info_format(pinfo, ti, &ei_value_missing, "%s %s must have Value",
                           val_to_str_ext(opcode, &client_opcode_vals_ext, "Opcode 0x%x"),
                           request ? "Request" : "Response");
  }
}

static void flex_frame_duration_dissect(tvbuff_t* tvb,
                                        proto_tree* frame_tree,
                                        gint offset,
                                        gint length) {

  if (length != 2) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "FlexFrame: RX/TX Duration with illegal length %d", length);
  } else {
    guint16 encoded_micros = tvb_get_ntohs(tvb, offset);
    proto_tree_add_double(frame_tree,
                          hf_flex_frame_tracing_duration,
                          tvb,
                          offset,
                          2,
                          pow(encoded_micros, 1.74) / 2);
  }
}

static void flex_frame_ru_usage_dissect(tvbuff_t* tvb,
                                        proto_tree* frame_tree,
                                        gint offset,
                                        gint length) {

  if (length != 2) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "Read unit illegal length %d", length);
  } else {
    guint16 units = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(frame_tree, hf_flex_frame_ru_count, tvb, offset, 2, units);
  }
}

static void flex_frame_wu_usage_dissect(tvbuff_t* tvb,
                                        proto_tree* frame_tree,
                                        gint offset,
                                        gint length) {

  if (length != 2) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "Write unit illegal length %d", length);
  } else {
    guint16 units = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(frame_tree, hf_flex_frame_wu_count, tvb, offset, 2, units);
  }
}

static void flex_frame_reorder_dissect(tvbuff_t* tvb,
                                       proto_tree* frame_tree,
                                       gint offset,
                                       gint length) {
  /* Expects no data, so just check len */
  if (length != 0) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "FlexFrame: Out Of Order with illegal length %d", length);
  }
}

static void flex_frame_durability_dissect(tvbuff_t* tvb,
                                          proto_tree* frame_tree,
                                          gint offset,
                                          gint length) {
  if (!(length == 1 || length == 3)) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "FlexFrame: Durability with illegal length %d", length);
    return;
  }
  proto_tree_add_item(frame_tree, hf_flex_frame_durability_req, tvb, offset, 1, ENC_BIG_ENDIAN);
}

static void flex_frame_dcp_stream_id_dissect(tvbuff_t* tvb,
                                             proto_tree* frame_tree,
                                             gint offset,
                                             gint length) {
  if (length != 2) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "FlexFrame: DCP Stream ID with illegal length %d", length);
  } else {
    guint16 sid = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(frame_tree, hf_flex_frame_dcp_stream_id, tvb, offset, 2, sid);
  }
}

static void flex_frame_impersonate_dissect(tvbuff_t* tvb,
                                           proto_tree* frame_tree,
                                           gint offset,
                                           gint length) {
  proto_tree_add_item(frame_tree,
                      hf_flex_frame_impersonated_user,
                      tvb,
                      offset,
                      length,
                      ENC_UTF_8|ENC_STR_HEX);
}

static void flex_frame_preserve_ttl(tvbuff_t* tvb,
                                    proto_tree* frame_tree,
                                    gint offset,
                                    gint length) {
  /* Expects no data, so just check len */
  if (length != 0) {
    proto_tree_add_expert_format(frame_tree,
                                 NULL,
                                 &ef_warn_unknown_flex_len,
                                 tvb,
                                 offset,
                                 length,
                                 "FlexFrame: Preserve TTL with illegal length %d", length);
  }
}

typedef void (*flex_frame_by_id_dissect_fn)(tvbuff_t*,
                                            proto_tree*,
                                            gint,
                                            gint);

struct flex_frame_by_id_dissect {
  guint32 id;
  flex_frame_by_id_dissect_fn handler;
};

static const struct flex_frame_by_id_dissect flex_frame_response_dissect[] = {
  {FLEX_RESPONSE_ID_RX_TX_DURATION, &flex_frame_duration_dissect},
  {FLEX_RESPONSE_ID_RU_USAGE, &flex_frame_ru_usage_dissect},
  {FLEX_RESPONSE_ID_WU_USAGE, &flex_frame_wu_usage_dissect},
  {0, NULL }
};

static const struct flex_frame_by_id_dissect flex_frame_request_dissect[] = {
  { FLEX_REQUEST_ID_REORDER, &flex_frame_reorder_dissect},
  { FLEX_REQUEST_ID_DURABILITY, &flex_frame_durability_dissect},
  { FLEX_REQUEST_ID_DCP_STREAM_ID, &flex_frame_dcp_stream_id_dissect},
  { FLEX_REQUEST_ID_IMPERSONATE, &flex_frame_impersonate_dissect},
  { FLEX_REQUEST_ID_PRESERVE_TTL, &flex_frame_preserve_ttl},
  { 0, NULL }
};

/*
  Flexible Framing Extras:
  https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md
*/
static void dissect_flexible_framing_extras(tvbuff_t* tvb,
                                            packet_info* pinfo,
                                            proto_tree* tree,
                                            gint offset,
                                            guint8 flex_frame_extra_len,
                                            gboolean request) {

    /* select some request/response ID decoders */
  const struct flex_frame_by_id_dissect* id_dissectors = flex_frame_response_dissect;
  int info_id = hf_flex_frame_id_res;
  int info_id_esc = hf_flex_frame_id_res_esc;
  int info_len_id = hf_flex_frame_len;
  if (request) {
    id_dissectors = flex_frame_request_dissect;
    info_id = hf_flex_frame_id_req;
    info_id_esc = hf_flex_frame_id_req_esc;
  }

  /* This first item shows the entire extent of all frame extras.
     If we have multiple frames, we will add them in the iteration */
  proto_tree_add_uint(tree,
                      hf_flex_extras,
                      tvb,
                      offset,
                      flex_frame_extra_len,
                      flex_frame_extra_len);

  /* iterate until we've consumed the flex_frame_extra_len */
  gint bytes_remaining = flex_frame_extra_len;
  int frame_index = 0;

  while (bytes_remaining > 0) {

    /* FrameInfo starts with a 'tag' byte which is formed from 2 nibbles */
    guint8 tag_byte = tvb_get_guint8(tvb, offset);

    /* 0xff isn't defined yet in the spec as to what it should do */
    if (tag_byte == 0xFF) {
      proto_tree_add_expert_format(tree,
                                   pinfo,
                                   &ef_warn_unknown_flex_unsupported,
                                   tvb,
                                   offset,
                                   1,
                                   "Cannot decode 0xFF id/len byte");
      return;
    }

    /* extract the nibbles into u16, if the id/len nibbles are escapes, their
       true values come from following bytes and can be larger than u8 */
    guint16 id = tag_byte >> 4;
    guint16 len = tag_byte & 0x0F;

    int id_size = 1;
    /* Calculate the id/len and add to the tree */
    if (id == FLEX_ESCAPE) {
      id = id + tvb_get_guint8(tvb, offset + 1);
      id_size++;
      info_id = info_id_esc;
    }

    int len_size = 1;
    if (len == FLEX_ESCAPE) {
      len = len + tvb_get_guint8(tvb, offset + 1);
      len_size++;
      info_len_id = hf_flex_frame_len_esc;
    }

    /* add a new sub-tree for this FrameInfo */
    proto_item* flex_item = proto_tree_add_string_format(tree,
                                                         hf_flex_extras_n,
                                                         tvb,
                                                         offset,
                                                         1 + len,
                                                         NULL,
                                                         "Flexible Frame %d",
                                                         frame_index);

    proto_tree* frame_tree = proto_item_add_subtree(flex_item,
                                                    ett_flex_frame_extras);

    /* Now add the info under the sub-tree */
    proto_tree_add_uint(frame_tree, info_id, tvb, offset, id_size, id);
    proto_tree_add_uint(frame_tree, info_len_id, tvb, offset, len_size, len);

    /* this is broken if both len and id are escaped, but we've returned earlier
       for that case (with a warning) */
    offset = offset + 1 + (len_size - 1) + (id_size - 1);
    bytes_remaining = bytes_remaining - 1 - (len_size - 1) - (id_size - 1);;

    /* lookup a dissector function by id */
    int id_index = 0, found = 0;
    while (id_dissectors[id_index].handler) {
      if (id_dissectors[id_index].id == id) {
        id_dissectors[id_index].handler(tvb, frame_tree, offset, len);
        found = 1;
        break;
      }
      id_index++;
    }

    if (!found)  {
      proto_tree_add_expert_format(frame_tree,
                                   pinfo,
                                   &ef_warn_unknown_flex_id,
                                   tvb,
                                   offset,
                                   len,
                                   "FlexFrame: no dissector function for %d", id);
    }

    offset += len;
    bytes_remaining -= len;
    frame_index++;
  }
}

static gboolean
is_xerror(guint8 datatype, guint16 status)
{
  if ((datatype & DT_JSON) && status != STATUS_SUBDOC_MULTI_PATH_FAILURE) {
    return TRUE;
  }
  return FALSE;
}

/// The following section contains dissector functions for the various
/// server initiated push messages (and responses for them).
/// It's easier to understand the logic with a single function per opcode
/// than a long function with a ton of if/else statements

static void d_s_o_clustermap_change_notification_req(tvbuff_t *tvb,
                                                     packet_info *pinfo,
                                                     proto_tree *tree,
                                                     gint offset,
                                                     gint size) {
  if (size == 0) {
    // this is an error!
    expert_add_info_format(pinfo, tree, &ef_warn_illegal_value_length,
                           "Clustermap not present");
    return;
  }
  // The payload is the clustermap in JSON
  proto_tree_add_item(tree, hf_server_clustermap_value, tvb, offset, size,
                      ENC_ASCII | ENC_NA);
  tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
  call_dissector(json_handle, json_tvb, pinfo, tree);
}

static void d_s_o_authenticate_req(tvbuff_t *tvb,
                                   packet_info *pinfo,
                                   proto_tree *tree,
                                   gint offset,
                                   gint size) {
  if (size == 0) {
    // this is an error!
    expert_add_info_format(pinfo, tree, &ef_warn_illegal_value_length,
                           "Authentication payload not present");
    return;
  }
  // The payload is an JSON object with the authentication request
  proto_tree_add_item(tree, hf_server_authentication, tvb, offset, size,
                      ENC_ASCII | ENC_NA);
  tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
  call_dissector(json_handle, json_tvb, pinfo, tree);
}

static void d_s_o_active_external_users_req(tvbuff_t *tvb,
                                            packet_info *pinfo,
                                            proto_tree *tree,
                                            gint offset,
                                            gint size) {
  if (size == 0) {
    // this is an error!
    expert_add_info_format(pinfo, tree, &ef_warn_illegal_value_length,
                           "ActiveExternalUsers payload not present");
    return;
  }
  // The payload is an JSON array with the list of the users
  proto_tree_add_item(tree, hf_server_external_users, tvb, offset, size,
                      ENC_ASCII | ENC_NA);
  tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
  call_dissector(json_handle, json_tvb, pinfo, tree);
}

static void d_s_o_get_authorization_req(tvbuff_t *tvb,
                                        packet_info *pinfo,
                                        proto_tree *tree,
                                        gint offset,
                                        gint size) {
  if (size > 0) {
    // this is an error!
    proto_item *ti = proto_tree_add_item(tree, hf_value, tvb, offset, size,
                                         ENC_ASCII | ENC_NA);
    expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_value,
                           "GetAuthorization shall not have a value");
  }
}

/// Dissect the response to a server initiated push message which
/// don't require a response (the client may send it, but the server
/// will silently just ignore the response).
/// If sent the body should not contain value unless the status code
/// is an error and if so it shall be a JSON payload following the
/// standard error format.
static void d_s_o_server_ignored_response(tvbuff_t *tvb,
                                          packet_info *pinfo,
                                          proto_tree *tree,
                                          gint offset,
                                          gint size) {
  if (size == 0) {
    return;
  }
  proto_item *ti = proto_tree_add_item(tree, hf_value, tvb, offset, size,
                                       ENC_ASCII | ENC_NA);
  if (get_status(tvb) == STATUS_SUCCESS) {
    expert_add_info_format(pinfo, ti, &ef_warn_shall_not_have_value,
                           "Success should not carry value");
  } else {
    tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
    call_dissector(json_handle, json_tvb, pinfo, tree);
  }
}

static void d_s_o_authenticate_res(tvbuff_t *tvb ,
                                   packet_info *pinfo ,
                                   proto_tree *tree ,
                                   gint offset ,
                                   gint size ) {
  if (size == 0) {
    return;
  }

  // Payload is JSON (for success and if there is an error)
  proto_tree_add_item(tree, hf_server_authentication, tvb, offset, size,
                      ENC_ASCII | ENC_NA);
  tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
  call_dissector(json_handle, json_tvb, pinfo, tree);
}

static void d_s_o_get_authorization_res(tvbuff_t *tvb,
                                        packet_info *pinfo,
                                        proto_tree *tree,
                                        gint offset,
                                        gint size) {
  if (size == 0) {
    return;
  }

  // Payload is JSON (for success and if there is an error)
  proto_tree_add_item(tree, hf_server_get_authorization, tvb, offset, size,
                      ENC_ASCII | ENC_NA);
  tvbuff_t *json_tvb = tvb_new_subset_length(tvb, offset, size);
  call_dissector(json_handle, json_tvb, pinfo, tree);

}

/**
 * Does the opcode use the vbucket or not? (does it make any sense to
 * add the vbucket to the info)
 */
static bool opcode_use_vbucket(guint8 magic _U_, guint8 opcode) {
  switch (opcode) {
    case CLIENT_OPCODE_OBSERVE:
    case CLIENT_OPCODE_COLLECTIONS_GET_ID:
    case CLIENT_OPCODE_IFCONFIG:
    case CLIENT_OPCODE_SASL_LIST_MECHS:
    case CLIENT_OPCODE_SASL_AUTH:
    case CLIENT_OPCODE_SASL_STEP:
    case CLIENT_OPCODE_SHUTDOWN:
    case CLIENT_OPCODE_AUDIT_CONFIG_RELOAD:
    case CLIENT_OPCODE_AUDIT_PUT:
    case CLIENT_OPCODE_CONFIG_RELOAD:
    case CLIENT_OPCODE_CONFIG_VALIDATE:
    case CLIENT_OPCODE_IOCTL_SET:
    case CLIENT_OPCODE_IOCTL_GET:
    case CLIENT_OPCODE_HELLO:
    case CLIENT_OPCODE_VERBOSITY:
    case CLIENT_OPCODE_VERSION:
    case CLIENT_OPCODE_NOOP:
    case CLIENT_OPCODE_QUIT:
    case CLIENT_OPCODE_LIST_BUCKETS:
    case CLIENT_OPCODE_CREATE_BUCKET:
    case CLIENT_OPCODE_DELETE_BUCKET:
    case CLIENT_OPCODE_SELECT_BUCKET:
      return FALSE;

    default:
      return TRUE;
  }
}

/**
 * Each frame header consist of 24 bytes in two slightly different formats
 * (byte 6 and 7 is vbucket id in a request and status in a response).
 *
 * This method dissect the frame header. Please refer to
 * https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#request-header
 * for the layout of the frame header.
 */
static void dissect_frame_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *couchbase_tree, proto_item *couchbase_item) {
  guint8 magic = get_magic(tvb);
  proto_item *ti = proto_tree_add_item(couchbase_tree, hf_magic, tvb, 0, 1, ENC_BIG_ENDIAN);
  if (try_val_to_str(magic, magic_vals) == NULL) {
    expert_add_info_format(pinfo, ti, &ef_warn_unknown_magic_byte, "Unknown magic byte: 0x%x", magic);
  }

  guint8 opcode = get_opcode(tvb);

  const gchar *opcode_name;
  if (is_server_magic(magic)) {
    ti = proto_tree_add_item(couchbase_tree, hf_server_opcode, tvb, 1, 1, ENC_BIG_ENDIAN);
    opcode_name = try_val_to_str_ext(opcode, &server_opcode_vals_ext);
  } else {
    ti = proto_tree_add_item(couchbase_tree, hf_opcode, tvb, 1, 1, ENC_BIG_ENDIAN);
    opcode_name = try_val_to_str_ext(opcode, &client_opcode_vals_ext);
  }

  if (opcode_name == NULL) {
    expert_add_info_format(pinfo, ti, &ef_warn_unknown_opcode, "Unknown opcode: 0x%x", opcode);
    opcode_name = "Unknown opcode";
  }
  proto_item_append_text(couchbase_item, ", %s %s, Opcode: 0x%x",
                         opcode_name,
                         val_to_str(magic, magic_vals, "Unknown magic (0x%x)"),
                         opcode);
  col_append_fstr(pinfo->cinfo, COL_INFO, "%s %s, Opcode: 0x%x",
                  opcode_name,
                  val_to_str(magic, magic_vals, "Unknown magic (0x%x)"),
                  opcode);

  /* Check for flex magic, which changes the header format */
  guint16 keylen;
  guint8 flex_frame_extras = get_flex_framing_extras_length(tvb);
  if (is_flex_encoded(magic)) {
    /* 2 separate bytes for the flex_extras and keylen */
    proto_tree_add_item(couchbase_tree, hf_flex_extras_length, tvb, 2, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(couchbase_tree, hf_flex_keylength, tvb, 3, 1, ENC_BIG_ENDIAN);
  } else {
    /* 2 bytes for the key */
    proto_tree_add_item(couchbase_tree, hf_keylength, tvb, 2, 2, ENC_BIG_ENDIAN);
  }
  keylen = get_key_length(tvb);

  guint8 extlen = get_extras_length(tvb);
  proto_tree_add_item(couchbase_tree, hf_extlength, tvb, 4, 1, ENC_BIG_ENDIAN);

  proto_tree_add_bitmask(couchbase_tree, tvb, 5, hf_datatype, ett_datatype, datatype_vals, ENC_BIG_ENDIAN);

  if (is_request_magic(magic)) {
    guint16 vbucket = tvb_get_ntohs(tvb, 6);
    proto_tree_add_item(couchbase_tree, hf_vbucket, tvb, 6, 2, ENC_BIG_ENDIAN);
    if (opcode_use_vbucket(magic, opcode)) {
      proto_item_append_text(couchbase_item, ", vb:%d", vbucket);
      col_append_fstr(pinfo->cinfo, COL_INFO, ", vb:%d", vbucket);
    }
  } else {
    /* This is a response or invalid magic... */
    guint16 status = get_status(tvb);
    ti = proto_tree_add_item(couchbase_tree, hf_status, tvb, 6, 2, ENC_BIG_ENDIAN);
    if (status != 0) {
      expert_add_info_format(pinfo, ti, &ef_warn_unknown_opcode, "%s: %s",
                             val_to_str_ext(opcode, &client_opcode_vals_ext, "Unknown opcode (0x%x)"),
                             val_to_str_ext(status, &status_vals_ext, "Status: 0x%x"));
    }
  }

  guint32 bodylen = get_body_length(tvb);
  guint32 value_len = bodylen - extlen - keylen - flex_frame_extras;
  ti = proto_tree_add_uint(couchbase_tree, hf_value_length, tvb, 8, 0, value_len);
  proto_item_set_generated(ti);

  proto_tree_add_item(couchbase_tree, hf_total_bodylength, tvb, 8, 4, ENC_BIG_ENDIAN);

  /*
   * use little endian (network) encoding for the opaque as this is an opaque
   * field the client could use for whatever they want
   */
  proto_tree_add_item(couchbase_tree, hf_opaque, tvb, 12, 4, ENC_LITTLE_ENDIAN);

  // Finally we've got the CAS (which observe has a special use for)
  if (opcode == CLIENT_OPCODE_OBSERVE) {
    proto_tree_add_item(couchbase_tree, hf_ttp, tvb, 16, 4, ENC_BIG_ENDIAN);
    proto_tree_add_item(couchbase_tree, hf_ttr, tvb, 20, 4, ENC_BIG_ENDIAN);
  } else {
    proto_tree_add_item(couchbase_tree, hf_cas, tvb, 16, 8, ENC_BIG_ENDIAN);
  }
}

/**
 * Dissect the flexible frame info's encoded in the packet
 */
static void dissect_frame_flex_info_section(tvbuff_t *tvb,
                                            packet_info *pinfo,
                                            proto_tree *tree,
                                            gint offset,
                                            guint8 size,
                                            guint8 magic) {
  if (size == 0) {
    return;
  }

  switch (magic) {
    case MAGIC_SERVER_RESPONSE:
    case MAGIC_SERVER_REQUEST:
      // None of the server initiated messages use flex frame encoding!
      proto_tree_add_item(tree, hf_flex_extras, tvb, offset, size, ENC_UTF_8|ENC_STR_HEX);
      proto_tree_add_expert_format(tree,
                                   pinfo,
                                   &ef_warn_unknown_flex_unsupported,
                                   tvb,
                                   offset,
                                   size,
                                   "Server initiated messages don't use flex framing");
      break;

    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_CLIENT_RESPONSE_FLEX:
      dissect_flexible_framing_extras(tvb,
                                      pinfo,
                                      tree,
                                      offset,
                                      size,
                                      is_request_magic(magic));
      break;
    default:
      proto_tree_add_item(tree, hf_flex_extras, tvb, offset, size, ENC_UTF_8|ENC_STR_HEX);
      proto_tree_add_expert_format(tree,
                                   pinfo,
                                   &ef_warn_unknown_flex_unsupported,
                                   tvb,
                                   offset,
                                   size,
                                   "According to the magic we should not have flex encoding");
  }
}

/**
 * Dissect the extras section in the frame
 */
static void dissect_frame_extras(tvbuff_t *tvb,
                                 packet_info *pinfo,
                                 proto_tree *tree,
                                 gint offset,
                                 guint8 size,
                                 guint8 magic,
                                 guint8 opcode,
                                 guint16 *subdoc_path_len) {
  switch (magic) {
    case MAGIC_SERVER_RESPONSE:
      dissect_server_response_extras(tvb, pinfo, tree, offset, size, opcode);
      break;
    case MAGIC_SERVER_REQUEST:
      dissect_server_request_extras(tvb, pinfo, tree, offset, size, opcode);
      break;
    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_CLIENT_RESPONSE_FLEX:
    case MAGIC_CLIENT_REQUEST:
    case MAGIC_CLIENT_RESPONSE:
      dissect_client_extras(tvb, pinfo, tree, offset, size,
                            opcode, is_request_magic(magic), subdoc_path_len);
      break;
    default:
      proto_tree_add_item(tree, hf_extras, tvb, offset, size, ENC_UTF_8|ENC_STR_HEX);
      proto_tree_add_expert_format(tree,
                                   pinfo,
                                   &ef_warn_unknown_extras,
                                   tvb,
                                   offset,
                                   size,
                                   "Invalid magic so we can't interpret extras");
  }
}

/**
 * Dissect the key section in the frame
 */
static void dissect_frame_key(tvbuff_t *tvb,
                              packet_info *pinfo,
                              proto_tree *tree,
                              gint offset,
                              guint16 size,
                              guint8 magic,
                              guint8 opcode) {
  if (is_server_magic(magic)) {
    dissect_server_key(tvb, pinfo, tree, offset, size, opcode,
                       is_request_magic(magic));
  } else {
    dissect_client_key(tvb, pinfo, tree, offset, size, opcode,
                       is_request_magic(magic));
  }
}

static void dissect_client_value(tvbuff_t *tvb,
                                 packet_info *pinfo,
                                 proto_tree *tree,
                                 gint offset,
                                 guint32 size,
                                 guint8 magic,
                                 guint8 opcode,
                                 guint16 subdoc_path_len) {
  guint8 datatype = get_datatype(tvb);
  if (is_request_magic(magic)) {
    dissect_value(tvb, pinfo, tree, offset, size, subdoc_path_len, opcode, true, datatype);
  } else {
    guint16 status = get_status(tvb);
    if (status == 0) {
      dissect_value(tvb, pinfo, tree, offset, size, subdoc_path_len, opcode, false, datatype);
    } else if (size) {
      proto_tree_add_item(tree, hf_value, tvb, offset, size, ENC_ASCII | ENC_NA);
      if (status == STATUS_NOT_MY_VBUCKET || is_xerror(datatype, status)) {
        tvbuff_t *json_tvb;
        json_tvb = tvb_new_subset_length(tvb, offset, size);
        call_dissector(json_handle, json_tvb, pinfo, tree);
      } else if (opcode == CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP) {
        dissect_multipath_lookup_response(tvb, pinfo, tree, offset, size);
      } else if (opcode == CLIENT_OPCODE_SUBDOC_MULTI_MUTATION) {
        dissect_multipath_mutation_response(tvb, pinfo, tree, offset, size);
      }
      col_append_fstr(pinfo->cinfo, COL_INFO, ", %s",
                      val_to_str_ext(status, &status_vals_ext,
                                     "Unknown status: 0x%x"));
    } else {
      /* Newer opcodes do not include a value in non-SUCCESS responses. */
      proto_tree *ti;
      switch (opcode) {
        case CLIENT_OPCODE_SUBDOC_GET:
        case CLIENT_OPCODE_SUBDOC_EXISTS:
        case CLIENT_OPCODE_SUBDOC_DICT_ADD:
        case CLIENT_OPCODE_SUBDOC_DICT_UPSERT:
        case CLIENT_OPCODE_SUBDOC_DELETE:
        case CLIENT_OPCODE_SUBDOC_REPLACE:
        case CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_LAST:
        case CLIENT_OPCODE_SUBDOC_ARRAY_PUSH_FIRST:
        case CLIENT_OPCODE_SUBDOC_ARRAY_INSERT:
        case CLIENT_OPCODE_SUBDOC_ARRAY_ADD_UNIQUE:
        case CLIENT_OPCODE_SUBDOC_COUNTER:
        case CLIENT_OPCODE_SUBDOC_MULTI_LOOKUP:
        case CLIENT_OPCODE_SUBDOC_MULTI_MUTATION:
          break;

        default:
          ti = proto_tree_add_item(tree, hf_value, tvb, offset, 0,
                                   ENC_ASCII | ENC_NA);
          expert_add_info_format(pinfo, ti, &ei_value_missing,
                                 "%s with status %s (0x%x) must have Value",
                                 val_to_str_ext(opcode,
                                                &client_opcode_vals_ext,
                                                "Opcode 0x%x"),
                                 val_to_str_ext(status,
                                                &status_vals_ext,
                                                "Unknown"),
                                 status);
      }
    }
  }
}

static void dissect_server_request_value(tvbuff_t *tvb,
                                         packet_info *pinfo,
                                         proto_tree *tree,
                                         gint offset,
                                         gint size) {
  switch (get_opcode(tvb)) {
    case SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION:
      d_s_o_clustermap_change_notification_req(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_AUTHENTICATE:
      d_s_o_authenticate_req(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_ACTIVE_EXTERNAL_USERS:
      d_s_o_active_external_users_req(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_GET_AUTHORIZATION:
      d_s_o_get_authorization_req(tvb, pinfo, tree, offset, size);
      return;
    default:
      // Unknown packet type.. just dump the data
      if (size > 0) {
        proto_tree_add_item(tree, hf_value, tvb, offset, size,
                            ENC_ASCII | ENC_NA);
      }
      return;
  }
}

static void dissect_server_response_value(tvbuff_t *tvb,
                                         packet_info *pinfo,
                                         proto_tree *tree,
                                         gint offset,
                                         gint size) {
  col_append_fstr(pinfo->cinfo, COL_INFO, ", %s",
                  val_to_str_ext(get_status(tvb), &status_vals_ext,
                                 "Unknown status: 0x%x"));

  switch (get_opcode(tvb)) {
    case SERVER_OPCODE_CLUSTERMAP_CHANGE_NOTIFICATION:
      d_s_o_server_ignored_response(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_AUTHENTICATE:
      d_s_o_authenticate_res(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_ACTIVE_EXTERNAL_USERS:
      d_s_o_server_ignored_response(tvb, pinfo, tree, offset, size);
      return;
    case SERVER_OPCODE_GET_AUTHORIZATION:
      d_s_o_get_authorization_res(tvb, pinfo, tree, offset, size);
      return;
    default:
      // Unknown packet type.. just dump the data
      if (size > 0) {
        proto_tree_add_item(tree, hf_value, tvb, offset, size,
                            ENC_ASCII | ENC_NA);
      }
      return;
  }
}

/**
 * Dissect the value section in the frame
 */
static void dissect_frame_value(tvbuff_t *tvb,
                                packet_info *pinfo,
                                proto_tree *tree,
                                gint offset,
                                guint32 size,
                                guint8 magic,
                                guint8 opcode,
                                guint16 subdoc_path_len) {
  if (size > G_MAXINT32) {
    // The packet size isn't supported
  }

  switch (magic) {
    case MAGIC_CLIENT_REQUEST:
    case MAGIC_CLIENT_RESPONSE:
    case MAGIC_CLIENT_REQUEST_FLEX:
    case MAGIC_CLIENT_RESPONSE_FLEX:
      dissect_client_value(tvb, pinfo, tree, offset, size, magic, opcode, subdoc_path_len);
      return;
    case MAGIC_SERVER_REQUEST:
      dissect_server_request_value(tvb, pinfo, tree, offset, (gint)size);
      return;
    case MAGIC_SERVER_RESPONSE:
      dissect_server_response_value(tvb, pinfo, tree, offset, (gint)size);
      return;
    default:
      // Unknown magic... just dump the data
      if (size > 0) {
        proto_tree_add_item(tree, hf_value, tvb, offset, (gint)size, ENC_ASCII | ENC_NA);
      }
      return;
  }
}

/**
 * Each frame in the protocol consists of a 24 byte header, followed by
 * a variable number of sections (all of the sizes is located in the
 * first 24 byte header):
 *
 * |---------------------------------------|
 * |  Fixed 24 byte frame header           |
 * |---------------------------------------|
 * |  n bytes flex frame info              |
 * |---------------------------------------|
 * |  n bytes extras                       |
 * |---------------------------------------|
 * |  n bytes key                          |
 * |---------------------------------------|
 * |  n bytes value                        |
 * |---------------------------------------|
 *
 * Call each function responsible for printing the segment
 */
static int
dissect_couchbase(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
  col_set_str(pinfo->cinfo, COL_PROTOCOL, PSNAME);
  col_clear(pinfo->cinfo, COL_INFO);

  proto_item *couchbase_item = proto_tree_add_item(tree, proto_couchbase, tvb, 0, -1, ENC_NA);
  proto_tree *couchbase_tree = proto_item_add_subtree(couchbase_item, ett_couchbase);

  dissect_frame_header(tvb, pinfo, couchbase_tree, couchbase_item);
  guint8 magic = get_magic(tvb);
  gint offset = 24;

  guint8 flex_frame_extra_len = get_flex_framing_extras_length(tvb);
  guint8 opcode = get_opcode(tvb);
  guint8 extras_length = get_extras_length(tvb);
  guint16 key_length = get_key_length(tvb);
  guint32 body_length = get_body_length(tvb);
  guint32 value_len = body_length - key_length - extras_length - flex_frame_extra_len;

  dissect_frame_flex_info_section(tvb, pinfo, couchbase_tree, offset, flex_frame_extra_len, magic);
  offset += flex_frame_extra_len;

  guint16 subdoc_path_len = 0;
  // Dissect the extras section
  dissect_frame_extras(tvb, pinfo, couchbase_tree, offset, extras_length, magic, opcode, &subdoc_path_len);
  offset += extras_length;

  // dissect the key
  dissect_frame_key(tvb, pinfo, couchbase_tree, offset, key_length, magic, opcode);
  offset += key_length;

  dissect_frame_value(tvb, pinfo, couchbase_tree, offset, value_len, magic, opcode, subdoc_path_len);
  return tvb_reported_length(tvb);
}

static guint
get_couchbase_pdu_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset,
                         void *data _U_) {
  // See https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md#packet-structure
  // for a description of each packet.
  // The "length" field is located at offset 8 within the frame and does
  // not include the fixed header.
  return tvb_get_ntohl(tvb, offset + 8) + COUCHBASE_HEADER_LEN;
}

/* Dissect the couchbase packet */
static int
dissect_couchbase_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                      void *data) {
  if (try_val_to_str(tvb_get_guint8(tvb, 0), magic_vals) == NULL) {
    // Magic isn't one of the know magics used by the Couchbase dissector
    return 0;
  }

  tcp_dissect_pdus(tvb, pinfo, tree, couchbase_desegment_body,
                   COUCHBASE_HEADER_LEN,
                   get_couchbase_pdu_length, dissect_couchbase, data);
  return tvb_captured_length(tvb);
}


/* Registration functions; register couchbase protocol,
 * its configuration options and also register the tcp dissectors.
 */
void
proto_register_couchbase(void)
{
  static hf_register_info hf[] = {
    { &hf_magic, { "Magic", "couchbase.magic", FT_UINT8, BASE_HEX, VALS(magic_vals), 0x0, "Magic number", HFILL } },
    { &hf_opcode, { "Opcode", "couchbase.opcode", FT_UINT8, BASE_HEX|BASE_EXT_STRING, &client_opcode_vals_ext, 0x0, "Command code", HFILL } },
    { &hf_server_opcode, { "Server Opcode", "couchbase.server.opcode", FT_UINT8, BASE_HEX|BASE_EXT_STRING, &server_opcode_vals_ext, 0x0, "Command code", HFILL } },
    { &hf_extlength, { "Extras Length", "couchbase.extras.length", FT_UINT8, BASE_DEC, NULL, 0x0, "Length in bytes of the command extras", HFILL } },
    { &hf_keylength, { "Key Length", "couchbase.key.length", FT_UINT16, BASE_DEC, NULL, 0x0, "Length in bytes of the text key that follows the command extras", HFILL } },
    { &hf_value_length, { "Value Length", "couchbase.value.length", FT_UINT32, BASE_DEC, NULL, 0x0, "Length in bytes of the value that follows the key", HFILL } },
    { &hf_datatype, { "Data Type", "couchbase.datatype", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_datatype_json, { "JSON", "couchbase.datatype.json", FT_BOOLEAN, 8, TFS(&tfs_set_notset), DT_JSON, "JSON datatype", HFILL} },
    { &hf_datatype_snappy, { "Snappy", "couchbase.datatype.snappy", FT_BOOLEAN, 8, TFS(&tfs_set_notset), DT_SNAPPY, "Snappy Compressed", HFILL} },
    { &hf_datatype_xattr, { "XATTR", "couchbase.datatype.xattr", FT_BOOLEAN, 8, TFS(&tfs_set_notset), DT_XATTR, "Xattrs included", HFILL} },
    { &hf_vbucket, { "VBucket", "couchbase.vbucket", FT_UINT16, BASE_DEC_HEX, NULL, 0x0, "VBucket ID", HFILL } },
    { &hf_status, { "Status", "couchbase.status", FT_UINT16, BASE_HEX|BASE_EXT_STRING, &status_vals_ext, 0x0, "Status of the response", HFILL } },
    { &hf_total_bodylength, { "Total Body Length", "couchbase.total_bodylength", FT_UINT32, BASE_DEC, NULL, 0x0, "Length in bytes of extra + key + value", HFILL } },
    { &hf_opaque, { "Opaque", "couchbase.opaque", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_cas, { "CAS", "couchbase.cas", FT_UINT64, BASE_HEX, NULL, 0x0, "Data version check", HFILL } },
    { &hf_ttp, { "Time to Persist", "couchbase.ttp", FT_UINT32, BASE_DEC, NULL, 0x0, "Approximate time needed to persist the key (milliseconds)", HFILL } },
    { &hf_ttr, { "Time to Replicate", "couchbase.ttr", FT_UINT32, BASE_DEC, NULL, 0x0, "Approximate time needed to replicate the key (milliseconds)", HFILL } },

    { &hf_collection_key_id, { "Collection ID", "couchbase.key.collection_id", FT_UINT32, BASE_HEX, NULL, 0x0, "If this a collection stream, this is the collection-ID", HFILL } },
    { &hf_collection_key_logical, { "Collection Logical Key", "couchbase.key.logical_key", FT_STRING, BASE_NONE, NULL, 0x0, "If this a collection stream, this is the key in the collection", HFILL } },
    { &hf_collection_manifest_id, { "Collections Manifest ID", "couchbase.key.collection_manifest_id", FT_UINT64, BASE_HEX, NULL, 0x0, "The collections manifest id", HFILL } },

    { &hf_flex_keylength, { "Key Length", "couchbase.key.length", FT_UINT8, BASE_DEC, NULL, 0x0, "Length in bytes of the text key that follows the command extras", HFILL } },
    { &hf_flex_extras_length, { "Flexible Framing Extras Length", "couchbase.flex_extras", FT_UINT8, BASE_DEC, NULL, 0x0, "Length in bytes of the flexible framing extras that follows the response header", HFILL } },
    { &hf_flex_extras, {"Flexible Framing Extras", "couchbase.flex_frame_extras", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_flex_extras_n, {"Flexible Framing Extras", "couchbase.flex_frame_extras.string", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },

    { &hf_flex_frame_id_byte0, {"Flexible Frame Byte0", "couchbase.flex_frame.byte0", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    { &hf_flex_frame_id_req, {"Flexible Frame ID (request)", "couchbase.flex_frame.frame.id", FT_UINT8, BASE_DEC, VALS(flex_frame_request_ids), 0x0, NULL, HFILL } },
    { &hf_flex_frame_id_res, {"Flexible Frame ID (response)", "couchbase.flex_frame.frame.id", FT_UINT8, BASE_DEC, VALS(flex_frame_response_ids), 0x0, NULL, HFILL } },
    { &hf_flex_frame_id_req_esc, {"Flexible Frame ID esc (request)", "couchbase.flex_frame.frame.id", FT_UINT16, BASE_DEC, VALS(flex_frame_request_ids), 0x0, NULL, HFILL } },
    { &hf_flex_frame_id_res_esc, {"Flexible Frame ID esc (response)", "couchbase.flex_frame.frame.id", FT_UINT16, BASE_DEC, VALS(flex_frame_response_ids), 0x0, NULL, HFILL } },


    { &hf_flex_frame_len, {"Flexible Frame Len", "couchbase.flex_frame.frame.len", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_flex_frame_len_esc, {"Flexible Frame Len (esc)", "couchbase.flex_frame.frame.len", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    { &hf_flex_frame_tracing_duration, {"Server Recv->Send duration", "couchbase.flex_frame.frame.duration", FT_DOUBLE, BASE_NONE|BASE_UNIT_STRING, &units_microseconds, 0, NULL, HFILL } },
    { &hf_flex_frame_ru_count, {"Read unit count", "couchbase.flex_frame.frame.ru_count", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
    { &hf_flex_frame_wu_count, {"Write unit count", "couchbase.flex_frame.frame.wu_count", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
    { &hf_flex_frame_durability_req, {"Durability Requirement", "couchbase.flex_frame.frame.durability_req", FT_UINT8, BASE_DEC, VALS(flex_frame_durability_req), 0, NULL, HFILL } },
    { &hf_flex_frame_dcp_stream_id, {"DCP Stream Identifier", "couchbase.flex_frame.frame.dcp_stream_id", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
    { &hf_flex_frame_impersonated_user, {"Impersonated User", "couchbase.flex_frame.frame.impersonated_user", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },

    { &hf_extras, { "Extras", "couchbase.extras", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_flags, { "Flags", "couchbase.extras.flags", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_flags_backfill, { "Backfill Age", "couchbase.extras.flags.backfill", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0001, NULL, HFILL } },
    { &hf_extras_flags_dump, { "Dump", "couchbase.extras.flags.dump", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0002, NULL, HFILL } },
    { &hf_extras_flags_list_vbuckets, { "List VBuckets", "couchbase.extras.flags.list_vbuckets", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0004, NULL, HFILL } },
    { &hf_extras_flags_takeover_vbuckets, { "Takeover VBuckets", "couchbase.extras.flags.takeover_vbuckets", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0008, NULL, HFILL } },
    { &hf_extras_flags_support_ack, { "Support ACK", "couchbase.extras.flags.support_ack", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0010, NULL, HFILL } },
    { &hf_extras_flags_request_keys_only, { "Request Keys Only", "couchbase.extras.flags.request_keys_only", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0020, NULL, HFILL } },
    { &hf_extras_flags_checkpoint, { "Checkpoint", "couchbase.extras.flags.checkpoint", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0040, NULL, HFILL } },

    /* Sub-document */
    { &hf_subdoc_flags, { "Subdoc flags", "couchbase.extras.subdoc.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_subdoc_flags_mkdirp, { "MKDIR_P", "couchbase.extras.subdoc.flags.mkdir_p", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x01, "Create non-existent intermediate paths", HFILL} },
    { &hf_subdoc_flags_xattrpath, { "XATTR_PATH", "couchbase.extras.subdoc.flags.xattr_path", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x04, "If set path refers to extended attribute (XATTR)", HFILL} },
    { &hf_subdoc_flags_expandmacros, { "EXPAND_MACROS", "couchbase.extras.subdoc.flags.expand_macros", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x10, "Expand macro values inside XATTRs", HFILL} },
    { &hf_subdoc_flags_reserved, {"Reserved fields", "couchbase.extras.subdoc.flags.reserved", FT_UINT8, BASE_HEX, NULL, 0xEA, "A reserved field", HFILL} },
    { &hf_subdoc_doc_flags, { "Subdoc Doc flags", "couchbase.extras.subdoc.doc_flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_subdoc_doc_flags_mkdoc, { "MKDOC", "couchbase.extras.subdoc.doc_flags.mkdoc", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x01, "Create document if it does not exist, implies mkdir_p", HFILL} },
    { &hf_subdoc_doc_flags_add, { "ADD", "couchbase.extras.subdoc.doc_flags.add", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x02, "Fail if doc already exists", HFILL} },
    { &hf_subdoc_doc_flags_accessdeleted, { "ACCESS_DELETED", "couchbase.extras.subdoc.doc_flags.access_deleted", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x04, "Allow access to XATTRs for deleted documents", HFILL} },
    { &hf_subdoc_doc_flags_createasdeleted, { "CREATE_AS_DELETED", "couchbase.extras.subdoc.doc_flags.create_as_deleted", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x08, "If the document does not exist then create it in the Deleted state, instead of the normal Alive state", HFILL} },
    { &hf_subdoc_doc_flags_revivedocument, { "REVIVE_DOCUMENT", "couchbase.extras.subdoc.doc_flags.revive_document", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x10, "If the document exists in the Deleted state, revive it to the normal Alive state", HFILL} },
    { &hf_subdoc_doc_flags_replicaread, { "REPLICA_READ", "couchbase.extras.subdoc.doc_flags.replica_read", FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x20, "Operate on a replica vbucket instead of an active one", HFILL} },
    { &hf_subdoc_doc_flags_reserved, {"Reserved fields", "couchbase.extras.subdoc.doc_flags.reserved", FT_UINT8, BASE_HEX, NULL, 0xC0, "A reserved field", HFILL} },
    { &hf_extras_pathlen, { "Path Length", "couchbase.extras.pathlen", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    /* DCP flags */
    { &hf_extras_flags_dcp_connection_type, {"Connection Type", "couchbase.extras.flags.dcp_connection_type", FT_UINT32, BASE_HEX, VALS(dcp_connection_type_vals), 0x00000003, NULL, HFILL } },
    { &hf_extras_flags_dcp_add_stream_takeover, {"Take Over", "couchbase.extras.flags.dcp_add_stream_takeover", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0001, NULL, HFILL } },
    { &hf_extras_flags_dcp_add_stream_diskonly, {"Disk Only", "couchbase.extras.flags.dcp_add_stream_diskonly", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0002, NULL, HFILL } },
    { &hf_extras_flags_dcp_add_stream_latest, {"Latest", "couchbase.extras.flags.dcp_add_stream_latest", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0004, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_memory, {"Memory", "couchbase.extras.flags.dcp_snapshot_marker_memory", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0001, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_disk, {"Disk", "couchbase.extras.flags.dcp_snapshot_marker_disk", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0002, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_chk, {"Chk", "couchbase.extras.flags.dcp_snapshot_marker_chk", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0004, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_ack, {"Ack", "couchbase.extras.flags.dcp_snapshot_marker_ack", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0008, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_history, {"History", "couchbase.extras.flags.dcp_snapshot_marker_history", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0010, NULL, HFILL } },
    { &hf_extras_flags_dcp_snapshot_marker_may_contain_dups, {"May Contain Duplicates", "couchbase.extras.flags.dcp_snapshot_marker_may_contain_duplicates", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0020, NULL, HFILL } },
    { &hf_extras_flags_dcp_include_xattrs, {"Include XATTRs", "couchbase.extras.flags.dcp_include_xattrs", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0004, "Indicates the server should include documents XATTRs", HFILL} },
    { &hf_extras_flags_dcp_no_value, {"No Value", "couchbase.extras.flags.dcp_no_value", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0008, "Indicates the server should strip off values", HFILL} },
    { &hf_extras_flags_dcp_collections, {"Enable Collections", "couchbase.extras.flags.dcp_collections", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0010, "Indicates the server should stream collections", HFILL} },
    { &hf_extras_flags_dcp_include_delete_times, {"Include Delete Times", "couchbase.extras.flags.dcp_include_delete_times", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0020, "Indicates the server should include delete timestamps", HFILL} },
    { &hf_extras_flags_dcp_oso_snapshot_begin, {"OSO Begin", "couchbase.extras.flags.dcp_oso_snapshot_begin", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0001, "The start of an OSO snapshot", HFILL} },
    { &hf_extras_flags_dcp_oso_snapshot_end, {"OSO End", "couchbase.extras.flags.dcp_oso_snapshot_end", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0002, "The end of an OSO snapshot", HFILL} },

    { &hf_extras_seqno, { "Sequence number", "couchbase.extras.seqno", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_mutation_seqno, { "Mutation Sequence Number", "couchbase.extras.mutation_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_opaque, { "Opaque (vBucket identifier)", "couchbase.extras.opaque", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_reserved, { "Reserved", "couchbase.extras.reserved", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_start_seqno, { "Start Sequence Number", "couchbase.extras.start_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_end_seqno, { "End Sequence Number", "couchbase.extras.end_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_high_completed_seqno, { "High Completed Sequence Number", "couchbase.extras.high_completed_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_max_visible_seqno, { "Max Visible Seqno", "couchbase.extras.max_visible_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_timestamp, { "PiTR timestamp", "couchbase.extras.timestamp", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_marker_version, { "Snapshot Marker Version", "couchbase.extras.marker_version", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_vbucket_uuid, { "VBucket UUID", "couchbase.extras.vbucket_uuid", FT_UINT64, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_snap_start_seqno, { "Snapshot Start Sequence Number", "couchbase.extras.snap_start_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_snap_end_seqno, { "Snapshot End Sequence Number", "couchbase.extras.snap_end_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_by_seqno, { "by_seqno", "couchbase.extras.by_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_prepared_seqno, { "by_seqno (prepared)", "couchbase.extras.by_seqno_prepared", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_commit_seqno, { "by_seqno (commit)", "couchbase.extras.by_seqno_commit", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_abort_seqno, { "by_seqno (abort)", "couchbase.extras.by_seqno_abort", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_rev_seqno, { "rev_seqno", "couchbase.extras.rev_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_lock_time, { "lock_time", "couchbase.extras.lock_time", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_nmeta, { "nmeta", "couchbase.extras.nmeta", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_nru, { "nru", "couchbase.extras.nru", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_deleted, { "deleted", "couchbase.extras.deleted", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_bytes_to_ack, { "bytes_to_ack", "couchbase.extras.bytes_to_ack", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_delete_time, { "delete_time", "couchbase.extras.delete_time", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_delete_unused, { "unused", "couchbase.extras.delete_unused", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_system_event_id, { "system_event_id", "couchbase.extras.system_event_id", FT_UINT32, BASE_DEC, VALS(dcp_system_event_id_vals), 0x0, NULL, HFILL } },
    { &hf_extras_system_event_version, { "system_event_version", "couchbase.extras.system_event_version", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_dcp_oso_snapshot_flags, { "OSO snapshot flags", "couchbase.extras.dcp_oso_snapshot_flags", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    { &hf_failover_log, { "Failover Log", "couchbase.dcp.failover_log", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_failover_log_size, { "Size", "couchbase.dcp.failover_log.size", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_failover_log_vbucket_uuid, { "VBucket UUID", "couchbase.dcp.failover_log.vbucket_uuid", FT_UINT64, BASE_HEX, NULL, 0x0, NULL, HFILL } },
    { &hf_failover_log_vbucket_seqno, { "Sequence Number", "couchbase.dcp.failover_log.seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    { &hf_vbucket_states, { "VBucket States", "couchbase.vbucket_states", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_vbucket_states_state, { "State", "couchbase.vbucket_states.state", FT_UINT32, BASE_HEX, VALS(vbucket_states_vals), 0x0, NULL, HFILL } },
    { &hf_vbucket_states_size, { "Size", "couchbase.vbucket_states.size", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_vbucket_states_id, { "VBucket", "couchbase.vbucket_states.id", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_vbucket_states_seqno, { "Sequence Number", "couchbase.vbucket_states.seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },

    { &hf_extras_expiration, { "Expiration", "couchbase.extras.expiration", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_delta, { "Amount to Add", "couchbase.extras.delta", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_initial, { "Initial Value", "couchbase.extras.initial", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_extras_unknown, { "Unknown", "couchbase.extras.unknown", FT_BYTES, BASE_NONE, NULL, 0x0, "Unknown Extras", HFILL } },
    { &hf_key, { "Key", "couchbase.key", FT_STRING, BASE_NONE, NULL, 0x0, "If this is a collection stream, the key is formed of a leb128 prefix and then the key", HFILL } },
    { &hf_path, { "Path", "couchbase.path", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_value, { "Value", "couchbase.value", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_uint64_response, { "Response", "couchbase.extras.response", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_observe, { "Observe", "couchbase.observe", FT_STRING, BASE_NONE, NULL, 0x0, "The observe properties", HFILL } },
    { &hf_observe_key, { "Key", "couchbase.observe.key", FT_STRING, BASE_NONE, NULL, 0x0, "The observable key", HFILL } },
    { &hf_observe_keylength, { "Key Length", "couchbase.observe.keylength", FT_UINT16, BASE_DEC, NULL, 0x0, "The length of the observable key", HFILL } },
    { &hf_observe_vbucket, { "VBucket", "couchbase.observe.vbucket", FT_UINT16, BASE_HEX, NULL, 0x0, "VBucket of the observable key", HFILL } },
    { &hf_observe_status, { "Status", "couchbase.observe.status", FT_UINT8, BASE_HEX, NULL, 0x0, "Status of the observable key", HFILL } },
    { &hf_observe_cas, { "CAS", "couchbase.observe.cas", FT_UINT64, BASE_HEX, NULL,                                                              0x0, "CAS value of the observable key",                                 HFILL } },
    { &hf_observe_vbucket_uuid, { "VBucket UUID", "couchbase.observe.vbucket_uuid", FT_UINT64, BASE_HEX, NULL,                                   0x0, NULL,                                                              HFILL } },
    { &hf_observe_last_persisted_seqno, { "Last persisted sequence number", "couchbase.observe.last_persisted_seqno", FT_UINT64, BASE_DEC, NULL, 0x0, NULL,                                                              HFILL } },
    { &hf_observe_current_seqno, { "Current sequence number", "couchbase.observe.current_seqno", FT_UINT64, BASE_DEC, NULL,                      0x0, NULL,                                                              HFILL } },
    { &hf_observe_old_vbucket_uuid, { "Old VBucket UUID", "couchbase.observe.old_vbucket_uuid", FT_UINT64, BASE_HEX, NULL,                       0x0, NULL,                                                              HFILL } },
    { &hf_observe_last_received_seqno, { "Last received sequence number", "couchbase.observe.last_received_seqno", FT_UINT64, BASE_DEC, NULL,    0x0, NULL,                                                              HFILL } },
    { &hf_observe_failed_over, { "Failed over", "couchbase.observe.failed_over", FT_UINT8, BASE_DEC, NULL,                                       0x0, NULL,                                                              HFILL } },

    { &hf_get_errmap_version, {"Version", "couchbase.geterrmap.version", FT_UINT16, BASE_DEC, NULL,                                              0x0, NULL,                                                              HFILL} },

    { &hf_multipath_opcode, { "Opcode", "couchbase.multipath.opcode", FT_UINT8, BASE_HEX|BASE_EXT_STRING, &client_opcode_vals_ext,               0x0, "Command code",                                                    HFILL } },
    { &hf_multipath_index, { "Index", "couchbase.multipath.index", FT_UINT8, BASE_DEC, NULL,                                                     0x0, NULL,                                                              HFILL } },
    { &hf_multipath_pathlen, { "Path Length", "couchbase.multipath.path.length", FT_UINT16, BASE_DEC, NULL,                                      0x0, NULL,                                                              HFILL } },
    { &hf_multipath_path, { "Path", "couchbase.multipath.path", FT_STRING, BASE_NONE, NULL,                                                      0x0, NULL,                                                              HFILL } },
    { &hf_multipath_valuelen, { "Value Length", "couchbase.multipath.value.length", FT_UINT32, BASE_DEC, NULL,                                   0x0, NULL,                                                              HFILL } },
    { &hf_multipath_value, { "Value", "couchbase.multipath.value", FT_STRING, BASE_NONE, NULL,                                                   0x0, NULL,                                                              HFILL } },

    { &hf_meta_flags, {"Flags", "couchbase.extras.flags", FT_UINT32, BASE_HEX, NULL,                                                             0x0, NULL,                                                              HFILL} },
    { &hf_meta_expiration, {"Expiration", "couchbase.extras.expiration", FT_UINT32, BASE_HEX, NULL,                                              0x0, NULL,                                                              HFILL} },
    { &hf_meta_revseqno, {"RevSeqno", "couchbase.extras.revseqno", FT_UINT64, BASE_HEX, NULL,                                                    0x0, NULL,                                                              HFILL} },
    { &hf_meta_cas, {"CAS", "couchbase.extras.cas", FT_UINT64, BASE_HEX, NULL,                                                                   0x0, NULL,                                                              HFILL} },
    { &hf_meta_options, {"Options", "couchbase.extras.options", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_force_meta, {"FORCE_WITH_META_OP", "couchbase.extras.options.force_with_meta_op", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0001, NULL, HFILL} },
    { &hf_force_accept, {"FORCE_ACCEPT_WITH_META_OPS", "couchbase.extras.options.force_accept_with_meta_ops", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0002, NULL, HFILL} },
    { &hf_regenerate_cas, {"REGENERATE_CAS", "couchbase.extras.option.regenerate_cas", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0004, NULL, HFILL} },
    { &hf_skip_conflict, {"SKIP_CONFLICT_RESOLUTION", "couchbase.extras.options.skip_conflict_resolution", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0008, NULL, HFILL} },
    { &hf_is_expiration, {"IS_EXPIRATION", "couchbase.extras.options.is_expiration", FT_BOOLEAN, 16, TFS(&tfs_set_notset), 0x0010, NULL, HFILL} },
    { &hf_metalen, {"Meta Length", "couchbase.extras.meta_length", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_meta_reqextmeta, {"ReqExtMeta", "couchbase.extras.reqextmeta", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_meta_deleted, {"Deleted", "couchbase.extras.deleted", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_exptime, {"Expiry", "couchbase.extras.expiry", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_extras_meta_seqno, {"Seqno", "couchbase.extras.meta.seqno", FT_UINT64, BASE_HEX, NULL, 0x0, NULL, HFILL} },
    { &hf_confres, {"ConfRes", "couchbase.extras.confres", FT_UINT8, BASE_HEX, NULL, 0x0, "Conflict Resolution Mode", HFILL} },

    { &hf_bucket_type, {"Bucket Type", "couchbase.bucket.type", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_bucket_config, {"Bucket Config", "couchbase.bucket.config", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_config_key, {"Key", "couchbase.bucket.config.key", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_config_value, {"Value", "couchbase.bucket.config.value", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_hello_features, {"Hello Features", "couchbase.hello.features", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_hello_features_feature, {"Feature", "couchbase.hello.features.feature", FT_UINT16, BASE_HEX, VALS(feature_vals), 0x0, NULL, HFILL} },

    { &hf_xattrs, { "XATTRs", "couchbase.xattrs", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_xattr_length, {  "XATTR Length", "couchbase.xattrs.length", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL} },
    { &hf_xattr_pair_length, { "XATTR Pair Length", "couchbase.xattrs.pair.length", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL} },
    { &hf_xattr_key, { "Key", "couchbase.xattrs.pair.key", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_xattr_value, { "Value", "couchbase.xattrs.pair.value", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },


    { &hf_server_extras_cccp_epoch, { "Epoch", "couchbase.server.extras.cccp.epoch", FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_server_extras_cccp_revno, { "Revision", "couchbase.server.extras.cccp.revision", FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_server_clustermap_value, { "Clustermap", "couchbase.server.clustermap.value", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_server_authentication, { "Authentication", "couchbase.server.authentication", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_server_external_users, { "External users", "couchbase.server.external_users", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_server_get_authorization, { "Authorization", "couchbase.server.authorization", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },

    { &hf_range_scan_uuid, { "Range Scan UUID", "couchbase.range_scan.uuid", FT_GUID, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_range_scan_item_limit, { "Range Scan item limit", "couchbase.range_scan.item_limit", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_range_scan_time_limit, { "Range Scan time limit", "couchbase.range_scan.time_limit", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    { &hf_range_scan_byte_limit, { "Range Scan byte limit", "couchbase.range_scan.byte_limit", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }
  };

  static ei_register_info ei[] = {
    { &ei_value_missing, { "couchbase.value_missing", PI_PROTOCOL, PI_WARN, "Value is mandatory for this command", EXPFILL }},
    { &ef_warn_shall_not_have_value, { "couchbase.warn.shall_not_have_value", PI_UNDECODED, PI_WARN, "Packet shall not have value", EXPFILL }},
    { &ef_warn_shall_not_have_extras, { "couchbase.warn.shall_not_have_extras", PI_UNDECODED, PI_WARN, "Packet shall not have extras", EXPFILL }},
    { &ef_warn_shall_not_have_key, { "couchbase.warn.shall_not_have_key", PI_UNDECODED, PI_WARN, "Packet shall not have key", EXPFILL }},
    { &ef_warn_must_have_extras, { "couchbase.warn.must_have_extras", PI_UNDECODED, PI_WARN, "Packet must have extras", EXPFILL }},
    { &ef_warn_must_have_key, { "couchbase.warn.must_have_key", PI_UNDECODED, PI_WARN, "%s %s must have Key", EXPFILL }},
    { &ef_warn_illegal_extras_length, { "couchbase.warn.illegal_extras_length", PI_UNDECODED, PI_WARN, "Illegal Extras length", EXPFILL }},
    { &ef_warn_illegal_value_length, { "couchbase.warn.illegal_value_length", PI_UNDECODED, PI_WARN, "Illegal Value length", EXPFILL }},
    { &ef_warn_unknown_magic_byte, { "couchbase.warn.unknown_magic_byte", PI_UNDECODED, PI_WARN, "Unknown magic byte", EXPFILL }},
    { &ef_warn_unknown_opcode, { "couchbase.warn.unknown_opcode", PI_UNDECODED, PI_WARN, "Unknown opcode", EXPFILL }},
    { &ef_warn_unknown_extras, { "couchbase.warn.unknown_extras", PI_UNDECODED, PI_WARN, "Unknown extras", EXPFILL }},
    { &ef_note_status_code, { "couchbase.note.status_code", PI_RESPONSE_CODE, PI_NOTE, "Status", EXPFILL }},
    { &ef_separator_not_found, { "couchbase.warn.separator_not_found", PI_UNDECODED, PI_WARN, "Separator not found", EXPFILL }},
    { &ef_illegal_value, { "couchbase.warn.illegal_value", PI_UNDECODED, PI_WARN, "Illegal value for command", EXPFILL }},
    { &ef_compression_error, { "couchbase.error.compression", PI_UNDECODED, PI_WARN, "Compression error", EXPFILL }},
    { &ef_warn_unknown_flex_unsupported, { "couchbase.warn.unsupported_flexible_frame", PI_UNDECODED, PI_WARN, "Flexible Response ID warning", EXPFILL }},
    { &ef_warn_unknown_flex_id, { "couchbase.warn.unknown_flexible_frame_id", PI_UNDECODED, PI_WARN, "Flexible Response ID warning", EXPFILL }},
    { &ef_warn_unknown_flex_len, { "couchbase.warn.unknown_flexible_frame_len", PI_UNDECODED, PI_WARN, "Flexible Response ID warning", EXPFILL }}
  };

  static gint *ett[] = {
    &ett_couchbase,
    &ett_extras,
    &ett_flex_frame_extras,
    &ett_extras_flags,
    &ett_observe,
    &ett_failover_log,
    &ett_vbucket_states,
    &ett_multipath,
    &ett_config,
    &ett_config_key,
    &ett_hello_features,
    &ett_datatype,
    &ett_xattrs,
    &ett_xattr_pair,
    &ett_collection_key
  };

  module_t *couchbase_module;
  expert_module_t* expert_couchbase;

  proto_couchbase = proto_register_protocol(PNAME, PSNAME, PFNAME);

  proto_register_field_array(proto_couchbase, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));

  expert_couchbase = expert_register_protocol(proto_couchbase);
  expert_register_field_array(expert_couchbase, ei, array_length(ei));

  /* Register our configuration options */
  couchbase_module = prefs_register_protocol(proto_couchbase, &proto_reg_handoff_couchbase);

  couchbase_handle = register_dissector("couchbase", dissect_couchbase_pdu, proto_couchbase);

  prefs_register_bool_preference(couchbase_module, "desegment_pdus",
                                 "Reassemble PDUs spanning multiple TCP segments",
                                 "Whether the Couchbase dissector should reassemble PDUs"
                                 " spanning multiple TCP segments."
                                 " To use this option, you must also enable \"Allow subdissectors"
                                 " to reassemble TCP streams\" in the TCP protocol settings.",
                                 &couchbase_desegment_body);

  prefs_register_uint_preference(couchbase_module, "tls.port", "SSL/TLS Data Port",
                                 "The port used for communicating with the data service via SSL/TLS",
                                 10, &couchbase_ssl_port_pref);
  prefs_register_obsolete_preference(couchbase_module, "ssl_port");
}

/* Register the tcp couchbase dissector. */
void
proto_reg_handoff_couchbase(void)
{
  static gboolean initialized = FALSE;

  if (!initialized){
    json_handle = find_dissector_add_dependency("json", proto_couchbase);
    dissector_add_uint_range_with_preference("tcp.port", COUCHBASE_DEFAULT_PORT, couchbase_handle);
    initialized = TRUE;
  } else {
    ssl_dissector_delete(couchbase_ssl_port, couchbase_handle);
  }
  couchbase_ssl_port = couchbase_ssl_port_pref;
  ssl_dissector_add(couchbase_ssl_port, couchbase_handle);
}

/*
 * Editor modelines
 *
 * Local Variables:
 * c-basic-offset: 2
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * ex: set shiftwidth=2 tabstop=8 expandtab:
 * :indentSize=2:tabSize=8:noTabs=true:
 */
