/* packet-9p.c
 * Routines for 9P dissection
 * Copyright 2005, Nils O. Selaasdal
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * File permission bits decoding taken from packet-nfs.c
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"


#include <epan/packet.h>
#include <epan/expert.h>

#include "packet-tcp.h"

/*
 * Protocol specifications:
 *
 *   9P2000: http://ericvh.github.io/9p-rfc/rfc9p2000.html
 *   9P2000.L: https://github.com/chaos/diod/blob/master/protocol.md
 *   9P2000.u: https://ericvh.github.io/9p-rfc/rfc9p2000.u.html
 */

/**
 * enum _9p_msg_t - 9P message types
 * @_9P_TLERROR:   not used
 * @_9P_RLERROR:   response for any failed request for 9P2000.L
 * @_9P_TSTATFS:   file system status request
 * @_9P_RSTATFS:   file system status response
 * @_9P_TSYMLINK:  make symlink request
 * @_9P_RSYMLINK:  make symlink response
 * @_9P_TMKNOD:    create a special file object request
 * @_9P_RMKNOD:    create a special file object response
 * @_9P_TLCREATE:  prepare a handle for I/O on an new file for 9P2000.L
 * @_9P_RLCREATE:  response with file access information for 9P2000.L
 * @_9P_TRENAME:   rename request
 * @_9P_RRENAME:   rename response
 * @_9P_TMKDIR:    create a directory request
 * @_9P_RMKDIR:    create a directory response
 * @_9P_TVERSION:  version handshake request
 * @_9P_RVERSION:  version handshake response
 * @_9P_TAUTH:     request to establish authentication channel
 * @_9P_RAUTH:     response with authentication information
 * @_9P_TATTACH:   establish user access to file service
 * @_9P_RATTACH:   response with top level handle to file hierarchy
 * @_9P_TERROR:    not used
 * @_9P_RERROR:    response for any failed request
 * @_9P_TFLUSH:    request to abort a previous request
 * @_9P_RFLUSH:    response when previous request has been cancelled
 * @_9P_TWALK:     descend a directory hierarchy
 * @_9P_RWALK:     response with new handle for position within hierarchy
 * @_9P_TOPEN:     prepare a handle for I/O on an existing file
 * @_9P_ROPEN:     response with file access information
 * @_9P_TCREATE:   prepare a handle for I/O on a new file
 * @_9P_RCREATE:   response with file access information
 * @_9P_TREAD:     request to transfer data from a file or directory
 * @_9P_RREAD:     response with data requested
 * @_9P_TWRITE:    request to transfer data to a file
 * @_9P_RWRITE:    response with out much data was transferred to file
 * @_9P_TCLUNK:    forget about a handle to an entity within the file system
 * @_9P_RCLUNK:    response when server has forgotten about the handle
 * @_9P_TREMOVE:   request to remove an entity from the hierarchy
 * @_9P_RREMOVE:   response when server has removed the entity
 * @_9P_TSTAT:     request file entity attributes
 * @_9P_RSTAT:     response with file entity attributes
 * @_9P_TWSTAT:    request to update file entity attributes
 * @_9P_RWSTAT:    response when file entity attributes are updated
 *
 * There are 14 basic operations in 9P2000, paired as
 * requests and responses.  The one special case is ERROR
 * as there is no @_9P_TERROR request for clients to transmit to
 * the server, but the server may respond to any other request
 * with an @_9P_RERROR.
 *
 * See Also: http://plan9.bell-labs.com/sys/man/5/INDEX.html
 */

static dissector_handle_t ninep_handle;

enum _9p_msg_t {
	_9P_TLERROR = 6,
	_9P_RLERROR,
	_9P_TSTATFS = 8,
	_9P_RSTATFS,
	_9P_TLOPEN = 12,
	_9P_RLOPEN,
	_9P_TLCREATE = 14,
	_9P_RLCREATE,
	_9P_TSYMLINK = 16,
	_9P_RSYMLINK,
	_9P_TMKNOD = 18,
	_9P_RMKNOD,
	_9P_TRENAME = 20,
	_9P_RRENAME,
	_9P_TREADLINK = 22,
	_9P_RREADLINK,
	_9P_TGETATTR = 24,
	_9P_RGETATTR,
	_9P_TSETATTR = 26,
	_9P_RSETATTR,
	_9P_TXATTRWALK = 30,
	_9P_RXATTRWALK,
	_9P_TXATTRCREATE = 32,
	_9P_RXATTRCREATE,
	_9P_TREADDIR = 40,
	_9P_RREADDIR,
	_9P_TFSYNC = 50,
	_9P_RFSYNC,
	_9P_TLOCK = 52,
	_9P_RLOCK,
	_9P_TGETLOCK = 54,
	_9P_RGETLOCK,
	_9P_TLINK = 70,
	_9P_RLINK,
	_9P_TMKDIR = 72,
	_9P_RMKDIR,
	_9P_TRENAMEAT = 74,
	_9P_RRENAMEAT,
	_9P_TUNLINKAT = 76,
	_9P_RUNLINKAT,
	_9P_TVERSION = 100,
	_9P_RVERSION,
	_9P_TAUTH = 102,
	_9P_RAUTH,
	_9P_TATTACH = 104,
	_9P_RATTACH,
	_9P_TERROR = 106,
	_9P_RERROR,
	_9P_TFLUSH = 108,
	_9P_RFLUSH,
	_9P_TWALK = 110,
	_9P_RWALK,
	_9P_TOPEN = 112,
	_9P_ROPEN,
	_9P_TCREATE = 114,
	_9P_RCREATE,
	_9P_TREAD = 116,
	_9P_RREAD,
	_9P_TWRITE = 118,
	_9P_RWRITE,
	_9P_TCLUNK = 120,
	_9P_RCLUNK,
	_9P_TREMOVE = 122,
	_9P_RREMOVE,
	_9P_TSTAT = 124,
	_9P_RSTAT,
	_9P_TWSTAT = 126,
	_9P_RWSTAT
};

/* 9P Msg types to name mapping */
static const value_string ninep_msg_type[] =
{
	{_9P_TLERROR,	   "Tlerror"},
	{_9P_RLERROR,	   "Rlerror"},
	{_9P_TSTATFS,	   "Tstatfs"},
	{_9P_RSTATFS,	   "Rstatfs"},
	{_9P_TLOPEN,	   "Tlopen"},
	{_9P_RLOPEN,	   "Rlopen"},
	{_9P_TLCREATE,	   "Tlcreate"},
	{_9P_RLCREATE,	   "Rlcreate"},
	{_9P_TSYMLINK,	   "Tsymlink"},
	{_9P_RSYMLINK,	   "Rsymlink"},
	{_9P_TMKNOD,	   "Tmknod"},
	{_9P_RMKNOD,	   "Rmknod"},
	{_9P_TRENAME,	   "Trename"},
	{_9P_RRENAME,	   "Rrename"},
	{_9P_TREADLINK,	   "Treadlink"},
	{_9P_RREADLINK,	   "Rreadlink"},
	{_9P_TGETATTR,	   "Tgetattr"},
	{_9P_RGETATTR,	   "Rgetattr"},
	{_9P_TSETATTR,	   "Tsetattr"},
	{_9P_RSETATTR,	   "Rsetattr"},
	{_9P_TXATTRWALK,   "Txattrwalk"},
	{_9P_RXATTRWALK,   "Rxattrwalk"},
	{_9P_TXATTRCREATE, "Txattrcreate"},
	{_9P_RXATTRCREATE, "Rxattrcreate"},
	{_9P_TREADDIR,	   "Treaddir"},
	{_9P_RREADDIR,	   "Rreaddir"},
	{_9P_TFSYNC,	   "Tfsync"},
	{_9P_RFSYNC,	   "Rfsync"},
	{_9P_TLOCK,	   "Tlock"},
	{_9P_RLOCK,	   "Rlock"},
	{_9P_TGETLOCK,	   "Tgetlock"},
	{_9P_RGETLOCK,	   "Rgetlock"},
	{_9P_TLINK,	   "Tlink"},
	{_9P_RLINK,	   "Rlink"},
	{_9P_TMKDIR,	   "Tmkdir"},
	{_9P_RMKDIR,	   "Rmkdir"},
	{_9P_TRENAMEAT,	   "Trenameat"},
	{_9P_RRENAMEAT,	   "Rrenameat"},
	{_9P_TUNLINKAT,	   "Tunlinkat"},
	{_9P_RUNLINKAT,	   "Runlinkat"},
	{_9P_TVERSION,	   "Tversion"},
	{_9P_RVERSION,	   "Rversion"},
	{_9P_TAUTH,	   "Tauth"},
	{_9P_RAUTH,	   "Rauth"},
	{_9P_TATTACH,	   "Tattach"},
	{_9P_RATTACH,	   "Rattach"},
	{_9P_TERROR,	   "Terror"},
	{_9P_RERROR,	   "Rerror"},
	{_9P_TFLUSH,	   "Tflush"},
	{_9P_RFLUSH,	   "Rflush"},
	{_9P_TWALK,	   "Twalk"},
	{_9P_RWALK,	   "Rwalk"},
	{_9P_TOPEN,	   "Topen"},
	{_9P_ROPEN,	   "Ropen"},
	{_9P_TCREATE,	   "Tcreate"},
	{_9P_RCREATE,	   "Rcreate"},
	{_9P_TREAD,	   "Tread"},
	{_9P_RREAD,	   "Rread"},
	{_9P_TWRITE,	   "Twrite"},
	{_9P_RWRITE,	   "Rwrite"},
	{_9P_TCLUNK,	   "Tclunk"},
	{_9P_RCLUNK,	   "Rclunk"},
	{_9P_TREMOVE,	   "Tremove"},
	{_9P_RREMOVE,	   "Rremove"},
	{_9P_TSTAT,	   "Tstat"},
	{_9P_RSTAT,	   "Rstat"},
	{_9P_TWSTAT,	   "Twstat"},
	{_9P_RWSTAT,	   "Rwstat"},
	{0, NULL},
};
static value_string_ext ninep_msg_type_ext = VALUE_STRING_EXT_INIT(ninep_msg_type);

enum _9p_version {
	_9P = 1,
	_9P2000,
	_9P2000_L,
	_9P2000_u
};

static const value_string ninep_version[] =
{
	{_9P,		"9P"},
	{_9P2000,	"9P2000"},
	{_9P2000_L,	"9P2000.L"},
	{_9P2000_u,	"9P2000.u"},
	{0,		NULL},
};
static value_string_ext ninep_version_ext = VALUE_STRING_EXT_INIT(ninep_version);


/* File open modes */
#define	_9P_OREAD           0x0
#define	_9P_OWRITE          0x1
#define	_9P_ORDWR	    0x2
#define	_9P_OEXEC           0x3
#define	_9P_MODEMASK        0x3
#define _9P_OTRUNC         0x10
#define	_9P_ORCLOSE    	   0x40

/* Open/Create modes */
static const value_string ninep_mode_vals[] =
{
	{_9P_OREAD,	"Read Access"},
	{_9P_OWRITE,	"Write Access"},
	{_9P_ORDWR,	"Read/Write Access "},
	{_9P_OEXEC,	"Execute Access"},
	{0,		NULL},
};
static value_string_ext ninep_mode_vals_ext = VALUE_STRING_EXT_INIT(ninep_mode_vals);


/* stat mode flags */
#define DMDIR           0x80000000 /* Directory */
#define	DMAPPEND        0x40000000 /* Append only */
#define	DMEXCL	        0x20000000 /* Exclusive use */
#define	DMMOUNT	        0x10000000 /* Mounted channel */
#define DMAUTH          0x08000000 /* Authentication */
#define	DMTMP          	0x04000000 /* Temporary */


