// Copyright 2009 The Go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The p9 package go9provides the definitions and functions used to implement
// the 9P2000 protocol.
// TODO.
// All the packet conversion code in this file is crap and needs a rewrite.
package go9p

import (
	"fmt"
)

// 9P2000 message types
const (
	Tversion = 100 + iota
	Rversion
	Tauth
	Rauth
	Tattach
	Rattach
	Terror
	Rerror
	Tflush
	Rflush
	Twalk
	Rwalk
	Topen
	Ropen
	Tcreate
	Rcreate
	Tread
	Rread
	Twrite
	Rwrite
	Tclunk
	Rclunk
	Tremove
	Rremove
	Tstat
	Rstat
	Twstat
	Rwstat
	Tlast
)

const (
	MSIZE   = 1048576 + IOHDRSZ // default message size (1048576+IOHdrSz)
	IOHDRSZ = 24                // the non-data size of the Twrite messages
	PORT    = 564               // default port for 9P file servers
)

// Qid types
const (
	QTDIR     = 0x80 // directories
	QTAPPEND  = 0x40 // append only files
	QTEXCL    = 0x20 // exclusive use files
	QTMOUNT   = 0x10 // mounted channel
	QTAUTH    = 0x08 // authentication file
	QTTMP     = 0x04 // non-backed-up file
	QTSYMLINK = 0x02 // symbolic link (Unix, 9P2000.u)
	QTLINK    = 0x01 // hard link (Unix, 9P2000.u)
	QTFILE    = 0x00
)

// Flags for the mode field in Topen and Tcreate messages
const (
	OREAD   = 0  // open read-only
	OWRITE  = 1  // open write-only
	ORDWR   = 2  // open read-write
	OEXEC   = 3  // execute (== read but check execute permission)
	OTRUNC  = 16 // or'ed in (except for exec), truncate file first
	OCEXEC  = 32 // or'ed in, close on exec
	ORCLOSE = 64 // or'ed in, remove on close
)

// File modes
const (
	DMDIR       = 0x80000000 // mode bit for directories
	DMAPPEND    = 0x40000000 // mode bit for append only files
	DMEXCL      = 0x20000000 // mode bit for exclusive use files
	DMMOUNT     = 0x10000000 // mode bit for mounted channel
	DMAUTH      = 0x08000000 // mode bit for authentication file
	DMTMP       = 0x04000000 // mode bit for non-backed-up file
	DMSYMLINK   = 0x02000000 // mode bit for symbolic link (Unix, 9P2000.u)
	DMLINK      = 0x01000000 // mode bit for hard link (Unix, 9P2000.u)
	DMDEVICE    = 0x00800000 // mode bit for device file (Unix, 9P2000.u)
	DMNAMEDPIPE = 0x00200000 // mode bit for named pipe (Unix, 9P2000.u)
	DMSOCKET    = 0x00100000 // mode bit for socket (Unix, 9P2000.u)
	DMSETUID    = 0x00080000 // mode bit for setuid (Unix, 9P2000.u)
	DMSETGID    = 0x00040000 // mode bit for setgid (Unix, 9P2000.u)
	DMREAD      = 0x4        // mode bit for read permission
	DMWRITE     = 0x2        // mode bit for write permission
	DMEXEC      = 0x1        // mode bit for execute permission
)

const (
	NOTAG uint16 = 0xFFFF     // no tag specified
	NOFID uint32 = 0xFFFFFFFF // no fid specified
	NOUID uint32 = 0xFFFFFFFF // no uid specified
)

// Error values
const (
	EPERM   = 1
	ENOENT  = 2
	EIO     = 5
	EEXIST  = 17
	ENOTDIR = 20
	EINVAL  = 22
)

// Error represents a 9P2000 (and 9P2000.u) error
type Error struct {
	Err      string // textual representation of the error
	Errornum uint32 // numeric representation of the error (9P2000.u)
}

// File identifier
type Qid struct {
	Type    uint8  // type of the file (high 8 bits of the mode)
	Version uint32 // version number for the path
	Path    uint64 // server's unique identification of the file
}

// Dir describes a file
type Dir struct {
	Size   uint16 // size-2 of the Dir on the wire
	Type   uint16
	Dev    uint32
	Qid           // file's Qid
	Mode   uint32 // permissions and flags
	Atime  uint32 // last access time in seconds
	Mtime  uint32 // last modified time in seconds
	Length uint64 // file length in bytes
	Name   string // file name
	Uid    string // owner name
	Gid    string // group name
	Muid   string // name of the last user that modified the file

	/* 9P2000.u extension */
	Ext     string // special file's descriptor
	Uidnum  uint32 // owner ID
	Gidnum  uint32 // group ID
	Muidnum uint32 // ID of the last user that modified the file
}