/**
 * enum _9p_qid_t - QID types
 * @_9P_QTDIR:     directory
 * @_9P_QTAPPEND:  append-only
 * @_9P_QTEXCL:    excluse use (only one open handle allowed)
 * @_9P_QTMOUNT:   mount points
 * @_9P_QTAUTH:    authentication file
 * @_9P_QTTMP:     non-backed-up files
 * @_9P_QTSYMLINK: symbolic links (9P2000.u)
 * @_9P_QTLINK:    hard-link (9P2000.u)
 * @_9P_QTFILE:    normal files
 *
 * QID types are a subset of permissions - they are primarily
 * used to differentiate semantics for a file system entity via
 * a jump-table.  Their value is also the most significant 16 bits
 * of the permission_t
 *
 * See Also: http://plan9.bell-labs.com/magic/man2html/2/stat
 */
enum _9p_qid_t {
	_9P_QTDIR     = 0x80,
	_9P_QTAPPEND  = 0x40,
	_9P_QTEXCL    = 0x20,
	_9P_QTMOUNT   = 0x10,
	_9P_QTAUTH    = 0x08,
	_9P_QTTMP     = 0x04,
	_9P_QTSYMLINK = 0x02,
	_9P_QTLINK    = 0x01,
	_9P_QTFILE    = 0x00
};

/* 9P Magic Numbers */
#define _9P_NOTAG	(guint16)(~0)
#define _9P_NOFID	(guint32)(~0)
#define _9P_NONUNAME	(guint32)(~0)
#define _9P_MAXWELEM	16


/**
 * @brief Length prefixed string type
 *
 * The protocol uses length prefixed strings for all
 * string data, so we replicate that for our internal
 * string members.
 */

struct _9p_str {
	guint16  len; /* Length of the string */
	char *str; /* The string */
};

/**
 * @brief file system entity information
 *
 * qids are /identifiers used by 9P servers to track file system
 * entities.  The type is used to differentiate semantics for operations
 * on the entity (ie. read means something different on a directory than
 * on a file).  The path provides a server unique index for an entity
 * (roughly analogous to an inode number), while the version is updated
 * every time a file is modified and can be used to maintain cache
 * coherency between clients and serves.
 * Servers will often differentiate purely synthetic entities by setting
 * their version to 0, signaling that they should never be cached and
 * should be accessed synchronously.
 *
 * See Also://plan9.bell-labs.com/magic/man2html/2/stat
 */

struct _9p_qid {
	guint8 type;     /* Type */
	guint32 version; /* Monotonically incrementing version number */
	guint64 path;    /* Per-server-unique ID for a file system element */
};


/* Bit values for getattr valid field.
 */
#define _9P_GETATTR_MODE         0x00000001U
#define _9P_GETATTR_NLINK        0x00000002U
#define _9P_GETATTR_UID          0x00000004U
#define _9P_GETATTR_GID          0x00000008U
#define _9P_GETATTR_RDEV         0x00000010U
#define _9P_GETATTR_ATIME        0x00000020U
#define _9P_GETATTR_MTIME        0x00000040U
#define _9P_GETATTR_CTIME        0x00000080U
#define _9P_GETATTR_INO          0x00000100U
#define _9P_GETATTR_SIZE         0x00000200U
#define _9P_GETATTR_BLOCKS       0x00000400U

#define _9P_GETATTR_BTIME        0x00000800U
#define _9P_GETATTR_GEN          0x00001000U
#define _9P_GETATTR_DATA_VERSION 0x00002000U

#if 0
#define _9P_GETATTR_BASIC        0x000007ffU  /* Mask for fields up to BLOCKS */
#endif
#define _9P_GETATTR_ALL          0x00003fffU  /* Mask for All fields above */


/* Bit values for setattr valid field from <linux/fs.h>.
 */
#define _9P_SETATTR_MODE         0x00000001U
#define _9P_SETATTR_UID          0x00000002U
#define _9P_SETATTR_GID          0x00000004U
#define _9P_SETATTR_SIZE         0x00000008U
#define _9P_SETATTR_ATIME        0x00000010U
#define _9P_SETATTR_MTIME        0x00000020U
#define _9P_SETATTR_CTIME        0x00000040U
#define _9P_SETATTR_ATIME_SET    0x00000080U
#define _9P_SETATTR_MTIME_SET    0x00000100U

#define _9P_SETATTR_ALL	0x000001FFU

/* 9p2000.L open flags */
#define _9P_DOTL_RDONLY		00000000
#define _9P_DOTL_WRONLY		00000001
#define _9P_DOTL_RDWR		00000002
#define _9P_DOTL_NOACCESS	00000003
#define _9P_DOTL_CREATE		00000100
#define _9P_DOTL_EXCL		00000200
#define _9P_DOTL_NOCTTY		00000400
#define _9P_DOTL_TRUNC		00001000
#define _9P_DOTL_APPEND		00002000
#define _9P_DOTL_NONBLOCK	00004000
#define _9P_DOTL_DSYNC		00010000
#define _9P_DOTL_FASYNC		00020000
#define _9P_DOTL_DIRECT		00040000
#define _9P_DOTL_LARGEFILE	00100000
#define _9P_DOTL_DIRECTORY	00200000
#define _9P_DOTL_NOFOLLOW	00400000
#define _9P_DOTL_NOATIME	01000000
#define _9P_DOTL_CLOEXEC	02000000
#define _9P_DOTL_SYNC		04000000


/* Bit values for lock type.
 */
#define _9P_LOCK_TYPE_RDLCK 0
#define _9P_LOCK_TYPE_WRLCK 1
#define _9P_LOCK_TYPE_UNLCK 2

/* 9P lock type to string table */
static const value_string ninep_lock_type[] =
{
	{_9P_LOCK_TYPE_RDLCK,	"Read lock"},
	{_9P_LOCK_TYPE_WRLCK,	"Write lock"},
	{_9P_LOCK_TYPE_UNLCK,	"Unlock"},
	{ 0,			NULL},
};
static value_string_ext ninep_lock_type_ext = VALUE_STRING_EXT_INIT(ninep_lock_type);

/* Bit values for lock status.
 */
#define _9P_LOCK_SUCCESS 0
#define _9P_LOCK_BLOCKED 1
#define _9P_LOCK_ERROR   2
#define _9P_LOCK_GRACE   3

/* 9P lock status to string table */
static const value_string ninep_lock_status[] =
{
	{_9P_LOCK_SUCCESS,	"Success"},
	{_9P_LOCK_BLOCKED,	"Blocked"},
	{_9P_LOCK_ERROR,	"Error"},
	{_9P_LOCK_GRACE,	"Grace"},
	{ 0,			NULL},
};
static value_string_ext ninep_lock_status_ext = VALUE_STRING_EXT_INIT(ninep_lock_status);

/* Bit values for lock flags.
 */
#define _9P_LOCK_FLAGS_NONE    0
#define _9P_LOCK_FLAGS_BLOCK   1
#define _9P_LOCK_FLAGS_RECLAIM 2

/* 9P lock flag to string table */
static const value_string ninep_lock_flag[] =
{
	{_9P_LOCK_FLAGS_NONE,   "No flag"},
	{_9P_LOCK_FLAGS_BLOCK,	"Block"},
	{_9P_LOCK_FLAGS_RECLAIM,"Reclaim"},
	{ 0,			NULL},
};
static value_string_ext ninep_lock_flag_ext = VALUE_STRING_EXT_INIT(ninep_lock_flag);

/*
 * Linux error code values to descriptions table.
 * Note that the error code values on Linux are platform-dependent;
 * Linux, on some platforms, tried to match the values of existing UN*Xes
 * on the platform in question.
 *
 * The platforms in question appear to be:
 *
 *   32-bit PowerPC (AIX?)
 *   32-bit and 64-bit SPARC (SunOS - pre-5, or 5?)
 *   PA-RISC (HP-UX)
 *   Alpha (Tru64 UNIX)
 *   32-bit MIPS (IRIX?)
 *   64-bit MIPS (IRIX?)
 *
 * For now, we don't worry about this, and use the errno values used
 * on most Linux platforms.
 */
static const value_string linux_errno[] =
{
	{1, "Operation not permitted"},			/* EPERM */
	{2, "No such file or directory"},		/* ENOENT */
	{3, "No such process"},				/* ESRCH */
	{4, "Interrupted system call"},			/* EINTR */
	{5, "I/O error"},				/* EIO */
	{6, "No such device or address"},		/* ENXIO */
	{7, "Argument list too long"},			/* E2BIG */
	{8, "Exec format error"},			/* ENOEXEC */
	{9, "Bad file number"},				/* EBADF */
	{10, "No child processes"},			/* ECHILD */
	{11, "Try again"},				/* EAGAIN */
	{12, "Out of memory"},				/* ENOMEM */
	{13, "Permission denied"},			/* EACCES */
	{14, "Bad address"},				/* EFAULT */
	{15, "Block device required"},			/* ENOTBLK */
	{16, "Device or resource busy"},		/* EBUSY */
	{17, "File exists"},				/* EEXIST */
	{18, "Cross-device link"},			/* EXDEV */
	{19, "No such device"},				/* ENODEV */
	{20, "Not a directory"},			/* ENOTDIR */
	{21, "Is a directory"},				/* EISDIR */
	{22, "Invalid argument"},			/* EINVAL */
	{23, "File table overflow"},			/* ENFILE */
	{24, "Too many open files"},			/* EMFILE */
	{25, "Not a typewriter"},			/* ENOTTY */
	{26, "Text file busy"},				/* ETXTBSY */
	{27, "File too large"},				/* EFBIG */
	{28, "No space left on device"},		/* ENOSPC */
	{29, "Illegal seek"},				/* ESPIPE */
	{30, "Read-only file system"},			/* EROFS */
	{31, "Too many links"},				/* EMLINK */
	{32, "Broken pipe"},				/* EPIPE */
	{33, "Math argument out of domain of func"},	/* EDOM */
	{34, "Math result not representable"},		/* ERANGE */
	{35, "Resource deadlock would occur"},		/* EDEADLK */
	{36, "File name too long"},			/* ENAMETOOLONG */
	{37, "No record locks available"},		/* ENOLCK */
	{38, "Function not implemented"},		/* ENOSYS */
	{39, "Directory not empty"},			/* ENOTEMPTY */
	{40, "Too many symbolic links encountered"},	/* ELOOP */
	{42, "No message of desired type"},		/* ENOMSG */
	{43, "Identifier removed"},			/* EIDRM */
	{44, "Channel number out of range"},		/* ECHRNG */
	{45, "Level 2 not synchronized"},		/* EL2NSYNC */
	{46, "Level 3 halted"},				/* EL3HLT */
	{47, "Level 3 reset"},				/* EL3RST */
	{48, "Link number out of range"},		/* ELNRNG */
	{49, "Protocol driver not attached"},		/* EUNATCH */
	{50, "No CSI structure available"},		/* ENOCSI */
	{51, "Level 2 halted"},				/* EL2HLT */
	{52, "Invalid exchange"},			/* EBADE */
	{53, "Invalid request descriptor"},		/* EBADR */
	{54, "Exchange full"},				/* EXFULL */
	{55, "No anode"},				/* ENOANO */
	{56, "Invalid request code"},			/* EBADRQC */
	{57, "Invalid slot"},				/* EBADSLT */
	{58, "File locking deadlock error"},		/* EDEADLOCK */
	{59, "Bad font file format"},			/* EBFONT */
	{60, "Device not a stream"},			/* ENOSTR */
	{61, "No data available"},			/* ENODATA */
	{62, "Timer expired"},				/* ETIME */
	{63, "Out of streams resources"},		/* ENOSR */
	{64, "Machine is not on the network"},		/* ENONET */
	{65, "Package not installed"},			/* ENOPKG */
	{66, "Object is remote"},			/* EREMOTE */
	{67, "Link has been severed"},			/* ENOLINK */
	{68, "Advertise error"},			/* EADV */
	{69, "Srmount error"},				/* ESRMNT */
	{70, "Communication error on send"},		/* ECOMM */
	{71, "Protocol error"},				/* EPROTO */
	{72, "Multihop attempted"},			/* EMULTIHOP */
	{73, "RFS specific error"},			/* EDOTDOT */
	{74, "Not a data message"},			/* EBADMSG */
	{75, "Value too large for defined data type"},	/* EOVERFLOW */
	{76, "Name not unique on network"},		/* ENOTUNIQ */
	{77, "File descriptor in bad state"},		/* EBADFD */
	{78, "Remote address changed"},			/* EREMCHG */
	{79, "Can not access a needed shared library"},	/* ELIBACC */
	{80, "Accessing a corrupted shared library"},	/* ELIBBAD */
	{81, ".lib section in a.out corrupted"},	/* ELIBSCN */
	{82, "Attempting to link in too many shared libraries"},	/* ELIBMAX */
	{83, "Cannot exec a shared library directly"},	/* ELIBEXEC */
	{84, "Illegal byte sequence"},			/* EILSEQ */
	{85, "Interrupted system call should be restarted"},	/* ERESTART */
	{86, "Streams pipe error"},			/* ESTRPIPE */
	{87, "Too many users"},				/* EUSERS */
	{88, "Socket operation on non-socket"},		/* ENOTSOCK */
	{89, "Destination address required"},		/* EDESTADDRREQ */
	{90, "Message too long"},			/* EMSGSIZE */
	{91, "Protocol wrong type for socket"},		/* EPROTOTYPE */
	{92, "Protocol not available"},			/* ENOPROTOOPT */
	{93, "Protocol not supported"},			/* EPROTONOSUPPORT */
	{94, "Socket type not supported"},		/* ESOCKTNOSUPPORT */
	{95, "Operation not supported on transport endpoint"},	/* EOPNOTSUPP */
	{96, "Protocol family not supported"},		/* EPFNOSUPPORT */
	{97, "Address family not supported by protocol"},	/* EAFNOSUPPORT */
	{98, "Address already in use"},			/* EADDRINUSE */
	{99, "Cannot assign requested address"},	/* EADDRNOTAVAIL */
	{100, "Network is down"},			/* ENETDOWN */
	{101, "Network is unreachable"},		/* ENETUNREACH */
	{102, "Network dropped connection because of reset"},	/* ENETRESET */
	{103, "Software caused connection abort"},	/* ECONNABORTED */
	{104, "Connection reset by peer"},		/* ECONNRESET */
	{105, "No buffer space available"},		/* ENOBUFS */
	{106, "Transport endpoint is already connected"},	/* EISCONN */
	{107, "Transport endpoint is not connected"},	/* ENOTCONN */
	{108, "Cannot send after transport endpoint shutdown"},	/* ESHUTDOWN */
	{109, "Too many references: cannot splice"},	/* ETOOMANYREFS */
	{110, "Connection timed out"},			/* ETIMEDOUT */
	{111, "Connection refused"},			/* ECONNREFUSED */
	{112, "Host is down"},				/* EHOSTDOWN */
	{113, "No route to host"},			/* EHOSTUNREACH */
	{114, "Operation already in progress"},		/* EALREADY */
	{115, "Operation now in progress"},		/* EINPROGRESS */
	{116, "Stale NFS file handle"},			/* ESTALE */
	{117, "Structure needs cleaning"},		/* EUCLEAN */
	{118, "Not a XENIX named type file"},		/* ENOTNAM */
	{119, "No XENIX semaphores available"},		/* ENAVAIL */
	{120, "Is a named type file"},			/* EISNAM */
	{121, "Remote I/O error"},			/* EREMOTEIO */
	{122, "Quota exceeded"},			/* EDQUOT */
	{123, "No medium found"},			/* ENOMEDIUM */
	{124, "Wrong medium type"},			/* EMEDIUMTYPE */
	{125, "Operation Canceled"},			/* ECANCELED */
	{126, "Required key not available"},		/* ENOKEY */
	{127, "Key has expired"},			/* EKEYEXPIRED */
	{128, "Key has been revoked"},			/* EKEYREVOKED */
	{129, "Key was rejected by service"},		/* EKEYREJECTED */
	{130, "Owner died"},				/* EOWNERDEAD */
	{131, "State not recoverable"},			/* ENOTRECOVERABLE */
	{132, "Operation not possible due to RF-kill"},	/* ERFKILL */
	{133, "Memory page has hardware error"},	/* EHWPOISON */
	{0,   NULL}
};
static value_string_ext linux_errno_ext = VALUE_STRING_EXT_INIT(linux_errno);

static const char *const invalid_fid_str = "<invalid fid>";
static const char *const afid_str = "<afid>";

/* Structures for Protocol Operations */
struct _9p_rlerror {
	guint32 ecode;
};
struct _9p_tstatfs {
	guint32 fid;
};
struct _9p_rstatfs {
	guint32 type;
	guint32 bsize;
	guint64 blocks;
	guint64 bfree;
	guint64 bavail;
	guint64 files;
	guint64 ffree;
	guint64 fsid;
	guint32 namelen;
};
struct _9p_tlopen {
	guint32 fid;
	guint32 flags;
};
struct _9p_rlopen {
	struct _9p_qid qid;
	guint32 iounit;
};
struct _9p_tlcreate {
	guint32 fid;
	struct _9p_str name;
	guint32 flags;
	guint32 mode;
	guint32 gid;
};
struct _9p_rlcreate {
	struct _9p_qid qid;
	guint32 iounit;
};
struct _9p_tsymlink {
	guint32 fid;
	struct _9p_str name;
	struct _9p_str symtgt;
	guint32 gid;
};
struct _9p_rsymlink {
	struct _9p_qid qid;
};
struct _9p_tmknod {
	guint32 fid;
	struct _9p_str name;
	guint32 mode;
	guint32 major;
	guint32 minor;
	guint32 gid;
};
struct _9p_rmknod {
	struct _9p_qid qid;
};
struct _9p_trename {
	guint32 fid;
	guint32 dfid;
	struct _9p_str name;
};
#if 0
struct _9p_rrename {
};
#endif
struct _9p_treadlink {
	guint32 fid;
};
struct _9p_rreadlink {
	struct _9p_str target;
};
struct _9p_tgetattr {
	guint32 fid;
	guint64 request_mask;
};
struct _9p_rgetattr {
	guint64 valid;
	struct _9p_qid qid;
	guint32 mode;
	guint32 uid;
	guint32 gid;
	guint64 nlink;
	guint64 rdev;
	guint64 size;
	guint64 blksize;
	guint64 blocks;
	guint64 atime_sec;
	guint64 atime_nsec;
	guint64 mtime_sec;
	guint64 mtime_nsec;
	guint64 ctime_sec;
	guint64 ctime_nsec;
	guint64 btime_sec;
	guint64 btime_nsec;
	guint64 gen;
	guint64 data_version;
};
struct _9p_tsetattr {
	guint32 fid;
	guint32 valid;
	guint32 mode;
	guint32 uid;
	guint32 gid;
	guint64 size;
	guint64 atime_sec;
	guint64 atime_nsec;
	guint64 mtime_sec;
	guint64 mtime_nsec;
};
#if 0
struct _9p_rsetattr {
};
#endif
struct _9p_txattrwalk {
	guint32 fid;
	guint32 attrfid;
	struct _9p_str name;
};
struct _9p_rxattrwalk {
	guint64 size;
};
struct _9p_txattrcreate {
	guint32 fid;
	struct _9p_str name;
	guint64 size;
	guint32 flag;
};
#if 0
struct _9p_rxattrcreate {
};
#endif
struct _9p_treaddir {
	guint32 fid;
	guint64 offset;
	guint32 count;
};
struct _9p_rreaddir {
	guint32 count;
	guint8 *data;
};
struct _9p_tfsync {
	guint32 fid;
};
#if 0
struct _9p_rfsync {
};
#endif
struct _9p_tlock {
	guint32 fid;
	guint8 type;
	guint32 flags;
	guint64 start;
	guint64 length;
	guint32 proc_id;
	struct _9p_str client_id;
};
struct _9p_rlock {
	guint8 status;
};
struct _9p_tgetlock {
	guint32 fid;
	guint8 type;
	guint64 start;
	guint64 length;
	guint32 proc_id;
	struct _9p_str client_id;
};
struct _9p_rgetlock {
	guint8 type;
	guint64 start;
	guint64 length;
	guint32 proc_id;
	struct _9p_str client_id;
};
struct _9p_tlink {
	guint32 dfid;
	guint32 fid;
	struct _9p_str name;
};
#if 0
struct _9p_rlink {
};
#endif
struct _9p_tmkdir {
	guint32 fid;
	struct _9p_str name;
	guint32 mode;
	guint32 gid;
};
struct _9p_rmkdir {
	struct _9p_qid qid;
};
struct _9p_trenameat {
	guint32 olddirfid;
	struct _9p_str oldname;
	guint32 newdirfid;
	struct _9p_str newname;
};
#if 0
struct _9p_rrenameat {
};
#endif
struct _9p_tunlinkat {
	guint32 dirfid;
	struct _9p_str name;
	guint32 flags;
};
#if 0
struct _9p_runlinkat {
};
#endif
struct _9p_tawrite {
	guint32 fid;
	guint8 datacheck;
	guint64 offset;
	guint32 count;
	guint32 rsize;
	guint8 *data;
	guint32 check;
};
struct _9p_rawrite {
	guint32 count;
};
struct _9p_tversion {
	guint32  msize              ;
	struct _9p_str version ;
};
struct _9p_rversion {
	guint32 msize;
	struct _9p_str version;
};
struct _9p_tauth {
	guint32 afid;
	struct _9p_str uname;
	struct _9p_str aname;
	guint32 n_uname;		/* 9P2000.u extensions */
};
struct _9p_rauth {
	struct _9p_qid qid;
};
struct _9p_rerror {
	struct _9p_str error;
	guint32 errnum;		/* 9p2000.u extension */
};
struct _9p_tflush {
	guint16 oldtag;
};
#if 0
struct _9p_rflush {
};
#endif
struct _9p_tattach {
	guint32 fid;
	guint32 afid;
	struct _9p_str uname;
	struct _9p_str aname;
	guint32 n_uname;		/* 9P2000.u extensions */
};
struct _9p_rattach {
	struct _9p_qid qid;
};
struct _9p_twalk {
	guint32 fid;
	guint32 newfid;
	guint16 nwname;
	struct _9p_str wnames[_9P_MAXWELEM];
};
struct _9p_rwalk {
	guint16 nwqid;
	struct _9p_qid wqids[_9P_MAXWELEM];
};
struct _9p_topen {
	guint32 fid;
	guint8 mode;
};
struct _9p_ropen {
	struct _9p_qid qid;
	guint32 iounit;
};
struct _9p_tcreate {
	guint32 fid;
	struct _9p_str name;
	guint32 perm;
	guint8 mode;
	struct _9p_str extension;
};
struct _9p_rcreate {
	struct _9p_qid qid;
	guint32 iounit;
};
struct _9p_tread {
	guint32 fid;
	guint64 offset;
	guint32 count;
};
struct _9p_rread {
	guint32 count;
	guint8 *data;
};
struct _9p_twrite {
	guint32 fid;
	guint64 offset;
	guint32 count;
	guint8 *data;
};
struct _9p_rwrite {
	guint32 count;
};
struct _9p_tclunk {
	guint32 fid;
};
#if 0
struct _9p_rclunk {
};
#endif
struct _9p_tremove {
	guint32 fid;
};
#if 0
struct _9p_rremove {
};

union _9p_tmsg {
} ;
#endif
#define NINEPORT 564