// Fcall represents a 9P2000 message
type Fcall struct {
	Size    uint32   // size of the message
	Type    uint8    // message type
	Fid     uint32   // file identifier
	Tag     uint16   // message tag
	Msize   uint32   // maximum message size (used by Tversion, Rversion)
	Version string   // protocol version (used by Tversion, Rversion)
	Oldtag  uint16   // tag of the message to flush (used by Tflush)
	Error   string   // error (used by Rerror)
	Qid              // file Qid (used by Rauth, Rattach, Ropen, Rcreate)
	Iounit  uint32   // maximum bytes read without breaking in multiple messages (used by Ropen, Rcreate)
	Afid    uint32   // authentication fid (used by Tauth, Tattach)
	Uname   string   // user name (used by Tauth, Tattach)
	Aname   string   // attach name (used by Tauth, Tattach)
	Perm    uint32   // file permission (mode) (used by Tcreate)
	Name    string   // file name (used by Tcreate)
	Mode    uint8    // open mode (used by Topen, Tcreate)
	Newfid  uint32   // the fid that represents the file walked to (used by Twalk)
	Wname   []string // list of names to walk (used by Twalk)
	Wqid    []Qid    // list of Qids for the walked files (used by Rwalk)
	Offset  uint64   // offset in the file to read/write from/to (used by Tread, Twrite)
	Count   uint32   // number of bytes read/written (used by Tread, Rread, Twrite, Rwrite)
	Data    []uint8  // data read/to-write (used by Rread, Twrite)
	Dir              // file description (used by Rstat, Twstat)

	/* 9P2000.u extensions */
	Errornum uint32 // error code, 9P2000.u only (used by Rerror)
	Ext      string // special file description, 9P2000.u only (used by Tcreate)
	Unamenum uint32 // user ID, 9P2000.u only (used by Tauth, Tattach)

	Pkt []uint8 // raw packet data
	Buf []uint8 // buffer to put the raw data in
}

// Interface for accessing users and groups
type Users interface {
	Uid2User(uid int) User
	Uname2User(uname string) User
	Gid2Group(gid int) Group
	Gname2Group(gname string) Group
}

// Represents a user
type User interface {
	Name() string          // user name
	Id() int               // user id
	Groups() []Group       // groups the user belongs to (can return nil)
	IsMember(g Group) bool // returns true if the user is member of the specified group
}

// Represents a group of users
type Group interface {
	Name() string    // group name
	Id() int         // group id
	Members() []User // list of members that belong to the group (can return nil)
}

// minimum size of a 9P2000 message for a type
var minFcsize = [...]uint32{
	6,  /* Tversion msize[4] version[s] */
	6,  /* Rversion msize[4] version[s] */
	8,  /* Tauth fid[4] uname[s] aname[s] */
	13, /* Rauth aqid[13] */
	12, /* Tattach fid[4] afid[4] uname[s] aname[s] */
	13, /* Rattach qid[13] */
	0,  /* Terror */
	2,  /* Rerror ename[s] (ecode[4]) */
	2,  /* Tflush oldtag[2] */
	0,  /* Rflush */
	10, /* Twalk fid[4] newfid[4] nwname[2] */
	2,  /* Rwalk nwqid[2] */
	5,  /* Topen fid[4] mode[1] */
	17, /* Ropen qid[13] iounit[4] */
	11, /* Tcreate fid[4] name[s] perm[4] mode[1] */
	17, /* Rcreate qid[13] iounit[4] */
	16, /* Tread fid[4] offset[8] count[4] */
	4,  /* Rread count[4] */
	16, /* Twrite fid[4] offset[8] count[4] */
	4,  /* Rwrite count[4] */
	4,  /* Tclunk fid[4] */
	0,  /* Rclunk */
	4,  /* Tremove fid[4] */
	0,  /* Rremove */
	4,  /* Tstat fid[4] */
	4,  /* Rstat stat[n] */
	8,  /* Twstat fid[4] stat[n] */
	0,  /* Rwstat */
	20, /* Tbread fileid[8] offset[8] count[4] */
	4,  /* Rbread count[4] */
	20, /* Tbwrite fileid[8] offset[8] count[4] */
	4,  /* Rbwrite count[4] */
	16, /* Tbtrunc fileid[8] offset[8] */
	0,  /* Rbtrunc */
}