/* Forward declarations */
void proto_register_9P(void);
void proto_reg_handoff_9P(void);

/* Initialize the protocol and registered fields */
static int proto_9P = -1;
static int hf_9P_msgsz = -1;
static int hf_9P_msgtype = -1;
static int hf_9P_tag = -1;
static int hf_9P_oldtag = -1;
static int hf_9P_parmsz = -1;
static int hf_9P_maxsize = -1;
static int hf_9P_fid = -1;
static int hf_9P_nqid = -1;
static int hf_9P_mode = -1;
static int hf_9P_mode_rwx = -1;
static int hf_9P_mode_t = -1;
static int hf_9P_mode_c = -1;
static int hf_9P_extension = -1;
static int hf_9P_iounit = -1;
static int hf_9P_count = -1;
static int hf_9P_offset = -1;
static int hf_9P_perm = -1;
static int hf_9P_qidtype = -1;
static int hf_9P_qidtype_dir = -1;
static int hf_9P_qidtype_append = -1;
static int hf_9P_qidtype_exclusive = -1;
static int hf_9P_qidtype_mount = -1;
static int hf_9P_qidtype_auth_file = -1;
static int hf_9P_qidtype_temp_file = -1;
static int hf_9P_qidvers = -1;
static int hf_9P_qidpath = -1;
static int hf_9P_dm_dir = -1;
static int hf_9P_dm_append = -1;
static int hf_9P_dm_exclusive = -1;
static int hf_9P_dm_mount = -1;
static int hf_9P_dm_auth_file = -1;
static int hf_9P_dm_temp_file = -1;
static int hf_9P_dm_read_owner = -1;
static int hf_9P_dm_write_owner = -1;
static int hf_9P_dm_exec_owner = -1;
static int hf_9P_dm_read_group = -1;
static int hf_9P_dm_write_group = -1;
static int hf_9P_dm_exec_group = -1;
static int hf_9P_dm_read_others = -1;
static int hf_9P_dm_write_others = -1;
static int hf_9P_dm_exec_others = -1;
static int hf_9P_stattype = -1;
static int hf_9P_statmode = -1;
static int hf_9P_atime = -1;
static int hf_9P_mtime = -1;
static int hf_9P_ctime = -1;
static int hf_9P_btime = -1;
static int hf_9P_length = -1;
static int hf_9P_dev = -1;
static int hf_9P_wname = -1;
static int hf_9P_version = -1;
static int hf_9P_afid = -1;
static int hf_9P_uname = -1;
static int hf_9P_aname = -1;
static int hf_9P_ename = -1;
static int hf_9P_enum = -1;
/* static int hf_9P_name = -1; */
static int hf_9P_filename = -1;
static int hf_9P_sdlen = -1;
static int hf_9P_user = -1;
static int hf_9P_group = -1;
static int hf_9P_uid = -1;
static int hf_9P_gid = -1;
static int hf_9P_muid = -1;
static int hf_9P_nwalk = -1;
static int hf_9P_newfid = -1;
static int hf_9P_dfid = -1;
static int hf_9P_getattr_flags = -1;
static int hf_9P_getattr_mode = -1;
static int hf_9P_getattr_nlink = -1;
static int hf_9P_getattr_uid = -1;
static int hf_9P_getattr_gid = -1;
static int hf_9P_getattr_rdev = -1;
static int hf_9P_getattr_atime = -1;
static int hf_9P_getattr_mtime = -1;
static int hf_9P_getattr_ctime = -1;
static int hf_9P_getattr_ino = -1;
static int hf_9P_getattr_size = -1;
static int hf_9P_getattr_blocks = -1;
static int hf_9P_getattr_btime = -1;
static int hf_9P_getattr_gen = -1;
static int hf_9P_getattr_dataversion = -1;
static int hf_9P_setattr_flags = -1;
static int hf_9P_setattr_mode = -1;
static int hf_9P_setattr_uid = -1;
static int hf_9P_setattr_gid = -1;
static int hf_9P_setattr_size = -1;
static int hf_9P_setattr_atime = -1;
static int hf_9P_setattr_mtime = -1;
static int hf_9P_setattr_ctime = -1;
static int hf_9P_setattr_atime_set = -1;
static int hf_9P_setattr_mtime_set = -1;
static int hf_9P_unlinkat_flags = -1;
static int hf_9P_nlink = -1;
static int hf_9P_rdev = -1;
static int hf_9P_size = -1;
static int hf_9P_blksize = -1;
static int hf_9P_blocks = -1;
static int hf_9P_gen = -1;
static int hf_9P_dataversion = -1;
static int hf_9P_fstype = -1;
static int hf_9P_bfree = -1;
static int hf_9P_bavail = -1;
static int hf_9P_files = -1;
static int hf_9P_ffree = -1;
static int hf_9P_fsid = -1;
static int hf_9P_namelen = -1;
static int hf_9P_mknod_major = -1;
static int hf_9P_mknod_minor = -1;
static int hf_9P_lflags = -1;
static int hf_9P_lflags_rdonly = -1;
static int hf_9P_lflags_wronly = -1;
static int hf_9P_lflags_rdwr = -1;
static int hf_9P_lflags_create = -1;
static int hf_9P_lflags_excl = -1;
static int hf_9P_lflags_noctty = -1;
static int hf_9P_lflags_trunc = -1;
static int hf_9P_lflags_append = -1;
static int hf_9P_lflags_nonblock = -1;
static int hf_9P_lflags_dsync = -1;
static int hf_9P_lflags_fasync = -1;
static int hf_9P_lflags_direct = -1;
static int hf_9P_lflags_largefile = -1;
static int hf_9P_lflags_directory = -1;
static int hf_9P_lflags_nofollow = -1;
static int hf_9P_lflags_noatime = -1;
static int hf_9P_lflags_cloexec = -1;
static int hf_9P_lflags_sync = -1;
static int hf_9P_xattr_flag = -1;
static int hf_9P_lock_type = -1;
static int hf_9P_lock_flag = -1;
static int hf_9P_lock_start = -1;
static int hf_9P_lock_length = -1;
static int hf_9P_lock_procid = -1;
static int hf_9P_lock_status = -1;
static int hf_9P_unknown_message = -1;

/* subtree pointers */
static gint ett_9P = -1;
static gint ett_9P_omode = -1;
static gint ett_9P_dm = -1;
static gint ett_9P_wname = -1;
static gint ett_9P_aname = -1;
static gint ett_9P_ename = -1;
static gint ett_9P_uname = -1;
static gint ett_9P_user = -1;
static gint ett_9P_group = -1;
static gint ett_9P_muid = -1;
static gint ett_9P_filename = -1;
static gint ett_9P_version = -1;
static gint ett_9P_qid = -1;
static gint ett_9P_qidtype = -1;
static gint ett_9P_getattr_flags = -1;
static gint ett_9P_setattr_flags = -1;
static gint ett_9P_lflags = -1;

static expert_field ei_9P_first_250 = EI_INIT;
static expert_field ei_9P_msgtype = EI_INIT;

static wmem_map_t *_9p_hashtable = NULL;

static void dissect_9P_dm(tvbuff_t *tvb,  proto_item *tree, int offset, int iscreate);
static void dissect_9P_qid(tvbuff_t *tvb,  proto_tree *tree, int offset);
static void dissect_9P_lflags(tvbuff_t *tvb, proto_tree *tree, int offset);
static void dissect_9P_getattrflags(tvbuff_t *tvb, proto_tree *tree, int offset);
static void dissect_9P_setattrflags(tvbuff_t *tvb, proto_tree *tree, int offset);

static int * const _9P_modes[] = {
	&hf_9P_mode_c,
	&hf_9P_mode_t,
	&hf_9P_mode_rwx,
	NULL
};

struct _9p_hashkey {
	guint32 conv_index;
	guint16 tag;
	guint32 fid;
};

struct _9p_hashval {
	gsize len;
	void *data;
};

struct _9p_taginfo {
	enum _9p_msg_t msgtype;
	guint32 fid;
	/* fid path used for create and lcreate */
	char *fid_path;
};

static gint _9p_hash_equal(gconstpointer k1, gconstpointer k2) {
	const struct _9p_hashkey *key1 = (const struct _9p_hashkey *)k1, *key2 = (const struct _9p_hashkey *)k2;

	return ((key1->conv_index == key2->conv_index) && (key1->tag == key2->tag) && (key1->fid == key2->fid));
}

static guint _9p_hash_hash(gconstpointer k)
{
	const struct _9p_hashkey *key = (const struct _9p_hashkey *)k;

	return (key->conv_index ^ key->tag ^ key->fid);
}

static struct _9p_hashval *_9p_hash_new_val(gsize len)
{
	struct _9p_hashval *val;
	val = wmem_new(wmem_file_scope(), struct _9p_hashval);

	val->data = wmem_alloc(wmem_file_scope(), len);
	val->len = len;

	return val;
}

static void _9p_hash_set(packet_info *pinfo, guint16 tag, guint32 fid, struct _9p_hashval *val)
{
	struct _9p_hashkey *key;
	struct _9p_hashval *oldval;
	conversation_t *conv;

	conv = find_or_create_conversation(pinfo);

	key = wmem_new(wmem_file_scope(), struct _9p_hashkey);

	key->conv_index = conv->conv_index;
	key->tag = tag;
	key->fid = fid;

	/* remove eventual old entry */
	oldval = (struct _9p_hashval *)wmem_map_lookup(_9p_hashtable, key);
	if (oldval) {
		wmem_map_remove(_9p_hashtable, key);
	}
	wmem_map_insert(_9p_hashtable, key, val);
}

static struct _9p_hashval *_9p_hash_get(packet_info *pinfo, guint16 tag, guint32 fid)
{
	struct _9p_hashkey key;
	conversation_t *conv;

	conv = find_or_create_conversation(pinfo);

	key.conv_index = conv->conv_index;
	key.tag = tag;
	key.fid = fid;

	return (struct _9p_hashval *)wmem_map_lookup(_9p_hashtable, &key);
}

static void _9p_hash_free(packet_info *pinfo, guint16 tag, guint32 fid)
{
	struct _9p_hashkey key;
	conversation_t *conv;

	conv = find_or_create_conversation(pinfo);

	key.conv_index = conv->conv_index;
	key.tag = tag;
	key.fid = fid;

	wmem_map_remove(_9p_hashtable, &key);
}

static void conv_set_version(packet_info *pinfo, enum _9p_version version)
{
	struct _9p_hashval *val;

	val = _9p_hash_new_val(sizeof(enum _9p_version));


	*(enum _9p_version*)val->data = version;

	_9p_hash_set(pinfo, _9P_NOTAG, _9P_NOFID, val);
}

static enum _9p_version conv_get_version(packet_info *pinfo)
{
	struct _9p_hashval *val;

	val = _9p_hash_get(pinfo, _9P_NOTAG, _9P_NOFID);

	return val ? *(enum _9p_version*)val->data : _9P;
}

static void conv_set_fid_nocopy(packet_info *pinfo, guint32 fid, const char *path)
{
	struct _9p_hashval *val;

	if (pinfo->fd->visited || fid == _9P_NOFID)
		return;

	/* get or create&insert fid tree */
	val = _9p_hash_get(pinfo, _9P_NOTAG, fid);
	if (!val) {
		val = _9p_hash_new_val(0);
		val->data = wmem_tree_new(wmem_file_scope());
		/* val->len is intentionally left to 0 so the tree won't be freed */
		_9p_hash_set(pinfo, _9P_NOTAG, fid, val);
	}

	/* fill it */
	wmem_tree_insert32((wmem_tree_t *)val->data, pinfo->num, (void *)path);
}