// minimum size of a 9P2000.u message for a type
var minFcusize = [...]uint32{
	6,  /* Tversion msize[4] version[s] */
	6,  /* Rversion msize[4] version[s] */
	12, /* Tauth fid[4] uname[s] aname[s] */
	13, /* Rauth aqid[13] */
	16, /* Tattach fid[4] afid[4] uname[s] aname[s] */
	13, /* Rattach qid[13] */
	0,  /* Terror */
	6,  /* Rerror ename[s] (ecode[4]) */
	2,  /* Tflush oldtag[2] */
	0,  /* Rflush */
	10, /* Twalk fid[4] newfid[4] nwname[2] */
	2,  /* Rwalk nwqid[2] */
	5,  /* Topen fid[4] mode[1] */
	17, /* Ropen qid[13] iounit[4] */
	13, /* Tcreate fid[4] name[s] perm[4] mode[1] */
	17, /* Rcreate qid[13] iounit[4] */
	16, /* Tread fid[4] offset[8] count[4] */
	4,  /* Rread count[4] */
	16, /* Twrite fid[4] offset[8] count[4] */
	4,  /* Rwrite count[4] */
	4,  /* Tclunk fid[4] */
	0,  /* Rclunk */
	4,  /* Tremove fid[4] */
	0,  /* Rremove */
	4,  /* Tstat fid[4] */
	4,  /* Rstat stat[n] */
	8,  /* Twstat fid[4] stat[n] */
	20, /* Tbread fileid[8] offset[8] count[4] */
	4,  /* Rbread count[4] */
	20, /* Tbwrite fileid[8] offset[8] count[4] */
	4,  /* Rbwrite count[4] */
	16, /* Tbtrunc fileid[8] offset[8] */
	0,  /* Rbtrunc */
}

func gint8(buf []byte) (uint8, []byte) { return buf[0], buf[1:] }

func gint16(buf []byte) (uint16, []byte) {
	return uint16(buf[0]) | (uint16(buf[1]) << 8), buf[2:]
}

func gint32(buf []byte) (uint32, []byte) {
	return uint32(buf[0]) | (uint32(buf[1]) << 8) | (uint32(buf[2]) << 16) |
			(uint32(buf[3]) << 24),
		buf[4:]
}

func Gint32(buf []byte) (uint32, []byte) { return gint32(buf) }

func gint64(buf []byte) (uint64, []byte) {
	return uint64(buf[0]) | (uint64(buf[1]) << 8) | (uint64(buf[2]) << 16) |
			(uint64(buf[3]) << 24) | (uint64(buf[4]) << 32) | (uint64(buf[5]) << 40) |
			(uint64(buf[6]) << 48) | (uint64(buf[7]) << 56),
		buf[8:]
}

func gstr(buf []byte) (string, []byte) {
	var n uint16

	if buf == nil || len(buf) < 2 {
		return "", nil
	}

	n, buf = gint16(buf)

	if int(n) > len(buf) {
		return "", nil
	}

	return string(buf[0:n]), buf[n:]
}

func gqid(buf []byte, qid *Qid) []byte {
	qid.Type, buf = gint8(buf)
	qid.Version, buf = gint32(buf)
	qid.Path, buf = gint64(buf)

	return buf
}

func gstat(buf []byte, d *Dir, dotu bool) ([]byte, error) {
	sz := len(buf)
	d.Size, buf = gint16(buf)
	d.Type, buf = gint16(buf)
	d.Dev, buf = gint32(buf)
	buf = gqid(buf, &d.Qid)
	d.Mode, buf = gint32(buf)
	d.Atime, buf = gint32(buf)
	d.Mtime, buf = gint32(buf)
	d.Length, buf = gint64(buf)
	d.Name, buf = gstr(buf)
	if buf == nil {
		s := fmt.Sprintf("Buffer too short for basic 9p: need %d, have %d",
			49, sz)
		return nil, &Error{s, EINVAL}
	}

	d.Uid, buf = gstr(buf)
	if buf == nil {
		return nil, &Error{"d.Uid failed", EINVAL}
	}
	d.Gid, buf = gstr(buf)
	if buf == nil {
		return nil, &Error{"d.Gid failed", EINVAL}
	}

	d.Muid, buf = gstr(buf)
	if buf == nil {
		return nil, &Error{"d.Muid failed", EINVAL}
	}

	if dotu {
		d.Ext, buf = gstr(buf)
		if buf == nil {
			return nil, &Error{"d.Ext failed", EINVAL}
		}

		d.Uidnum, buf = gint32(buf)
		d.Gidnum, buf = gint32(buf)
		d.Muidnum, buf = gint32(buf)
	} else {
		d.Uidnum = NOUID
		d.Gidnum = NOUID
		d.Muidnum = NOUID
	}

	return buf, nil
}

func pint8(val uint8, buf []byte) []byte {
	buf[0] = val
	return buf[1:]
}

func pint16(val uint16, buf []byte) []byte {
	buf[0] = uint8(val)
	buf[1] = uint8(val >> 8)
	return buf[2:]
}

func pint32(val uint32, buf []byte) []byte {
	buf[0] = uint8(val)
	buf[1] = uint8(val >> 8)
	buf[2] = uint8(val >> 16)
	buf[3] = uint8(val >> 24)
	return buf[4:]
}

func pint64(val uint64, buf []byte) []byte {
	buf[0] = uint8(val)
	buf[1] = uint8(val >> 8)
	buf[2] = uint8(val >> 16)
	buf[3] = uint8(val >> 24)
	buf[4] = uint8(val >> 32)
	buf[5] = uint8(val >> 40)
	buf[6] = uint8(val >> 48)
	buf[7] = uint8(val >> 56)
	return buf[8:]
}

func pstr(val string, buf []byte) []byte {
	n := uint16(len(val))
	buf = pint16(n, buf)
	b := []byte(val)
	copy(buf, b)
	return buf[n:]
}

func pqid(val *Qid, buf []byte) []byte {
	buf = pint8(val.Type, buf)
	buf = pint32(val.Version, buf)
	buf = pint64(val.Path, buf)

	return buf
}

func statsz(d *Dir, dotu bool) int {
	sz := 2 + 2 + 4 + 13 + 4 + 4 + 4 + 8 + 2 + 2 + 2 + 2 + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid)
	if dotu {
		sz += 2 + 4 + 4 + 4 + len(d.Ext)
	}

	return sz
}

func pstat(d *Dir, buf []byte, dotu bool) []byte {
	sz := statsz(d, dotu)
	buf = pint16(uint16(sz-2), buf)
	buf = pint16(d.Type, buf)
	buf = pint32(d.Dev, buf)
	buf = pqid(&d.Qid, buf)
	buf = pint32(d.Mode, buf)
	buf = pint32(d.Atime, buf)
	buf = pint32(d.Mtime, buf)
	buf = pint64(d.Length, buf)
	buf = pstr(d.Name, buf)
	buf = pstr(d.Uid, buf)
	buf = pstr(d.Gid, buf)
	buf = pstr(d.Muid, buf)
	if dotu {
		buf = pstr(d.Ext, buf)
		buf = pint32(d.Uidnum, buf)
		buf = pint32(d.Gidnum, buf)
		buf = pint32(d.Muidnum, buf)
	}

	return buf
}

// Converts a Dir value to its on-the-wire representation and writes it to
// the buf. Returns the number of bytes written, 0 if there is not enough space.
func PackDir(d *Dir, dotu bool) []byte {
	sz := statsz(d, dotu)
	buf := make([]byte, sz)
	pstat(d, buf, dotu)
	return buf
}

// Converts the on-the-wire representation of a stat to Stat value.
// Returns an error if the conversion is impossible, otherwise
// a pointer to a Stat value.
func UnpackDir(buf []byte, dotu bool) (d *Dir, b []byte, amt int, err error) {
	sz := 2 + 2 + 4 + 13 + 4 + /* size[2] type[2] dev[4] qid[13] mode[4] */
		4 + 4 + 8 + /* atime[4] mtime[4] length[8] */
		2 + 2 + 2 + 2 /* name[s] uid[s] gid[s] muid[s] */

	if dotu {
		sz += 2 + 4 + 4 + 4 /* extension[s] n_uid[4] n_gid[4] n_muid[4] */
	}

	if len(buf) < sz {
		s := fmt.Sprintf("short buffer: Need %d and have %v", sz, len(buf))
		return nil, nil, 0, &Error{s, EINVAL}
	}

	d = new(Dir)
	b, err = gstat(buf, d, dotu)
	if err != nil {
		return nil, nil, 0, err
	}

	return d, b, len(buf) - len(b), nil

}

// Allocates a new Fcall.
func NewFcall(sz uint32) *Fcall {
	fc := new(Fcall)
	fc.Buf = make([]byte, sz)

	return fc
}

// Sets the tag of a Fcall.
func SetTag(fc *Fcall, tag uint16) {
	fc.Tag = tag
	pint16(tag, fc.Pkt[5:])
}

func packCommon(fc *Fcall, size int, id uint8) ([]byte, error) {
	size += 4 + 1 + 2 /* size[4] id[1] tag[2] */
	if len(fc.Buf) < int(size) {
		return nil, &Error{"buffer too small", EINVAL}
	}

	fc.Size = uint32(size)
	fc.Type = id
	fc.Tag = NOTAG
	p := fc.Buf
	p = pint32(uint32(size), p)
	p = pint8(id, p)
	p = pint16(NOTAG, p)
	fc.Pkt = fc.Buf[0:size]

	return p, nil
}

func (err *Error) Error() string {
	if err != nil {
		return err.Err
	}

	return ""
}