static void conv_set_fid(packet_info *pinfo, guint32 fid, const gchar *path, gsize len)
{
	char *str;

	if (pinfo->fd->visited || fid == _9P_NOFID || len == 0)
		return;

	str = (char*)wmem_alloc(wmem_file_scope(), len);
	(void) g_strlcpy(str, path, len);
	conv_set_fid_nocopy(pinfo, fid, str);
}

static const char *conv_get_fid(packet_info *pinfo, guint32 fid)
{
	struct _9p_hashval *val;

	if (fid == _9P_NOFID)
		return invalid_fid_str;

	val = _9p_hash_get(pinfo, _9P_NOTAG, fid);
	if (!val)
		return invalid_fid_str;

	/* -1 because the fid needs to have been set on a previous message.
	   Let's ignore the possibility of num == 0... */
	return (char*)wmem_tree_lookup32_le((wmem_tree_t*)val->data, pinfo->num-1);
}

static inline void conv_free_fid(packet_info *pinfo, guint32 fid)
{
	conv_set_fid_nocopy(pinfo, fid, invalid_fid_str);
}

static void conv_set_tag(packet_info *pinfo, guint16 tag, enum _9p_msg_t msgtype, guint32 fid, wmem_strbuf_t *fid_path)
{
	struct _9p_hashval *val;
	struct _9p_taginfo *taginfo;

	if (pinfo->fd->visited || tag == _9P_NOTAG)
		return;

	val = _9p_hash_new_val(sizeof(struct _9p_taginfo));
	taginfo = (struct _9p_taginfo*)val->data;

	taginfo->msgtype = msgtype;
	taginfo->fid = fid;
	if (fid_path) {
		taginfo->fid_path = (char*)wmem_alloc(wmem_file_scope(), wmem_strbuf_get_len(fid_path)+1);
		(void) g_strlcpy(taginfo->fid_path, wmem_strbuf_get_str(fid_path), wmem_strbuf_get_len(fid_path)+1);
	} else {
		taginfo->fid_path = NULL;
	}

	_9p_hash_set(pinfo, tag, _9P_NOFID, val);
}

static inline struct _9p_taginfo *conv_get_tag(packet_info *pinfo, guint16 tag)
{
	struct _9p_hashval *val;

	/* get tag only makes sense on first pass, as tree isn't built like fid */
	if (pinfo->fd->visited || tag == _9P_NOTAG)
		return NULL;

	/* check that length matches? */
	val = _9p_hash_get(pinfo, tag, _9P_NOFID);

	return val ? (struct _9p_taginfo*)val->data : NULL;
}

static inline void conv_free_tag(packet_info *pinfo, guint16 tag)
{
	if (pinfo->fd->visited || tag == _9P_NOTAG)
		return;

	_9p_hash_free(pinfo, tag, _9P_NOFID);
}

/* dissects a string[s] (2 bytes followed by UTF-8 string) */
static guint _9p_dissect_string(tvbuff_t *tvb, proto_tree *ninep_tree,
	guint offset, int hf_string, gint ett_string)
{
	guint16              _9p_len;
	proto_item          *ti;
	proto_tree          *sub_tree;

	_9p_len = tvb_get_letohs(tvb, offset);
	ti = proto_tree_add_item(ninep_tree, hf_string, tvb, offset + 2,
			_9p_len, ENC_UTF_8|ENC_NA);
	sub_tree = proto_item_add_subtree(ti, ett_string);
	proto_tree_add_item(sub_tree, hf_9P_parmsz, tvb, offset, 2, ENC_LITTLE_ENDIAN);

	return 2 + _9p_len;
}

/* Dissect 9P messages*/
static int dissect_9P_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	guint32             u32, i, fid, dfid, newfid;
	guint16             u16, tag, _9p_len;
	enum _9p_msg_t      ninemsg;
	guint               offset    = 0;
	const char         *mname, *fid_path;
	char               *tvb_s;
	wmem_strbuf_t      *tmppath   = NULL;
	gint                len, reportedlen;
	tvbuff_t           *next_tvb;
	proto_item         *ti, *msg_item;
	proto_tree         *ninep_tree;
	struct _9p_taginfo *taginfo;
	int                 _9p_version;

	_9p_version = conv_get_version(pinfo);
	col_set_str(pinfo->cinfo, COL_PROTOCOL, val_to_str_ext_const(_9p_version, &ninep_version_ext, "9P"));

	col_clear(pinfo->cinfo, COL_INFO);

	/*ninesz = tvb_get_letohl(tvb, offset);*/
	ninemsg = (enum _9p_msg_t)tvb_get_guint8(tvb, offset + 4);

	mname = val_to_str_ext_const(ninemsg, &ninep_msg_type_ext, "Unknown");

	if(strcmp(mname, "Unknown") == 0) {
		col_add_fstr(pinfo->cinfo, COL_INFO, "9P Data (Message type %u)", (guint)ninemsg);
		return 0;
	}

	tag = tvb_get_letohs(tvb, offset+5);
	col_append_fstr(pinfo->cinfo, COL_INFO, "%s Tag=%u", mname, (guint)tag);

	ti = proto_tree_add_item(tree, proto_9P, tvb, 0, -1, ENC_NA);
	ninep_tree = proto_item_add_subtree(ti, ett_9P);
	proto_tree_add_item(ninep_tree, hf_9P_msgsz, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	offset+= 4;

	msg_item = proto_tree_add_item(ninep_tree, hf_9P_msgtype, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	++offset;
	proto_tree_add_item(ninep_tree, hf_9P_tag, tvb, offset, 2, ENC_LITTLE_ENDIAN);
	offset += 2;

	switch(ninemsg) {
	case _9P_RVERSION:
	case _9P_TVERSION:
		proto_tree_add_item(ninep_tree, hf_9P_maxsize, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		if (!pinfo->fd->visited) {
			_9p_len = tvb_get_letohs(tvb, offset);
			tvb_s = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);

			if (!strcmp(tvb_s, "9P2000.L")) {
				u32 = _9P2000_L;
			} else if (!strcmp(tvb_s, "9P2000")) {
				u32 = _9P2000;
			} else if (!strcmp(tvb_s, "9P2000.u")) {
				u32 = _9P2000_u;
			} else {
				u32 = _9P;
			}

			conv_set_version(pinfo, (enum _9p_version)u32);
		}
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_version, ett_9P_version);

		/* don't set tag for tversion/free it for rversion,
		   we need that for the actual version number */
		break;

	case _9P_TAUTH:
		proto_tree_add_item(ninep_tree, hf_9P_afid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid = tvb_get_letohl(tvb, offset);
		conv_set_fid_nocopy(pinfo, fid, afid_str);
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_uname, ett_9P_uname);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_aname, ett_9P_aname);

		conv_set_tag(pinfo, tag, ninemsg, fid, NULL);
		break;

	case _9P_RERROR:
		if (_9p_version == _9P2000_L) {
			u32 = tvb_get_letohl(tvb, offset);
			ti = proto_tree_add_item(ninep_tree, hf_9P_enum, tvb, offset, 4, ENC_LITTLE_ENDIAN);
			proto_item_append_text(ti, " (%s)", val_to_str_ext_const(u32, &linux_errno_ext, "Unknown"));
			offset += 4;
		} else {
			offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_ename, ett_9P_ename);
		}

		/* conv_get_tag checks we're in first pass */
		taginfo = conv_get_tag(pinfo, tag);
		if (taginfo && (taginfo->msgtype == _9P_TWALK || taginfo->msgtype == _9P_TATTACH))
			conv_free_fid(pinfo, taginfo->fid);

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TFLUSH:
		u16 = tvb_get_letohs(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_oldtag, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		conv_free_tag(pinfo, u16);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TATTACH:
		fid = tvb_get_letohl(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_afid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_uname, ett_9P_uname);

		if(!pinfo->fd->visited) {
			_9p_len = tvb_get_letohs(tvb, offset);
			tvb_s = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);
			conv_set_fid(pinfo, fid, tvb_s, strlen(tvb_s)+1);
		}
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_aname, ett_9P_aname);

		if (_9p_version == _9P2000_u || _9p_version == _9P2000_L) {
			proto_tree_add_item(ninep_tree, hf_9P_uid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
			offset += 4;
		}

		conv_set_tag(pinfo, tag, ninemsg, fid, NULL);
		break;

	case _9P_TWALK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid_path = conv_get_fid(pinfo, fid);
		proto_item_append_text(ti, " (%s)", fid_path);
		if (!pinfo->fd->visited) {
			tmppath = wmem_strbuf_create(pinfo->pool);
			wmem_strbuf_append(tmppath, fid_path);
		}
		offset += 4;

		fid = tvb_get_letohl(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_newfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		u16 = tvb_get_letohs(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_nwalk, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		for(i = 0 ; i < u16; i++) {
			if (!pinfo->fd->visited) {
				_9p_len = tvb_get_letohs(tvb, offset);
				tvb_s = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);
				wmem_strbuf_append_c(tmppath, '/');
				wmem_strbuf_append(tmppath, tvb_s);
			}

			if (i < 250) {
				offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);
			}
		}

		/* I can't imagine anyone having a directory depth more than 25,
		   Limit to 10 times that to be sure, 2^16 is too much */
		if(u16 > 250) {
			expert_add_info(pinfo, ti, &ei_9P_first_250);
		}

		if (!pinfo->fd->visited) {
			conv_set_fid(pinfo, fid, wmem_strbuf_get_str(tmppath), wmem_strbuf_get_len(tmppath)+1);
		}

		conv_set_tag(pinfo, tag, ninemsg, fid, NULL);
		break;

	case _9P_RWALK:
		u16 = tvb_get_letohs(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_nqid, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;
		/* I can't imagine anyone having a directory depth more than 25,
		   Limit to 10 times that to be sure, 2^16 is too much */
		if(u16 > 250) {
			u16 = 250;
		}

		for(i = 0; i < u16; i++) {
			dissect_9P_qid(tvb, ninep_tree, offset);
			offset += 13;
		}

		if (i >= 250) {
			expert_add_info(pinfo, ti, &ei_9P_first_250);
		}

		conv_free_tag(pinfo, tag);
		break;
	case _9P_TLOPEN:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_lflags(tvb, ti, offset);
		offset += 4;
		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TOPEN:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		proto_tree_add_bitmask(ninep_tree, tvb, offset, hf_9P_mode, ett_9P_omode, _9P_modes, ENC_LITTLE_ENDIAN);
		offset += 1;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TCREATE:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid_path = conv_get_fid(pinfo, fid);
		proto_item_append_text(ti, " (%s)", fid_path);
		offset += 4;

		if (!pinfo->fd->visited) {
			_9p_len = tvb_get_letohs(tvb, offset);
			tmppath = wmem_strbuf_create(pinfo->pool);
			wmem_strbuf_append(tmppath, fid_path);
			wmem_strbuf_append_c(tmppath, '/');
			tvb_s = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);
			wmem_strbuf_append(tmppath, tvb_s);
		}
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_filename, ett_9P_filename);

		ti = proto_tree_add_item(ninep_tree, hf_9P_perm, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 1);
		offset += 4;

		proto_tree_add_bitmask(ninep_tree, tvb, offset, hf_9P_mode, ett_9P_omode, _9P_modes, ENC_LITTLE_ENDIAN);
		offset += 1;

		if (_9p_version == _9P2000_u) {
			_9p_len = tvb_get_letohs(tvb, offset);
			proto_tree_add_item(ninep_tree, hf_9P_extension, tvb, offset+2, 4, ENC_ASCII);
			offset += 2 + _9p_len;
		}

		conv_set_tag(pinfo, tag, ninemsg, fid, tmppath);
		break;

	case _9P_TLCREATE:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid_path = conv_get_fid(pinfo, fid);
		proto_item_append_text(ti, " (%s)", fid_path);
		offset += 4;

		if (!pinfo->fd->visited) {
			_9p_len = tvb_get_letohs(tvb, offset);
			tmppath = wmem_strbuf_create(pinfo->pool);
			wmem_strbuf_append(tmppath, fid_path);
			wmem_strbuf_append_c(tmppath, '/');
			tvb_s = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);
			wmem_strbuf_append(tmppath, tvb_s);
		}
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_filename, ett_9P_filename);

		ti = proto_tree_add_item(ninep_tree, hf_9P_lflags, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_lflags(tvb, ti, offset);
		offset += 4;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, fid, tmppath);
		break;

	case _9P_TREAD:
	case _9P_TREADDIR:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_offset, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_count, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RREAD:
	case _9P_RREADDIR:
		u32 = tvb_get_letohl(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_count, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		len = tvb_reported_length_remaining(tvb, offset);
		reportedlen = ((gint)u32&0xffff) > len ? len : (gint)u32&0xffff;
		next_tvb = tvb_new_subset_length_caplen(tvb, offset, len, reportedlen);
		call_data_dissector(next_tvb, pinfo, tree);
		offset += len;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TWRITE:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_offset, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		u32 = tvb_get_letohl(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_count, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;
		len = tvb_reported_length_remaining(tvb, offset);
		reportedlen = ((gint)u32&0xffff) > len ? len : (gint)u32&0xffff;
		next_tvb = tvb_new_subset_length_caplen(tvb, offset, len, reportedlen);
		call_data_dissector(next_tvb, pinfo, tree);
		offset += len;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RWRITE:
		proto_tree_add_item(ninep_tree, hf_9P_count, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_RSTAT:
		proto_tree_add_item(ninep_tree, hf_9P_parmsz, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_sdlen, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_stattype, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_dev, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_atime, tvb, offset, 4, ENC_TIME_SECS|ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_mtime, tvb, offset, 4, ENC_TIME_SECS|ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_length, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_filename, ett_9P_filename);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_user, ett_9P_user);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_group, ett_9P_group);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_muid, ett_9P_muid);

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TWSTAT:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_parmsz, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_sdlen, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_stattype, tvb, offset, 2, ENC_LITTLE_ENDIAN);
		offset += 2;

		proto_tree_add_item(ninep_tree, hf_9P_dev, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_atime, tvb, offset, 4, ENC_TIME_SECS|ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_mtime, tvb, offset, 4, ENC_TIME_SECS|ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_length, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_filename, ett_9P_filename);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_user, ett_9P_user);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_group, ett_9P_group);
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_muid, ett_9P_muid);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TGETATTR:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		ti = proto_tree_add_item(ninep_tree, hf_9P_getattr_flags, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		dissect_9P_getattrflags(tvb, ti, offset);
		offset += 8;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RGETATTR:
		ti = proto_tree_add_item(ninep_tree, hf_9P_getattr_flags, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		dissect_9P_getattrflags(tvb, ti, offset);
		offset += 8;

		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_uid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_nlink, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_rdev, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_size, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_blksize, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_blocks, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_atime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		proto_tree_add_item(ninep_tree, hf_9P_mtime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		proto_tree_add_item(ninep_tree, hf_9P_ctime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		proto_tree_add_item(ninep_tree, hf_9P_btime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		proto_tree_add_item(ninep_tree, hf_9P_gen, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_dataversion, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TSETATTR:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		ti = proto_tree_add_item(ninep_tree, hf_9P_setattr_flags, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_setattrflags(tvb, ti, offset);
		offset += 4;

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_uid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_size, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_atime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		proto_tree_add_item(ninep_tree, hf_9P_mtime, tvb, offset, 16, ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN);
		offset += 16;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RSTATFS:
		proto_tree_add_item(ninep_tree, hf_9P_fstype, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_blksize, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_blocks, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_bfree, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_bavail, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_files, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_ffree, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_fsid, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_namelen, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TSYMLINK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		/* name */
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);
		/* XXX: maybe use a new field for symtgt? */
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TMKNOD:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_mknod_major, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_mknod_minor, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TRENAME:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		dfid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_dfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid_path = conv_get_fid(pinfo, dfid);
		proto_item_append_text(ti, " (%s)", fid_path);
		offset += 4;

		if (!pinfo->fd->visited) {
			_9p_len = tvb_get_letohs(tvb, offset);
			tmppath = wmem_strbuf_create(pinfo->pool);
			wmem_strbuf_append(tmppath, conv_get_fid(pinfo, dfid));
			wmem_strbuf_append_c(tmppath, '/');

			tvb_s = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset+2, _9p_len, ENC_UTF_8|ENC_NA);
			wmem_strbuf_append(tmppath, tvb_s);

			conv_set_fid(pinfo, fid, wmem_strbuf_get_str(tmppath), wmem_strbuf_get_len(tmppath)+1);
		}
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RREADLINK:
		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TXATTRWALK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		fid_path = conv_get_fid(pinfo, fid);
		proto_item_append_text(ti, " (%s)", fid_path);
		offset += 4;

		newfid = tvb_get_letohl(tvb, offset);
		proto_tree_add_item(ninep_tree, hf_9P_newfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		conv_set_fid_nocopy(pinfo, newfid, fid_path);
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RXATTRWALK:
		proto_tree_add_item(ninep_tree, hf_9P_size, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TXATTRCREATE:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		proto_tree_add_item(ninep_tree, hf_9P_size, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_xattr_flag, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TLOCK:
	case _9P_TGETLOCK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_lock_type, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		offset += 1;

		proto_tree_add_item(ninep_tree, hf_9P_lock_flag, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_lock_start, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_lock_length, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_lock_procid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_RLOCK:
		proto_tree_add_item(ninep_tree, hf_9P_lock_status, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		offset += 1;

		conv_free_tag(pinfo, tag);
		break;

	case _9P_RGETLOCK:
		proto_tree_add_item(ninep_tree, hf_9P_lock_type, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		offset += 1;

		proto_tree_add_item(ninep_tree, hf_9P_lock_flag, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_lock_start, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_lock_length, tvb, offset, 8, ENC_LITTLE_ENDIAN);
		offset += 8;

		proto_tree_add_item(ninep_tree, hf_9P_lock_procid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_free_tag(pinfo, tag);
		break;

	case _9P_TLINK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_dfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TMKDIR:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		ti = proto_tree_add_item(ninep_tree, hf_9P_statmode, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		dissect_9P_dm(tvb, ti, offset, 0);
		offset += 4;

		proto_tree_add_item(ninep_tree, hf_9P_gid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TRENAMEAT:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_dfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_newfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TUNLINKAT:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_dfid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		offset += 4;

		offset += _9p_dissect_string(tvb, ninep_tree, offset, hf_9P_wname, ett_9P_wname);

		proto_tree_add_item(ninep_tree, hf_9P_unlinkat_flags, tvb,
				offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	case _9P_TREMOVE:
	case _9P_TCLUNK:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));
		conv_free_fid(pinfo, fid);

		conv_set_tag(pinfo, tag, ninemsg, fid, NULL);
		break;

	/* Request with only fid */
	case _9P_TSTATFS:
	case _9P_TREADLINK:
	case _9P_TFSYNC:
	case _9P_TSTAT:
		fid = tvb_get_letohl(tvb, offset);
		ti = proto_tree_add_item(ninep_tree, hf_9P_fid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;
		proto_item_append_text(ti, " (%s)", conv_get_fid(pinfo, fid));

		conv_set_tag(pinfo, tag, ninemsg, _9P_NOFID, NULL);
		break;

	/* Reply with qid and ionuit */
	case _9P_RCREATE:
	case _9P_RLCREATE:
		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;
		proto_tree_add_item(ninep_tree, hf_9P_iounit, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		taginfo = conv_get_tag(pinfo, tag);
		if (taginfo && taginfo->fid_path) {
			conv_set_fid_nocopy(pinfo, taginfo->fid, taginfo->fid_path);
		}

		conv_free_tag(pinfo, tag);
		break;

	case _9P_ROPEN:
	case _9P_RLOPEN:
		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;
		proto_tree_add_item(ninep_tree, hf_9P_iounit, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		offset += 4;

		conv_free_tag(pinfo, tag);
		break;

	/* Reply with only qid */
	case _9P_RSYMLINK:
	case _9P_RMKNOD:
	case _9P_RMKDIR:
	case _9P_RAUTH:
	case _9P_RATTACH:
		dissect_9P_qid(tvb, ninep_tree, offset);
		offset += 13;

		conv_free_tag(pinfo, tag);
		break;

	/* Empty reply */
	case _9P_RRENAME:
	case _9P_RSETATTR:
	case _9P_RXATTRCREATE:
	case _9P_RFSYNC:
	case _9P_RLINK:
	case _9P_RRENAMEAT:
	case _9P_RUNLINKAT:
	case _9P_RFLUSH:
	case _9P_RCLUNK:
	case _9P_RREMOVE:
	/* Unhandled reply */
	case _9P_RWSTAT:
	case _9P_RLERROR:
		conv_free_tag(pinfo, tag);
		break;

	/* Should-not-happen query */
	case _9P_TLERROR:
	case _9P_TERROR:
	default:
		expert_add_info(pinfo, msg_item, &ei_9P_msgtype);
		break;
	}

	/* Show any extra data at the end of the message (but only
	   if it was captured) */
	if (offset != tvb_captured_length(tvb))
		proto_tree_add_item(ninep_tree, hf_9P_unknown_message, tvb, offset, -1, ENC_NA);
	return tvb_captured_length(tvb);
}

static guint get_9P_message_len(packet_info *pinfo _U_, tvbuff_t *tvb,
                                int offset, void *data _U_)
{
	return (guint) tvb_get_letohl(tvb, offset);
}

static int dissect_9P(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
	tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 4,
			get_9P_message_len, dissect_9P_message, data);
	return tvb_captured_length(tvb);
}

/* dissect 9P Qid */
static void dissect_9P_qid(tvbuff_t *tvb,  proto_tree *tree, int offset)
{
	proto_item *qidtype_item;
	proto_tree *qid_tree,*qidtype_tree;
	guint64 path;
	guint32 vers;
	guint8 type;

	if(!tree)
		return;

	type = tvb_get_guint8(tvb, offset);
	vers = tvb_get_letohs(tvb, offset+1);
	path = tvb_get_letoh64(tvb, offset+1+4);

	qid_tree = proto_tree_add_subtree_format(tree, tvb, offset, 13, ett_9P_qid, NULL,
		    "Qid type=0x%02x vers=%d path=%" PRIu64, type, vers, path);

	qidtype_item = proto_tree_add_item(qid_tree, hf_9P_qidtype, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	qidtype_tree = proto_item_add_subtree(qidtype_item, ett_9P_qidtype);

	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_dir,       tvb, offset,     1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_append,    tvb, offset,     1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_exclusive, tvb, offset,     1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_mount,     tvb, offset,     1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_auth_file, tvb, offset,     1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qidtype_tree, hf_9P_qidtype_temp_file, tvb, offset,     1, ENC_LITTLE_ENDIAN);

	proto_tree_add_item(qid_tree, hf_9P_qidvers,               tvb, offset+1,   4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qid_tree, hf_9P_qidpath,               tvb, offset+1+4, 8, ENC_LITTLE_ENDIAN);
}

/*dissect 9P stat mode and create perm flags */
static void dissect_9P_dm(tvbuff_t *tvb,  proto_item *item, int offset, int iscreate)
{
	proto_item *mode_tree;


	mode_tree = proto_item_add_subtree(item, ett_9P_dm);
	if(!mode_tree)
		return;

	proto_tree_add_item(mode_tree, hf_9P_dm_dir, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	if(!iscreate) { /* Not applicable to Tcreate (?) */
		proto_tree_add_item(mode_tree, hf_9P_dm_append,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(mode_tree, hf_9P_dm_exclusive, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(mode_tree, hf_9P_dm_mount,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(mode_tree, hf_9P_dm_auth_file, tvb, offset, 4, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(mode_tree, hf_9P_dm_temp_file, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	}

	proto_tree_add_item(mode_tree, hf_9P_dm_read_owner,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_write_owner,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_exec_owner,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_read_group,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_write_group,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_exec_group,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_read_others,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_write_others, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(mode_tree, hf_9P_dm_exec_others,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
}

/* Dissect 9P getattr_flags */
static void dissect_9P_getattrflags(tvbuff_t *tvb, proto_item *item, int offset)
{
	proto_item *attrmask_tree;

	attrmask_tree = proto_item_add_subtree(item, ett_9P_getattr_flags);
	if(!attrmask_tree)
		return;

	/* fixme: This is actually 8 bytes (64bit) long, but masks have to fit on 32bit. */
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_mode,        tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_nlink,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_uid,         tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_gid,         tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_rdev,        tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_atime,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_mtime,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_ctime,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_ino,         tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_size,        tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_blocks,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_btime,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_gen,         tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_getattr_dataversion, tvb, offset, 4, ENC_LITTLE_ENDIAN);
}

/* Dissect 9P setattr_flags */
static void dissect_9P_setattrflags(tvbuff_t *tvb, proto_item *item, int offset)
{
	proto_item *attrmask_tree;

	attrmask_tree = proto_item_add_subtree(item, ett_9P_setattr_flags);
	if(!attrmask_tree)
		return;

	proto_tree_add_item(attrmask_tree, hf_9P_setattr_mode,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_uid,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_gid,       tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_size,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_atime,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_mtime,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_ctime,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_atime_set, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_setattr_mtime_set, tvb, offset, 4, ENC_LITTLE_ENDIAN);
}

/* Dissect 9P lflags */
static void dissect_9P_lflags(tvbuff_t *tvb, proto_item *item, int offset)
{
	proto_item *attrmask_tree;

	attrmask_tree = proto_item_add_subtree(item, ett_9P_lflags);
	if(!attrmask_tree)
		return;

	proto_tree_add_item(attrmask_tree, hf_9P_lflags_rdonly,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_wronly,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_rdwr,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_create,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_excl,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_noctty,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_trunc,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_append,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_nonblock,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_dsync,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_fasync,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_direct,    tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_largefile, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_directory, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_nofollow,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_noatime,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_cloexec,   tvb, offset, 4, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(attrmask_tree, hf_9P_lflags_sync,      tvb, offset, 4, ENC_LITTLE_ENDIAN);
}


/* Register 9P with Wireshark */
void proto_register_9P(void)
{
	static hf_register_info hf[] = {
		{&hf_9P_msgsz,
		 {"Msg length", "9p.msglen", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "9P Message Length", HFILL}},
		{&hf_9P_msgtype,
		 {"Msg Type", "9p.msgtype", FT_UINT8, BASE_DEC | BASE_EXT_STRING, &ninep_msg_type_ext, 0x0,
		  "Message Type", HFILL}},
		{&hf_9P_tag,
		 {"Tag", "9p.tag", FT_UINT16, BASE_DEC, NULL, 0x0,
		  "9P Tag", HFILL}},
		{&hf_9P_oldtag,
		 {"Old tag", "9p.oldtag", FT_UINT16, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_parmsz,
		 {"Param length", "9p.paramsz", FT_UINT16, BASE_DEC, NULL, 0x0,
		  "Parameter length", HFILL}},
		{&hf_9P_maxsize,
		 {"Max msg size", "9p.maxsize", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Max message size", HFILL}},
		{&hf_9P_fid,
		 {"Fid", "9p.fid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "File ID", HFILL}},
		{&hf_9P_nqid,
		 {"Nr Qids", "9p.nqid", FT_UINT16, BASE_DEC, NULL, 0x0,
		  "Number of Qid results", HFILL}},
		{&hf_9P_mode,
		 {"Mode", "9p.mode", FT_UINT8, BASE_HEX, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_mode_rwx,
		 {"Open/Create Mode", "9p.mode.rwx", FT_UINT8, BASE_OCT | BASE_EXT_STRING, &ninep_mode_vals_ext, _9P_MODEMASK,
		  NULL, HFILL}},
		{&hf_9P_mode_t,
		 {"Trunc", "9p.mode.trunc", FT_BOOLEAN, 8, TFS(&tfs_set_notset), _9P_OTRUNC,
		  "Truncate", HFILL}},
		{&hf_9P_mode_c,
		 {"Remove on close", "9p.mode.orclose", FT_BOOLEAN, 8, TFS(&tfs_set_notset), _9P_ORCLOSE,
		  NULL, HFILL}},
		{&hf_9P_extension,
		 {"Extension string", "9p.extension", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Link target for DSYMLINK mode, major+minor for DMDEVICE, empty for normal files", HFILL}},
		{&hf_9P_iounit,
		 {"I/O Unit", "9p.iounit", FT_UINT32, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_count,
		 {"Count", "9p.count", FT_UINT32, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_offset,
		 {"Offset", "9p.offset", FT_UINT64, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_perm,
		 {"Permissions", "9p.perm", FT_UINT32, BASE_OCT, NULL, 0x0,
		  "Permission bits", HFILL}},
		{&hf_9P_qidpath,
		 {"Qid path", "9p.qidpath", FT_UINT64, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_dm_dir,
		 {"Directory", "9p.dm.dir", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x80000000,
		  NULL, HFILL}},
		{&hf_9P_dm_append,
		 {"Append only", "9p.dm.append", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x40000000,
		  NULL, HFILL}},
		{&hf_9P_dm_exclusive,
		 {"Exclusive use", "9p.dm.exclusive", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x20000000,
		  NULL, HFILL}},
		{&hf_9P_dm_mount,
		 {"Mounted channel", "9p.dm.mount", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x10000000,
		  NULL, HFILL}},
		{&hf_9P_dm_auth_file,
		 {"Authentication file", "9p.dm.auth_file", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x08000000,
		  NULL, HFILL}},
		{&hf_9P_dm_temp_file,
		 {"Temporary file (not backed up)", "9p.dm.temp_file", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x04000000,
		  NULL, HFILL}},
		{&hf_9P_dm_read_owner,
		 {"Read permission for owner", "9p.dm.read_owner", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000400,
		  NULL, HFILL}},
		{&hf_9P_dm_write_owner,
		 {"Write permission for owner", "9p.dm.write_owner", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000200,
		  NULL, HFILL}},
		{&hf_9P_dm_exec_owner,
		 {"Execute permission for owner", "9p.dm.exec_owner", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000100,
		  NULL, HFILL}},
		{&hf_9P_dm_read_group,
		 {"Read permission for group", "9p.dm.read_group", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000040,
		  NULL, HFILL}},
		{&hf_9P_dm_write_group,
		 {"Write permission for group", "9p.dm.write_group", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000020,
		  NULL, HFILL}},
		{&hf_9P_dm_exec_group,
		 {"Execute permission for group", "9p.dm.exec_group", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000010,
		  NULL, HFILL}},
		{&hf_9P_dm_read_others,
		 {"Read permission for others", "9p.dm.read_others", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000004,
		  NULL, HFILL}},
		{&hf_9P_dm_write_others,
		 {"Write permission for others", "9p.dm.write_others", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000002,
		  NULL, HFILL}},
		{&hf_9P_dm_exec_others,
		 {"Execute permission for others", "9p.dm.exec_others", FT_BOOLEAN, 32, TFS(&tfs_yes_no), 0x00000001,
		  NULL, HFILL}},
		{&hf_9P_qidvers,
		 {"Qid version", "9p.qidvers", FT_UINT32, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_qidtype,
		 {"Qid type", "9p.qidtype", FT_UINT8, BASE_HEX, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_qidtype_dir,
		 {"Directory", "9p.qidtype.dir", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x80,
		  NULL, HFILL}},
		{&hf_9P_qidtype_append,
		 {"Append only", "9p.qidtype.append", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x40,
		  NULL, HFILL}},
		{&hf_9P_qidtype_exclusive,
		 {"Exclusive use", "9p.qidtype.exclusive", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x20,
		  NULL, HFILL}},
		{&hf_9P_qidtype_mount,
		 {"Mounted channel", "9p.qidtype.mount", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x10,
		  NULL, HFILL}},
		{&hf_9P_qidtype_auth_file,
		 {"Authentication file", "9p.qidtype.auth_file", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x08,
		  NULL, HFILL}},
		{&hf_9P_qidtype_temp_file,
		 {"Temporary file (not backed up)", "9p.qidtype.temp_file", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x04,
		  NULL, HFILL}},
		{&hf_9P_statmode,
		 {"Mode", "9p.statmode", FT_UINT32, BASE_OCT, NULL, 0x0,
		  "File mode flags", HFILL}},
		{&hf_9P_stattype,
		 {"Type", "9p.stattype", FT_UINT16, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_atime,
		 {"Atime", "9p.atime", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x0,
		  "Access Time", HFILL}},
		{&hf_9P_mtime,
		 {"Mtime", "9p.mtime", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x0,
		  "Modified Time", HFILL}},
		{&hf_9P_ctime,
		 {"Ctime", "9p.ctime", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x0,
		  "Creation Time", HFILL}},
		{&hf_9P_btime,
		 {"Btime", "9p.btime", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x0,
		  "Btime (Synchronization information)", HFILL}},
		{&hf_9P_length,
		 {"Length", "9p.length", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "File Length", HFILL}},
		{&hf_9P_dev,
		 {"Dev", "9p.dev", FT_UINT32, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_wname,
		 {"Wname", "9p.wname", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Path Name Element", HFILL}},
		{&hf_9P_version,
		 {"Version", "9p.version", FT_STRING, BASE_NONE, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_afid,
		 {"Afid", "9p.afid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Authenticating FID", HFILL}},
		{&hf_9P_uname,
		 {"Uname", "9p.uname", FT_STRING, BASE_NONE, NULL, 0x0,
		  "User Name", HFILL}},
		{&hf_9P_aname,
		 {"Aname", "9p.aname", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Access Name", HFILL}},
		{&hf_9P_ename,
		 {"Ename", "9p.ename", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Error", HFILL}},
		{&hf_9P_enum,
		 {"Enum", "9p.enum", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Error", HFILL}},
#if 0
		{&hf_9P_name,
		 {"Name", "9p.name", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Name of file", HFILL}},
#endif
		{&hf_9P_sdlen,
		 {"Stat data length", "9p.sdlen", FT_UINT16, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_filename,
		 {"File name", "9p.filename", FT_STRING, BASE_NONE, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_user,
		 {"User", "9p.user", FT_STRING, BASE_NONE, NULL, 0x0,
		  "User name", HFILL}},
		{&hf_9P_group,
		 {"Group", "9p.group", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Group name", HFILL}},
		{&hf_9P_uid,
		 {"Uid", "9p.uid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "User id", HFILL}},
		{&hf_9P_gid,
		 {"Gid", "9p.gid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Group id", HFILL}},
		{&hf_9P_muid,
		 {"Muid", "9p.muid", FT_STRING, BASE_NONE, NULL, 0x0,
		  "Last modifiers uid", HFILL}},
		{&hf_9P_newfid,
		 {"New fid", "9p.newfid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "New file ID", HFILL}},
		{&hf_9P_dfid,
		 {"Directory fid", "9p.dfid", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Directory ID", HFILL}},
		{&hf_9P_nwalk,
		 {"Nr Walks", "9p.nwalk", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Nr of walk items", HFILL}},
		{&hf_9P_nlink,
		 {"nlink", "9p.nlink", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Number of links", HFILL}},
		{&hf_9P_getattr_flags,
		 {"getattr_flags", "9p.getattr.flags", FT_UINT64, BASE_HEX, NULL, _9P_GETATTR_ALL,
		  "Getattr flags", HFILL}},
		{&hf_9P_getattr_mode,
		 {"Mode", "9p.getattr.mode", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_MODE,
		  NULL, HFILL}},
		{&hf_9P_getattr_nlink,
		 {"Nlink", "9p.getattr.nlink", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_NLINK,
		  NULL, HFILL}},
		{&hf_9P_getattr_uid,
		 {"UID", "9p.getattr.uid", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_UID,
		  NULL, HFILL}},
		{&hf_9P_getattr_gid,
		 {"GID", "9p.getattr.gid", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_GID,
		  NULL, HFILL}},
		{&hf_9P_getattr_rdev,
		 {"Rdev", "9p.getattr.rdev", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_RDEV,
		  NULL, HFILL}},
		{&hf_9P_getattr_atime,
		 {"Atime", "9p.getattr.atime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_ATIME,
		  NULL, HFILL}},
		{&hf_9P_getattr_mtime,
		 {"Mtime", "9p.getattr.mtime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_MTIME,
		  NULL, HFILL}},
		{&hf_9P_getattr_ctime,
		 {"Ctime", "9p.getattr.ctime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_CTIME,
		  NULL, HFILL}},
		{&hf_9P_getattr_ino,
		 {"Inode", "9p.getattr.inode", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_INO,
		  NULL, HFILL}},
		{&hf_9P_getattr_size,
		 {"Size", "9p.getattr.size", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_SIZE,
		  NULL, HFILL}},
		{&hf_9P_getattr_blocks,
		 {"Blocks", "9p.getattr.blocks", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_BLOCKS,
		  NULL, HFILL}},
		{&hf_9P_getattr_btime,
		 {"Btime", "9p.getattr.btime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_BTIME,
		  NULL, HFILL}},
		{&hf_9P_getattr_gen,
		 {"Gen", "9p.getattr.gen", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_GEN,
		  NULL, HFILL}},
		{&hf_9P_getattr_dataversion,
		 {"Data version", "9p.getattr.dataversion", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_GETATTR_DATA_VERSION,
		  NULL, HFILL}},
		{&hf_9P_setattr_flags,
		 {"setattr_flags", "9p.setattr.flags", FT_UINT32, BASE_HEX, NULL, _9P_SETATTR_ALL,
		  "Setattr flags", HFILL}},
		{&hf_9P_setattr_mode,
		 {"Mode", "9p.setattr.mode", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_MODE,
		  NULL, HFILL}},
		{&hf_9P_setattr_uid,
		 {"UID", "9p.setattr.uid", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_UID,
		  NULL, HFILL}},
		{&hf_9P_setattr_gid,
		 {"GID", "9p.setattr.gid", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_GID,
		  NULL, HFILL}},
		{&hf_9P_setattr_size,
		 {"Size", "9p.setattr.size", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_SIZE,
		  NULL, HFILL}},
		{&hf_9P_setattr_atime,
		 {"Atime", "9p.setattr.atime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_ATIME,
		  NULL, HFILL}},
		{&hf_9P_setattr_mtime,
		 {"Mtime", "9p.setattr.mtime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_MTIME,
		  NULL, HFILL}},
		{&hf_9P_setattr_ctime,
		 {"Ctime", "9p.setattr.ctime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_CTIME,
		  NULL, HFILL}},
		{&hf_9P_setattr_atime_set,
		 {"Atime set", "9p.setattr.atimeset", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_ATIME_SET,
		  NULL, HFILL}},
		{&hf_9P_setattr_mtime_set,
		 {"Mtime set", "9p.setattr.mtimeset", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_SETATTR_MTIME_SET,
		  NULL, HFILL}},
		{&hf_9P_unlinkat_flags,
		 {"unlinkat flags", "9p.unlinkat.flags", FT_UINT32, BASE_HEX, NULL, 0,
		  NULL, HFILL}},
		{&hf_9P_rdev,
		 {"rdev", "9p.rdev", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Device associated with file", HFILL}},
		{&hf_9P_size,
		 {"Size", "9p.size", FT_UINT64, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_blksize,
		 {"Blksize", "9p.blksize", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Block size", HFILL}},
		{&hf_9P_blocks,
		 {"Blocks", "9p.blocks", FT_UINT64, BASE_DEC, NULL, 0x0,
		  NULL, HFILL}},
		{&hf_9P_gen,
		 {"Gen", "9p.gen", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "inode generation number", HFILL}},
		{&hf_9P_dataversion,
		 {"Dataversion", "9p.dataversion", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Data version", HFILL}},
		{&hf_9P_fstype,
		 {"fstype", "9p.fstype", FT_UINT32, BASE_HEX, NULL, 0x0,
		  "Filesystem type", HFILL}},
		{&hf_9P_bfree,
		 {"bfree", "9p.bfree", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Free blocks", HFILL}},
		{&hf_9P_bavail,
		 {"bavail", "9p.bavail", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Available blocks", HFILL}},
		{&hf_9P_files,
		 {"files", "9p.files", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Total files", HFILL}},
		{&hf_9P_ffree,
		 {"ffree", "9p.ffree", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Free files", HFILL}},
		{&hf_9P_fsid,
		 {"fsid", "9p.fsid", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Filesystem id", HFILL}},
		{&hf_9P_namelen,
		 {"namelen", "9p.namelen", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Max name length", HFILL}},
		{&hf_9P_mknod_major,
		 {"mknod_major", "9p.mknod.major", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Major node number", HFILL}},
		{&hf_9P_mknod_minor,
		 {"mknod_minor", "9p.mknod.minor", FT_UINT32, BASE_DEC, NULL, 0x0,
		  "Minor node number", HFILL}},
		{&hf_9P_lflags,
		 {"lflags", "9p.lcreate.flags", FT_UINT32, BASE_HEX, NULL, 0x0,
		  "Lcreate flags", HFILL}},
		/* rdonly is 0x00, check instead that we are neither wronly nor rdwrite */
		{&hf_9P_lflags_rdonly,
		 {"Read only", "9p.lflags.rdonly", FT_BOOLEAN, 32, TFS(&tfs_no_yes), _9P_DOTL_WRONLY|_9P_DOTL_RDWR,
		  NULL, HFILL}},
		{&hf_9P_lflags_wronly,
		 {"Write only", "9p.lflags.wronly", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_WRONLY,
		  NULL, HFILL}},
		{&hf_9P_lflags_rdwr,
		 {"Read Write", "9p.lflags.rdwr", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_RDWR,
		  NULL, HFILL}},
		{&hf_9P_lflags_create,
		 {"Create", "9p.lflags.create", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_CREATE,
		  NULL, HFILL}},
		{&hf_9P_lflags_excl,
		 {"Exclusive", "9p.lflags.excl", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_EXCL,
		  NULL, HFILL}},
		{&hf_9P_lflags_noctty,
		 {"noctty", "9p.lflags.noctty", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_NOCTTY,
		  NULL, HFILL}},
		{&hf_9P_lflags_trunc,
		 {"Truncate", "9p.lflags.trunc", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_TRUNC,
		  NULL, HFILL}},
		{&hf_9P_lflags_append,
		 {"Append", "9p.lflags.append", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_APPEND,
		  NULL, HFILL}},
		{&hf_9P_lflags_nonblock,
		 {"Nonblock", "9p.lflags.nonblock", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_NONBLOCK,
		  NULL, HFILL}},
		{&hf_9P_lflags_dsync,
		 {"dsync", "9p.lflags.dsync", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_DSYNC,
		  NULL, HFILL}},
		{&hf_9P_lflags_fasync,
		 {"fasync", "9p.lflags.fasync", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_FASYNC,
		  NULL, HFILL}},
		{&hf_9P_lflags_direct,
		 {"Direct", "9p.lflags.direct", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_DIRECT,
		  NULL, HFILL}},
		{&hf_9P_lflags_largefile,
		 {"Large File", "9p.lflags.largefile", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_LARGEFILE,
		  NULL, HFILL}},
		{&hf_9P_lflags_directory,
		 {"Directory", "9p.lflags.directory", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_DIRECTORY,
		  NULL, HFILL}},
		{&hf_9P_lflags_nofollow,
		 {"No follow", "9p.lflags.nofollow", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_NOFOLLOW,
		  NULL, HFILL}},
		{&hf_9P_lflags_noatime,
		 {"No atime", "9p.lflags.noatime", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_NOATIME,
		  NULL, HFILL}},
		{&hf_9P_lflags_cloexec,
		 {"cloexec", "9p.lflags.cloexec", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_CLOEXEC,
		  NULL, HFILL}},
		{&hf_9P_lflags_sync,
		 {"Sync", "9p.lflags.sync", FT_BOOLEAN, 32, TFS(&tfs_yes_no), _9P_DOTL_SYNC,
		  NULL, HFILL}},
		{&hf_9P_xattr_flag,
		 {"xattr_flag", "9p.xattr.flag", FT_UINT32, BASE_HEX, NULL, 0x0,
		  "Xattr flag", HFILL}},
		{&hf_9P_lock_type,
		 {"lock_type", "9p.lock.type", FT_UINT32, BASE_HEX | BASE_EXT_STRING, &ninep_lock_type_ext, 0x0,
		  "Lock type", HFILL}},
		{&hf_9P_lock_flag,
		 {"lock_flag", "9p.lock.flag", FT_UINT32, BASE_HEX | BASE_EXT_STRING, &ninep_lock_flag_ext, 0x0,
		  "Lock flag", HFILL}},
		{&hf_9P_lock_start,
		 {"lock_start", "9p.lock.start", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Lock start", HFILL}},
		{&hf_9P_lock_length,
		 {"lock_length", "9p.lock.length", FT_UINT64, BASE_DEC, NULL, 0x0,
		  "Lock length", HFILL}},
		{&hf_9P_lock_procid,
		 {"lock_procid", "9p.lock.procid", FT_UINT32, BASE_HEX, NULL, 0x0,
		  "Lock procid", HFILL}},
		{&hf_9P_lock_status,
		 {"lock_status", "9p.lock.status", FT_UINT8, BASE_HEX | BASE_EXT_STRING, &ninep_lock_status_ext, 0x0,
		  "Lock status", HFILL}},
		{&hf_9P_unknown_message,
		 {"Message data", "9p.message_data", FT_BYTES, BASE_NONE, NULL, 0x0,
		  NULL, HFILL}}
	};

	static gint *ett[] = {
		&ett_9P,
		&ett_9P_omode,
		&ett_9P_dm,
		&ett_9P_wname,
		&ett_9P_aname,
		&ett_9P_ename,
		&ett_9P_uname,
		&ett_9P_user,
		&ett_9P_group,
		&ett_9P_muid,
		&ett_9P_filename,
		&ett_9P_version,
		&ett_9P_qid,
		&ett_9P_qidtype,
		&ett_9P_getattr_flags,
		&ett_9P_setattr_flags,
		&ett_9P_lflags,
	};

	expert_module_t* expert_9P;

	static ei_register_info ei[] = {
		{ &ei_9P_first_250, { "9p.first_250", PI_PROTOCOL, PI_NOTE, "Only first 250 items shown", EXPFILL }},
		{ &ei_9P_msgtype, { "9p.msgtype.unknown", PI_PROTOCOL, PI_WARN, "This message type should not happen", EXPFILL }},
	};

	proto_9P = proto_register_protocol("Plan 9", "9P", "9p");

	proto_register_field_array(proto_9P, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	expert_9P = expert_register_protocol(proto_9P);
	expert_register_field_array(expert_9P, ei, array_length(ei));

	_9p_hashtable = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), _9p_hash_hash, _9p_hash_equal);

	ninep_handle = register_dissector("9p", dissect_9P, proto_9P);
}

void proto_reg_handoff_9P(void)
{
	dissector_add_uint_with_preference("tcp.port", NINEPORT, ninep_handle);
}


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