~rbn/neinp

b441da6dea2e963387a86b9d1d57802b77f21b83 — Ruben Schuller 2 years ago v0.0.1
initial commit
A  => LICENSE +24 -0
@@ 1,24 @@
Copyright 2018 Ruben Schuller

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A  => README.md +16 -0
@@ 1,16 @@
# neinp
[![GoDoc](https://godoc.org/github.com/rbns/neinp?status.svg)](https://godoc.org/github.com/rbns/neinp)

## about
neinp is a toolkit to implement 9p servers.

## installation

	go get github.com/rbns/neinp

## usage
a example of how to use this can be found here: https://github.com/rbns/rssfs

furthermore, the documentation of this package should cover most things. additional information
about the 9p protocol can be found in the plan9 manual (should be available online at [cat-v.org](http://man.cat-v.org/plan_9/5/) for example).


A  => basic/basic.go +160 -0
@@ 1,160 @@
//Package basic handles en-/decoding of basic types to 9p wire format.
package basic

import (
	"io"
)

//Uint8Decode reads a single byte.
//
//Returns the decoded byte, the count of read bytes and a possible error.
func Uint8Decode(r io.Reader) (uint8, int64, error) {
	buf := make([]byte, 1)
	n, err := r.Read(buf)
	if n < 1 || err != nil {
		return 0, int64(n), err
	}
	return uint8(buf[0]), int64(n), nil
}

//Uint8Encode writes a single byte.
//
//Returns the count of written bytes and a possible error.
func Uint8Encode(w io.Writer, x uint8) (int64, error) {
	buf := []byte{x}
	n, err := w.Write(buf)
	if n < 1 || err != nil {
		return int64(n), err
	}
	return int64(n), nil
}

//Uint16Decode reads a 16 bit value from an io.Reader.
//
//Returns the decoded uint16 value, the count of read bytes and a possible error.
func Uint16Decode(r io.Reader) (uint16, int64, error) {
	buf := make([]byte, 2)
	n, err := r.Read(buf)
	if n < 2 || err != nil {
		return 0, int64(n), err
	}
	x := uint16(buf[0]) | uint16(buf[1])<<8
	return x, int64(n), nil
}

//Uint16Encode writes a uint16 value.
//
//Returns the count of written bytes and a possible error.
func Uint16Encode(w io.Writer, x uint16) (int64, error) {
	buf := []byte{uint8(x), uint8(x >> 8)}
	n, err := w.Write(buf)
	if n < 2 || err != nil {
		return int64(n), err
	}
	return int64(n), nil
}

//Uint32Decode reads a 32 bit value from an io.Reader.
//
//Returns the decoded uint32 value, the count of read bytes and a possible error.
func Uint32Decode(r io.Reader) (uint32, int64, error) {
	buf := make([]byte, 4)
	n, err := r.Read(buf)
	if n < 4 || err != nil {
		return 0, int64(n), err
	}
	x := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
	return x, int64(n), nil
}

//Uint32Encode writes a uint32 value.
//
//Returns the count of written bytes and a possible error.
func Uint32Encode(w io.Writer, x uint32) (int64, error) {
	buf := []byte{uint8(x), uint8(x >> 8), uint8(x >> 16), uint8(x >> 24)}
	n, err := w.Write(buf)
	if n < 4 || err != nil {
		return int64(n), err
	}
	return int64(n), nil
}

//Uint64Decode reads a 64 bit value from an io.Reader.
//
//Returns the decoded uint64 value, the count of read bytes and a possible error.
func Uint64Decode(r io.Reader) (uint64, int64, error) {
	buf := make([]byte, 8)
	n, err := r.Read(buf)
	if n < 8 || err != nil {
		return 0, int64(n), err
	}
	x := 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
	return x, int64(n), nil
}

//Uint64Encode writes a uint64 value.
//
//Returns the count of written bytes and a possible error.
func Uint64Encode(w io.Writer, x uint64) (int64, error) {
	buf := []byte{uint8(x), uint8(x >> 8), uint8(x >> 16), uint8(x >> 24), uint8(x >> 32), uint8(x >> 40), uint8(x >> 48), uint8(x >> 56)}
	n, err := w.Write(buf)
	if n < 8 || err != nil {
		return int64(n), err
	}
	return int64(n), nil
}

//BytesDecode reads a variable number of bytes into a byte slice.
//
//Returns a byte slice, the count of read bytes and a possible error.
func BytesDecode(r io.Reader) ([]byte, int64, error) {
	size, n1, err := Uint16Decode(r)
	if err != nil {
		return []byte{}, n1, err
	}

	buf := make([]byte, size)
	n2, err := r.Read(buf)
	if err != nil {
		return []byte{}, n1 + int64(n2), err
	}

	return buf, n1 + int64(n2), nil
}

//BytesEncode writes a byte slice.
//
//Returns the count of written bytes and a possible error.
func BytesEncode(w io.Writer, b []byte) (int64, error) {
	size := uint16(len(b))
	n1, err := Uint16Encode(w, size)
	if err != nil {
		return n1, err
	}

	n2, err := w.Write(b)
	if err != nil {
		return n1 + int64(n2), err
	}

	return n1 + int64(n2), nil
}

//StringDecode reads a string.
//
//Returns a string, the count of read bytes and a possible error.
func StringDecode(r io.Reader) (string, int64, error) {
	b, n1, err := BytesDecode(r)
	if err != nil {
		return "", n1, err
	}

	return string(b), n1, nil
}

//StringEncode writes a string.
//
//Returns the count of written bytes and a possible error.
func StringEncode(w io.Writer, s string) (int64, error) {
	return BytesEncode(w, []byte(s))
}

A  => basic/basic_test.go +114 -0
@@ 1,114 @@
package basic

import (
	"bytes"
	"testing"
)

func TestUint8EncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := Uint8Encode(&buf, 1<<8-1)
	if err != nil {
		t.Error(err)
	}

	x, _, err := Uint8Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if x != 1<<8-1 {
		t.Fail()
	}
}

func TestUint16EncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := Uint16Encode(&buf, 1<<16-1)
	if err != nil {
		t.Error(err)
	}

	x, _, err := Uint16Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if x != 1<<16-1 {
		t.Fail()
	}
}

func TestUint32EncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := Uint32Encode(&buf, 1<<32-1)
	if err != nil {
		t.Error(err)
	}

	x, _, err := Uint32Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if x != 1<<32-1 {
		t.Fail()
	}
}

func TestUint64EncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := Uint64Encode(&buf, 1<<64-1)
	if err != nil {
		t.Error(err)
	}

	x, _, err := Uint64Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if x != 1<<64-1 {
		t.Fail()
	}
}

func TestBytesEncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := BytesEncode(&buf, []byte{0xDE, 0xAD, 0xBE, 0xEF})
	if err != nil {
		t.Error(err)
	}

	x, _, err := BytesDecode(&buf)
	if err != nil {
		t.Error(err)
	}

	if !bytes.Equal(x, []byte{0xDE, 0xAD, 0xBE, 0xEF}) {
		t.Fail()
	}
}

func TestStringEncodeDecode(t *testing.T) {
	var buf bytes.Buffer

	_, err := StringEncode(&buf, "deadbeef")
	if err != nil {
		t.Error(err)
	}

	x, _, err := StringDecode(&buf)
	if err != nil {
		t.Error(err)
	}

	if x != "deadbeef" {
		t.Fail()
	}
}

A  => doc.go +10 -0
@@ 1,10 @@
/*Package neinp is a toolkit to implement 9p servers.

A 9p filesystem is created by implementing the P2000 interface, which is then used as argument
for NewServer. Server can then use an io.ReadWriter supplied to the Serve method
handle requests using the aforementioned P2000 implementer.

See https://github.com/rbns/rssfs for an example.

NB: This isn't really polished yet, things may break.*/
package neinp

A  => example_server_test.go +31 -0
@@ 1,31 @@
package neinp_test

import (
	"github.com/rbns/neinp"
	"fmt"
	"net"
)

func ExampleServer() {
	fs := &neinp.NopP2000{}

	l, err := net.Listen("tcp", "localhost:9999")
	if err != nil {
		fmt.Println("listen:", err)
		return
	}

	for {
		c, err := l.Accept()
		if err != nil {
			fmt.Println("accept:", err)
			return
		}

		s := neinp.NewServer(fs)
		if err := s.Serve(c); err != nil {
			fmt.Println("serve:", err)
			return
		}
	}
}

A  => fid/fid.go +4 -0
@@ 1,4 @@
//Package fid supplies a fid type and the usual mutexed map for storing them.
package fid

type Fid uint32

A  => fid/map.go +38 -0
@@ 1,38 @@
package fid

import (
	"sync"
)

type Map struct {
	fids map[Fid]interface{}
	sync.RWMutex
}

func New() *Map {
	return &Map{fids: make(map[Fid]interface{})}
}

func (f *Map) Get(fid Fid) interface{} {
	f.RLock()
	defer f.RUnlock()

	v, ok := f.fids[fid]
	if !ok {
		return nil
	}

	return v
}

func (f *Map) Set(fid Fid, v interface{}) {
	f.Lock()
	defer f.Unlock()
	f.fids[fid] = v
}

func (f *Map) Delete(fid Fid) {
	f.Lock()
	defer f.Unlock()
	delete(f.fids, fid)
}

A  => fs/entry.go +159 -0
@@ 1,159 @@
/*Package fs provides helpers to implement 9p objects: directories and files.*/
package fs

import (
	"github.com/rbns/neinp/message"
	"github.com/rbns/neinp/qid"
	"github.com/rbns/neinp/stat"
	"errors"
	"io"
)

//Entry is the general interface for 9p objects.
type Entry interface {
	Parent() Entry              // Parent returns this objects parent, it may be nil for the root object.
	Qid() qid.Qid               // Qid returns this files qid.
	Stat() stat.Stat            // Stat returns this files stat.
	Open() error                // Open prepares this file for access.
	Walk(string) (Entry, error) // Walk to a child or itself.
	io.ReadSeeker               // All files and directories need this.
}

//Dir implements a directory.
type Dir struct {
	parent   Entry
	stat     stat.Stat
	children []Entry
	*stat.Reader
}

//NewDir creates a new Dir ready for use.
//
//The stat and child entries are expected to be prepared by the caller.
//Children will have their parent set to this Dir.
func NewDir(s stat.Stat, children []Entry) *Dir {
	d := &Dir{
		stat: s,
	}

	for _, child := range children {
		switch t := child.(type) {
		case *Dir:
			t.parent = d
		case *File:
			t.parent = d
		}
	}

	d.children = children

	// just initialize with an empty stat.Reader here so we dont' hit nil
	d.Reader = stat.NewReader()

	return d
}

//Parent returns the parent of this dir, which may be nil if it is the root.
func (d *Dir) Parent() Entry {
	return d.parent
}

//Qid returns the qid of this dir.
func (d *Dir) Qid() qid.Qid {
	return d.stat.Qid
}

//Stat returns the stat of this dir.
func (d *Dir) Stat() stat.Stat {
	return d.stat
}

//Open prepares the dir for access.
//
//Internally, a stat.Reader is prepared from the contents of the children.
func (d *Dir) Open() error {
	stats := make([]stat.Stat, len(d.children))
	for i, child := range d.children {
		stats[i] = child.Stat()
	}

	d.Reader = stat.NewReader(stats...)
	return nil
}

//Walk to a child, the parent, or itself.
//
//wname is either the name of a child, "..", or "".
//If it is the name of a child, the respective entry is returned.
//For ".." the parent, or the dir itself (if it is the root) is returned.
//When wname is the empty string, the dir itself is returned.
func (d *Dir) Walk(wname string) (Entry, error) {
	if wname == ".." {
		if d.parent == nil {
			return d, nil
		}
		return d.parent, nil
	}

	for _, child := range d.children {
		if child.Stat().Name == wname {
			return child, nil
		}
	}

	return nil, errors.New(message.NotFoundErrorString)
}

//File implements a file.
type File struct {
	parent Entry
	stat   stat.Stat

	// this is the minimal interface we need, as a 9p read can also seek
	io.ReadSeeker
}

//NewFile prepares a new File ready for use.
//
//The stat and ReadSeeker are expected to be prepared by the caller.
func NewFile(s stat.Stat, rs io.ReadSeeker) *File {
	f := &File{
		stat:       s,
		ReadSeeker: rs,
	}

	return f
}

//Parent returns the parent.
func (f *File) Parent() Entry {
	return f.parent
}

//Qid returns the qid.
func (f *File) Qid() qid.Qid {
	return f.stat.Qid
}

//Stat returns the stat.
func (f *File) Stat() stat.Stat {
	return f.stat
}

//Walk can only walk to itself for plain files.
//
//It is used for the side effect of creating another fid for this file.
func (f *File) Walk(wname string) (Entry, error) {
	if len(wname) != 0 {
		return nil, errors.New(message.WalkNoDirErrorString)
	}

	return f, nil
}

//Open the file for access.
//
//This is a nop here and to be overriden by embedders.
func (f *File) Open() error {
	return nil
}

A  => id/uidgid.go +25 -0
@@ 1,25 @@
// Package id provides utility functions to convert unix uid/gid to strings.
package id

import (
	"os/user"
	"strconv"
)

//UidToName returns the user name for an uid.
func UidToName(uid uint32) (string, error) {
	x, err := user.LookupId(strconv.Itoa(int(uid)))
	if err != nil {
		return "", err
	}
	return x.Name, nil
}

//GidToName returns the group name for an gid.
func GidToName(gid uint32) (string, error) {
	y, err := user.LookupGroupId(strconv.Itoa(int(gid)))
	if err != nil {
		return "", err
	}
	return y.Name, nil
}

A  => message/attach.go +95 -0
@@ 1,95 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/qid"
	"io"
)

/*TAttach establishes a new connection.

Fid will be mapped to the root of the file system.
Afid specifies a fid gained from a previous TAuth.
Uname identifies the user accessing the file system, Aname
selecting the file tree to access.

See also: http://man.cat-v.org/plan_9/5/attach
*/
type TAttach struct {
	Fid   fid.Fid
	Afid  fid.Fid
	Uname string
	Aname string
}

func (m *TAttach) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint32Encode(w, uint32(m.Afid))
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.StringEncode(w, m.Uname)
	if err != nil {
		return n1 + n2 + n3, err
	}

	n4, err := basic.StringEncode(w, m.Aname)
	if err != nil {
		return n1 + n2 + n3 + n4, err
	}

	return n1 + n2 + n3 + n4, nil
}

func (m *TAttach) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	af, n2, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	uname, n3, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	aname, n4, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2 + n3 + n4, err
	}

	m.Fid = fid.Fid(f)
	m.Afid = fid.Fid(af)
	m.Uname = uname
	m.Aname = aname

	return n1 + n2 + n3 + n4, err
}

/*RAttach is the answer to an attach request.

Qid is the servers representation of the file trees root.

See also: http://man.cat-v.org/plan_9/5/attach
*/
type RAttach struct {
	Qid qid.Qid
}

func (m *RAttach) encode(w io.Writer) (int64, error) {
	return m.Qid.Encode(w)
}

func (m *RAttach) decode(r io.Reader) (int64, error) {
	return m.Qid.Decode(r)
}

A  => message/attach_test.go +47 -0
@@ 1,47 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"bytes"
	"testing"
)

func TestTAttachEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TAttach{Fid: 1, Afid: 2, Uname: "3", Aname: "4"}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TAttach{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRAttachEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RAttach{Qid: qid.Qid{Type: 1, Version: 2, Path: 3}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RAttach{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/auth.go +92 -0
@@ 1,92 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/qid"
	"io"
)

/*TAuth facilitates authentication.

Afid is a new fid to be used for authentication.
Uname and Aname must be the same as for the following TAttach.

Authentication is performed by reading and writing the file referenced
by Afid, after receiving RAuth.
When complete Afid is used for authentication in TAttach.

The same Afid, Uname and Aname may be used for multiple TAttach messages.

If no authentication is required, RError is returned instead of
RAuth.

See also: http://man.cat-v.org/plan_9/5/attach
*/
type TAuth struct {
	Afid  fid.Fid
	Uname string
	Aname string
}

func (m *TAuth) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Afid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.StringEncode(w, m.Uname)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.StringEncode(w, m.Aname)
	if err != nil {
		return n1 + n2 + n3, err
	}

	return n1 + n2 + n3, nil
}

func (m *TAuth) decode(r io.Reader) (int64, error) {
	afid, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	uname, n2, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2, err
	}

	aname, n3, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	m.Afid = fid.Fid(afid)
	m.Uname = uname
	m.Aname = aname

	return n1 + n2 + n3, nil
}

/*RAuth is the response to a TAuth

Aqid is a file of type QidTypeAuth that can be read and
written using read and write messages to perform authentication.
The authentication protocol is not part of 9p.

See also: http://man.cat-v.org/plan_9/5/attach
*/
type RAuth struct {
	Aqid qid.Qid
}

func (m *RAuth) encode(w io.Writer) (int64, error) {
	return m.Aqid.Encode(w)
}

func (m *RAuth) decode(r io.Reader) (int64, error) {
	return m.Aqid.Decode(r)
}

A  => message/auth_test.go +47 -0
@@ 1,47 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"bytes"
	"testing"
)

func TestTAuthEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TAuth{Afid: 1, Uname: "a", Aname: "b"}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TAuth{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRAuthEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RAuth{Aqid: qid.Qid{Type: 1, Version: 2, Path: 3}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RAuth{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/clunk.go +57 -0
@@ 1,57 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"io"
)

/*TClunk requests to forget a fid.

The mapping on the server from fid to actual file should be removed,
not touching the actual file unless it was used OpenClose.

Regardless of the result of this call, the fid will not be valid anymore
and can be reused.

See also: http://man.cat-v.org/plan_9/5/clunk
*/
type TClunk struct {
	Fid fid.Fid
}

func (m *TClunk) encode(w io.Writer) (int64, error) {
	n, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n, err
	}

	return n, nil
}

func (m *TClunk) decode(r io.Reader) (int64, error) {
	f, n, err := basic.Uint32Decode(r)
	if err != nil {
		return n, err
	}

	m.Fid = fid.Fid(f)

	return n, err
}

/*RClunk signals a successful clunk.

After the fid is successfully clunked it can be reused.

See also: http://man.cat-v.org/plan_9/5/clunk
*/
type RClunk struct{}

func (m *RClunk) encode(w io.Writer) (int64, error) {
	return 0, nil
}

func (m *RClunk) decode(r io.Reader) (int64, error) {
	return 0, nil
}

A  => message/clunk_test.go +46 -0
@@ 1,46 @@
package message

import (
	"bytes"
	"testing"
)

func TestTClunkEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TClunk{Fid: 1}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TClunk{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRClunkEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RClunk{}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RClunk{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/consts.go +35 -0
@@ 1,35 @@
package message

type messageType uint8

// Internal constants for tag values of messages.
const (
	tversion messageType = iota + 100
	rversion
	tauth
	rauth
	tattach
	rattach
	terror // unused, good name for that ;)
	rerror
	tflush
	rflush
	twalk
	rwalk
	topen
	ropen
	tcreate
	rcreate
	tread
	rread
	twrite
	rwrite
	tclunk
	rclunk
	tremove
	rremove
	tstat
	rstat
	twstat
	rwstat
)

A  => message/create.go +141 -0
@@ 1,141 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/qid"
	"github.com/rbns/neinp/stat"
	"io"
)

/*TCreate asks the file server to create a new file.

The file is to be created in the directory represented by Fid, with the supplied Name.
Write permission is required on the directory. The owner will be implied by the
file systems used. The group is the same as the directories.
Permissions for a file will be set to

	Perm & 0666 | DPerm & 0666

or

	Perm & 0777 | DPerm & 0777

if a directory (DPerm being the permissions of the parent directory).

The created file is opened with Mode, and Fid will represent the new file.
Mode is not checked to fulfill the permissions of Perm.

Directories are created by setting the DirModeDir bit
in Perm.

Names "." and ".." are forbidden.

Fid must not be in use already.

Creating a file with a name already in use, will truncate the existing file.

See also: http://man.cat-v.org/plan_9/5/open
*/
type TCreate struct {
	Fid  uint32
	Name string
	Perm stat.Mode
	Mode OpenMode
}

func (m *TCreate) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, m.Fid)
	if err != nil {
		return n1, err
	}

	n2, err := basic.StringEncode(w, m.Name)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.Uint32Encode(w, uint32(m.Perm))
	if err != nil {
		return n1 + n2 + n3, err
	}

	n4, err := basic.Uint8Encode(w, uint8(m.Mode))
	if err != nil {
		return n1 + n2 + n3 + n4, err
	}

	return n1 + n2 + n3 + n4, nil
}

func (m *TCreate) decode(r io.Reader) (int64, error) {
	fid, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	name, n2, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2, err
	}

	perm, n3, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	mod, n4, err := basic.Uint8Decode(r)
	if err != nil {
		return n1 + n2 + n3 + n4, err
	}

	m.Fid = fid
	m.Name = name
	m.Perm = stat.Mode(perm)
	m.Mode = OpenMode(mod)

	return n1 + n2 + n3 + n4, err
}

/*RCreate is the answer for a successful create.

Qid is the new file as seen by the server.

Iounit may be zero. If not, it is the number of bytes guaranteed
to succeed to be read or written in one message.

See also: http://man.cat-v.org/plan_9/5/open
*/
type RCreate struct {
	Qid    qid.Qid
	Iounit uint32
}

func (m *RCreate) encode(w io.Writer) (int64, error) {
	n1, err := m.Qid.Encode(w)
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint32Encode(w, m.Iounit)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *RCreate) decode(r io.Reader) (int64, error) {
	n1, err := m.Qid.Decode(r)
	if err != nil {
		return n1, err
	}

	iounit, n2, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	m.Iounit = iounit

	return n1 + n2, nil
}

A  => message/create_test.go +47 -0
@@ 1,47 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"bytes"
	"testing"
)

func TestTCreateEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TCreate{Fid: 1, Name: "a", Perm: 2, Mode: 3}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TCreate{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRCreateEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RCreate{Qid: qid.Qid{1, 2, 3}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RCreate{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/doc.go +4 -0
@@ 1,4 @@
/*Package message contains types for 9p messages.

The types defined here can encode/decode themselves to the 9p wire representation.*/
package message

A  => message/error.go +53 -0
@@ 1,53 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"io"
)

/*9p defined error strings.
See: https://github.com/0intro/plan9/blob/7524062cfa4689019a4ed6fc22500ec209522ef0/sys/src/lib9p/srv.c#L10 */
const (
	BadAttachErrorString    = "unknown specifier in attach"
	BadOffsetErrorString    = "bad offset"
	BadCountErrorString     = "bad count"
	BotchErrorString        = "9P protocol botch"
	CreateNonDirErrorString = "create in non-directory"
	DupFidErrorString       = "duplicate fid"
	DupTagErrorString       = "duplicate tag"
	IsDirErrorString        = "is a directory"
	NoCreateErrorString     = "create prohibited"
	NoMemErrorString        = "out of memory"
	NoRemoveErrorString     = "remove prohibited"
	NoStatErrorString       = "stat prohibited"
	NotFoundErrorString     = "file not found"
	NoWriteErrorString      = "write prohibited"
	NoWstatErrorString      = "wstat prohibited"
	PermErrorString         = "permission denied"
	UnknownFidErrorString   = "unknown fid"
	BadDirErrorString       = "bad directory in wstat"
	WalkNoDirErrorString    = "walk in non-directory"
)

/*RError is the response to failed requests.

It usually contains one of the standard *ErrorString constants.

See also: http://man.cat-v.org/plan_9/5/error */
type RError struct {
	Ename string
}

func (m *RError) encode(w io.Writer) (int64, error) {
	return basic.StringEncode(w, m.Ename)
}

func (m *RError) decode(r io.Reader) (int64, error) {
	ename, n, err := basic.StringDecode(r)
	if err != nil {
		return n, err
	}

	m.Ename = ename
	return n, err
}

A  => message/error_test.go +26 -0
@@ 1,26 @@
package message

import (
	"bytes"
	"testing"
)

func TestRErrorEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RError{Ename: "deadbeef"}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RError{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/flush.go +48 -0
@@ 1,48 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"io"
)

/*TFlush signals the server that it should abort processing a message.

Instances of Server directly handle these message type by running the cancel function
of the context for the message which should be aborted. It is here for usage in
client implementations.

Unlike the real world, flushing never fails.

See also: http://man.cat-v.org/inferno/5/flush
*/
type TFlush struct {
	Oldtag uint16
}

func (m *TFlush) encode(w io.Writer) (int64, error) {
	return basic.Uint16Encode(w, m.Oldtag)
}

func (m *TFlush) decode(r io.Reader) (int64, error) {
	oldtag, n, err := basic.Uint16Decode(r)
	if err != nil {
		return n, err
	}

	m.Oldtag = oldtag
	return n, err
}

/*RFlush is the answer to a TFlush and signals that the flush has finished.

See also: http://man.cat-v.org/plan_9/5/flush */
type RFlush struct {
}

func (m *RFlush) encode(w io.Writer) (int64, error) {
	return 0, nil
}

func (m *RFlush) decode(r io.Reader) (int64, error) {
	return 0, nil
}

A  => message/flush_test.go +46 -0
@@ 1,46 @@
package message

import (
	"bytes"
	"testing"
)

func TestTFlushEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TFlush{Oldtag: 1}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TFlush{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRFlushEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RFlush{}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RFlush{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/message.go +223 -0
@@ 1,223 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"bytes"
	"fmt"
	"io"
)

//HeaderSize is the length of the size, type and tag headers in bytes.
const HeaderSize uint64 = 4 + 1 + 2

//An encoder writes itself as 9p representation to a io.Writer
type encoder interface {
	encode(w io.Writer) (int64, error)
}

//A decoder reads a 9p representation of itself from a io.Reader
type decoder interface {
	decode(r io.Reader) (int64, error)
}

//Content is implemented by the message types containing the real information.
type Content interface {
	encoder
	decoder
}

/*Message is the general message type.

It wraps implementations of interface Content.
*/
type Message struct {
	typ     messageType
	Tag     uint16
	Content Content
}

// get the matching messageType for a Content
func contentType(c Content) (messageType, error) {
	switch c.(type) {
	case *RVersion:
		return rversion, nil
	case *RAuth:
		return rauth, nil
	case *RAttach:
		return rattach, nil
	case *RError:
		return rerror, nil
	case *RFlush:
		return rflush, nil
	case *RWalk:
		return rwalk, nil
	case *ROpen:
		return ropen, nil
	case *RCreate:
		return rcreate, nil
	case *RRead:
		return rread, nil
	case *RWrite:
		return rwrite, nil
	case *RClunk:
		return rclunk, nil
	case *RRemove:
		return rremove, nil
	case *RStat:
		return rstat, nil
	case *RWstat:
		return rwstat, nil
	case *TVersion:
		return tversion, nil
	case *TAuth:
		return tauth, nil
	case *TAttach:
		return tattach, nil
	case *TFlush:
		return tflush, nil
	case *TWalk:
		return twalk, nil
	case *TOpen:
		return topen, nil
	case *TCreate:
		return tcreate, nil
	case *TRead:
		return tread, nil
	case *TWrite:
		return twrite, nil
	case *TClunk:
		return tclunk, nil
	case *TRemove:
		return tremove, nil
	case *TStat:
		return tstat, nil
	case *TWstat:
		return twstat, nil
	default:
		return 0, fmt.Errorf("unknown content type: %T", c)
	}
}

/*New creates a new Message with a given tag and Content.*/
func New(tag uint16, content Content) (Message, error) {
	typ, err := contentType(content)
	if err != nil {
		return Message{}, err
	}
	return Message{typ: typ, Tag: tag, Content: content}, nil
}

//Response returns a new Message prepared for using as reply.
func (m Message) Response() Message {
	return Message{Tag: m.Tag}
}

/*Decode reads a message from a io.Reader.

It returns the count of read bytes and an error.*/
func (m *Message) Decode(r io.Reader) (int64, error) {
	size, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	typ, n2, err := basic.Uint8Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	tag, n3, err := basic.Uint16Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	switch messageType(typ) {
	case tversion:
		m.Content = &TVersion{}
	case tauth:
		m.Content = &TAuth{}
	case tattach:
		m.Content = &TAttach{}
	case tflush:
		m.Content = &TFlush{}
	case twalk:
		m.Content = &TWalk{}
	case topen:
		m.Content = &TOpen{}
	case tcreate:
		m.Content = &TCreate{}
	case tread:
		m.Content = &TRead{}
	case twrite:
		m.Content = &TWrite{}
	case tclunk:
		m.Content = &TClunk{}
	case tremove:
		m.Content = &TRemove{}
	case tstat:
		m.Content = &TStat{}
	case twstat:
		m.Content = &TWstat{}
	default:
		return n1 + n2 + n3, fmt.Errorf("unknown message type: %o", typ)
	}

	n4, err := m.Content.decode(r)
	if err != nil {
		return n1 + n2 + n3 + n4, err
	}

	if uint32(n1+n2+n3+n4) != size {
		return n1 + n2 + n3 + n4, fmt.Errorf("short read: %v read, %v expected", n1+n2+n3+n4, size)
	}

	m.typ = messageType(typ)
	m.Tag = tag

	return n1 + n2 + n3 + n4, nil
}

/*Encode writes a Message to an io.Writer.

It returns the count of bytes written and an error.*/
func (m *Message) Encode(w io.Writer) (int64, error) {
	var buf bytes.Buffer

	typ, err := contentType(m.Content)
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint8Encode(&buf, uint8(typ))
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint16Encode(&buf, m.Tag)
	if err != nil {
		return 0, err
	}

	_, err = m.Content.encode(&buf)
	if err != nil {
		return 0, err
	}

	size := uint32(4 + buf.Len())

	n1, err := basic.Uint32Encode(w, size)
	if err != nil {
		return n1, err
	}

	n2, err := buf.WriteTo(w)
	if err != nil {
		return n1 + n2, err
	}

	if uint32(n1+n2) != size {
		return n1 + n2, fmt.Errorf("short write: %v written %v expected", n1+n2, size)
	}

	return n1 + n2, nil
}

A  => message/message_test.go +78 -0
@@ 1,78 @@
package message

import (
	"bytes"
	"testing"
)

var contentTypeTests = []struct {
	c   Content
	typ messageType
}{
	{c: &TVersion{}, typ: tversion},
	{c: &RVersion{}, typ: rversion},
	{c: &TAuth{}, typ: tauth},
	{c: &RAuth{}, typ: rauth},
	{c: &TAttach{}, typ: tattach},
	{c: &RAttach{}, typ: rattach},
	{c: &RError{}, typ: rerror},
	{c: &TFlush{}, typ: tflush},
	{c: &RFlush{}, typ: rflush},
	{c: &TWalk{}, typ: twalk},
	{c: &RWalk{}, typ: rwalk},
	{c: &TOpen{}, typ: topen},
	{c: &ROpen{}, typ: ropen},
	{c: &TCreate{}, typ: tcreate},
	{c: &RCreate{}, typ: rcreate},
	{c: &TRead{}, typ: tread},
	{c: &RRead{}, typ: rread},
	{c: &TWrite{}, typ: twrite},
	{c: &RWrite{}, typ: rwrite},
	{c: &TClunk{}, typ: tclunk},
	{c: &RClunk{}, typ: rclunk},
	{c: &TRemove{}, typ: tremove},
	{c: &RRemove{}, typ: rremove},
	{c: &TStat{}, typ: tstat},
	{c: &RStat{}, typ: rstat},
	{c: &TWstat{}, typ: twstat},
	{c: &RWstat{}, typ: rwstat},
}

func TestContentType(t *testing.T) {
	for _, v := range contentTypeTests {
		x, err := contentType(v.c)
		if err != nil {
			t.Error(err)
		}

		if v.typ != x {
			t.Logf("%T %v %v", v.c, v.typ, x)
			t.Fail()
		}
	}
}

func TestEncodeDecode(t *testing.T) {
	mIn, err := New(0x1, &TVersion{Version: "9P2000.test", Msize: 1234})
	if err != nil {
		t.Error(err)
	}

	var buf bytes.Buffer

	n1, err := mIn.Encode(&buf)
	if err != nil {
		t.Error(err)
	}

	var mOut Message

	n2, err := mOut.Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if n1 != n2 {
		t.Fail()
	}
}

A  => message/open.go +168 -0
@@ 1,168 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/qid"
	"io"
	"os"
)

//OpenMode is used to signalize the mode a file should be opened with in a TOpenMessage.
type OpenMode uint8

const (
	// Read opens a file for reading.
	Read OpenMode = 0

	// Write opens a file for writing
	Write OpenMode = 1

	// ReadWrite opens a file for random access.
	ReadWrite OpenMode = 2

	// Exec opens a file for reading but also checks execute permission.
	Exec OpenMode = 3

	// Trunc is to be OR'ed in (except for OpenExec), truncates file before opening.
	Trunc OpenMode = 16

	// CloseExec is to be OR'ed in, closes on execution.
	CloseExec OpenMode = 32

	// Close is to be OR'ed in, remove on closing.
	Close OpenMode = 64
)

// OsMode convertes a OpenMode to os.Open mode.
//
// Converted modes: Read, Write, ReadWrite, Trunc
func OsMode(mode OpenMode) int {
	flg := 0
	if mode&Read == Read {
		flg |= os.O_RDONLY
	}
	if mode&Write == Write {
		flg |= os.O_WRONLY
	}
	if mode&ReadWrite == ReadWrite {
		flg |= os.O_RDWR
	}
	if mode&Trunc == Trunc {
		flg |= os.O_TRUNC
	}

	return flg
}

// NeinMode convertes a os.Open mode to OpenMode.
//
// Converted modes: os.O_RDONLY, os.O_WRONLY, os.O_RDWR, os.O_TRUNC
func NeinMode(osMode int) OpenMode {
	var mode OpenMode
	if osMode&os.O_RDONLY == os.O_RDONLY {
		mode |= Read
	}
	if osMode&os.O_WRONLY == os.O_WRONLY {
		mode |= Write
	}
	if osMode&os.O_RDWR == os.O_RDWR {
		mode |= ReadWrite
	}
	if osMode&os.O_TRUNC == os.O_TRUNC {
		mode |= Trunc
	}

	return mode
}

/*TOpen is a 9P open request message.

Open requests ask the file server to prepare a fid to be used
with read and write requests, checking the permissions before.

Mode determines the type of IO and can be one of the constants
OpenRead, OpenWrite, OpenReadWrite, OpenExec, additionally
OpenTrunc, OpenCloseExec and OpenClose or'ed into Mode.

See also: http://man.cat-v.org/plan_9/5/open
*/
type TOpen struct {
	Fid  fid.Fid
	Mode OpenMode
}

func (m *TOpen) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint8Encode(w, uint8(m.Mode))
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *TOpen) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	mod, n2, err := basic.Uint8Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	m.Fid = fid.Fid(f)
	m.Mode = OpenMode(mod)

	return n1 + n2, nil
}

/*ROpen is the reply to a open request.

Qid is the servers idea of the opened file accessed.

Iounit may be zero. If not, it's the number of bytes which can
be read or written in a single call.

See also: http://man.cat-v.org/plan_9/5/open
*/
type ROpen struct {
	Qid    qid.Qid
	Iounit uint32
}

func (m *ROpen) encode(w io.Writer) (int64, error) {
	n1, err := m.Qid.Encode(w)
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint32Encode(w, m.Iounit)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *ROpen) decode(r io.Reader) (int64, error) {
	n1, err := m.Qid.Decode(r)
	if err != nil {
		return n1, err
	}

	iounit, n2, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	m.Iounit = iounit

	return n1 + n2, nil
}

A  => message/open_test.go +47 -0
@@ 1,47 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"bytes"
	"testing"
)

func TestTOpenEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TOpen{Fid: 1, Mode: 2}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TOpen{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestROpenEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &ROpen{Qid: qid.Qid{Type: 1, Version: 2, Path: 3}, Iounit: 4}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &ROpen{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/read.go +121 -0
@@ 1,121 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"io"
)

/*TRead requests data from a file.

The file is identified by Fid, which must be opened before reading.
Count bytes are read from the file, starting at Offset bytes after the
beginning of the file.

The count Field in the reply indicates the number of bytes
returned.  This may be less than the requested amount.  If
the Offset field is greater than or equal to the number of
bytes in the file, a Count of zero will be returned.

For directories, the read request message must have
Offset equal to zero or the value of Offset in the previous
read on the directory, plus the number of bytes returned in
the previous read. In other words, seeking other than to
the beginning is illegal in a directory.

See also: http://man.cat-v.org/inferno/5/read
*/
type TRead struct {
	Fid    fid.Fid
	Offset uint64
	Count  uint32
}

func (m *TRead) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint64Encode(w, m.Offset)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.Uint32Encode(w, m.Count)
	if err != nil {
		return n1 + n2 + n3, err
	}

	return n1 + n2 + n3, nil
}

func (m *TRead) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	offset, n2, err := basic.Uint64Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	count, n3, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	m.Fid = fid.Fid(f)
	m.Offset = offset
	m.Count = count

	return n1 + n2 + n3, nil
}

/*RRead is the reply to a TRead.

The data read is stored in Data, Count being the length
of Data in bytes.

For directories, read returns an integral number of directory
entries exactly as in RStat, one for each member of the directory.
To help with this StatReader can be used.

See also: http://man.cat-v.org/inferno/5/read
*/
type RRead struct {
	Count uint32
	Data  []byte
}

func (m *RRead) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, m.Count)
	if err != nil {
		return n1, err
	}

	n2, err := w.Write(m.Data)
	if err != nil {
		return n1 + int64(n2), err
	}

	return n1 + int64(n2), nil
}

func (m *RRead) decode(r io.Reader) (int64, error) {
	count, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	m.Count = count
	m.Data = make([]byte, count)

	n2, err := r.Read(m.Data)
	if err != nil {
		return n1 + int64(n2), err
	}

	return n1 + int64(n2), nil
}

A  => message/read_test.go +46 -0
@@ 1,46 @@
package message

import (
	"bytes"
	"testing"
)

func TestTReadEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TRead{Fid: 1, Offset: 2, Count: 3}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TRead{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRReadEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RRead{Count: 4, Data: []byte{0xDE, 0xAD, 0xBE, 0xEF}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RRead{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if a.Count != b.Count || !bytes.Equal(a.Data, b.Data) {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/remove.go +51 -0
@@ 1,51 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"io"
)

/*TRemove signals the server to remove the file and clunk the Fid.

The Fid is clunked even if the remove fails. The request will fail
if the client doesn't have the permission to write the parent directory.

See also: http://man.cat-v.org/inferno/5/remove
*/
type TRemove struct {
	Fid fid.Fid
}

func (m *TRemove) encode(w io.Writer) (int64, error) {
	n, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n, err
	}

	return n, nil
}

func (m *TRemove) decode(r io.Reader) (int64, error) {
	f, n, err := basic.Uint32Decode(r)
	if err != nil {
		return n, err
	}

	m.Fid = fid.Fid(f)
	return n, nil
}

/*RRemove is the answer for a successful TRemove request.

See also: http://man.cat-v.org/inferno/5/remove
*/
type RRemove struct{}

func (m *RRemove) encode(w io.Writer) (int64, error) {
	return 0, nil
}

func (m *RRemove) decode(r io.Reader) (int64, error) {
	return 0, nil
}

A  => message/remove_test.go +46 -0
@@ 1,46 @@
package message

import (
	"bytes"
	"testing"
)

func TestTRemoveEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TRemove{Fid: 1}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TRemove{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRRemoveEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RRemove{}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RRemove{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/stat.go +94 -0
@@ 1,94 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/stat"
	"bytes"
	"io"
)

/*TStat requests information about file.

The file for which information is requested is identified by Fid.

See also: http://man.cat-v.org/inferno/5/stat
*/
type TStat struct {
	Fid fid.Fid
}

func (m *TStat) encode(w io.Writer) (int64, error) {
	n, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n, err
	}

	return n, nil
}

func (m *TStat) decode(r io.Reader) (int64, error) {
	f, n, err := basic.Uint32Decode(r)
	if err != nil {
		return n, err
	}

	m.Fid = fid.Fid(f)
	return n, nil
}

/*RStat contains a machine-independent directory entry, Stat.

See also: http://man.cat-v.org/inferno/5/stat
*/
type RStat struct {
	Stat stat.Stat
}

func (m *RStat) encode(w io.Writer) (int64, error) {
	var buf bytes.Buffer

	_, err := m.Stat.Encode(&buf)
	if err != nil {
		return 0, err
	}

	/* From plan9 manual:
	BUGS
	To make the contents of a directory, such as returned by
	read(9P), easy to parse, each directory entry begins with a
	size field.  For consistency, the entries in Twstat and
	Rstat messages also contain their size, which means the size
	appears twice.  For example, the Rstat message is formatted
	as ``(4+1+2+2+n)[4] Rstat tag[2] n[2] (n-2)[2] type[2]
	dev[4]...,'' where n is the value returned by convD2M.
	*/

	size := uint16(buf.Len())
	n1, err := basic.Uint16Encode(w, size)
	if err != nil {
		return n1, err
	}

	n2, err := buf.WriteTo(w)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *RStat) decode(r io.Reader) (int64, error) {
	// read the size but don't store it. see Rstat.encode
	_, n1, err := basic.Uint16Decode(r)
	if err != nil {
		return n1, err
	}

	n2, err := m.Stat.Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

A  => message/stat_test.go +69 -0
@@ 1,69 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"github.com/rbns/neinp/stat"
	"bytes"
	"testing"
	"time"
)

func TestStatEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &stat.Stat{Typ: 1, Dev: 2, Qid: qid.Qid{3, 4, 5}, Mode: 6, Atime: time.Unix(1234567, 0), Mtime: time.Unix(1234567, 0), Length: 6, Name: "deadbeef", Uid: "foo", Gid: "bar", Muid: "baz"}
	_, err := a.Encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &stat.Stat{}
	_, err = b.Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestTStatEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TStat{Fid: 1}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TStat{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRStatEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RStat{Stat: stat.Stat{Typ: 1, Dev: 2, Qid: qid.Qid{3, 4, 5}, Mode: 6, Atime: time.Unix(1234567, 0), Mtime: time.Unix(1234567, 0), Length: 6, Name: "deadbeef", Uid: "foo", Gid: "bar", Muid: "baz"}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RStat{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/version.go +103 -0
@@ 1,103 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"io"
)

/*TVersion initializes a new connection.

Msize is the maximum message size the client will ever create, and
includes _all_ protocol data (also 9p type, tag and size fields).
As the contents of these fields are handled by neinp.Server, the msize
has to include the length of the size (4 octets) type (1 octet) and tag (2 octets) fields,
even if not visible to neinp.P2000 implementers. The msize returned with RVersion
will be read by neinp.Server to setup a io.LimitReader for message reception.

Version identifies the level of the protocol, it must always start with
"9P" (though not enforced here).

See also: http://man.cat-v.org/inferno/5/version
*/
type TVersion struct {
	Msize   uint32
	Version string
}

func (m *TVersion) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, m.Msize)
	if err != nil {
		return n1, err
	}

	n2, err := basic.StringEncode(w, m.Version)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *TVersion) decode(r io.Reader) (int64, error) {
	msize, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	version, n2, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2, err
	}

	m.Msize = msize
	m.Version = version
	return n1 + n2, nil
}

/*RVersion is the servers reply to a TVersionMessage.

Msize must be lower or equal of what the client requested.

Version may be set to the clients version string or a string
of an earlier protocol version. If the server doesn't understand
the requested version, it replies with the version string "unknown".

A version request starts a new session, so any remaining I/O operations
are aborted and allocated fids are clunked.

See also: http://man.cat-v.org/inferno/5/version
*/
type RVersion struct {
	Msize   uint32
	Version string
}

func (m *RVersion) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, m.Msize)
	if err != nil {
		return n1, err
	}

	n2, err := basic.StringEncode(w, m.Version)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (m *RVersion) decode(r io.Reader) (int64, error) {
	msize, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	version, n2, err := basic.StringDecode(r)
	if err != nil {
		return n1 + n2, err
	}

	m.Msize = msize
	m.Version = version
	return n1 + n2, nil
}

A  => message/version_test.go +46 -0
@@ 1,46 @@
package message

import (
	"bytes"
	"testing"
)

func TestTVersionEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TVersion{Msize: 1, Version: "deadbeef"}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TVersion{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRVersionEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RVersion{Msize: 1, Version: "deadbeef"}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RVersion{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => message/walk.go +151 -0
@@ 1,151 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/qid"
	"io"
)

/*TWalk asks to walk down a directory hierarchy.

Fid is an already existing fid, Newfid is a proposed new
fid that the client wants to use for the result of the walk.
Wname are successive path elements to walk to, and may be of
length zero. Fid must be a directory, only if Wname is of length
zero. In this case Newfid will represent the same file as Fid.

Fid must be valid and not already opened by a TOpen or TCreate
request.

Wname may be "..", which represents the parent directory,
"." is not used and forbidden, zero length Wname is used for this.

Wname should have a maximum length of 16 elements (this limit is
not enforced here).

See also: http://man.cat-v.org/inferno/5/walk
*/
type TWalk struct {
	Fid    fid.Fid
	Newfid fid.Fid
	Wname  []string
}

func (m *TWalk) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint32Encode(w, uint32(m.Newfid))
	if err != nil {
		return n1 + n2, err
	}

	nwname := uint16(len(m.Wname))
	n3, err := basic.Uint16Encode(w, nwname)
	if err != nil {
		return n1 + n2 + n3, err
	}

	var n4 int64
	for _, v := range m.Wname {
		n, err := basic.StringEncode(w, v)
		n4 += n
		if err != nil {
			return n1 + n2 + n3 + n4, err
		}
	}

	return n1 + n2 + n3 + n4, err
}

func (m *TWalk) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	newf, n2, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	nwname, n3, err := basic.Uint16Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	wnames := make([]string, nwname)

	var n4 int64
	for i := uint16(0); i < nwname; i++ {
		wname, n, err := basic.StringDecode(r)
		n4 += n
		if err != nil {
			return n1 + n2 + n3 + n4, err
		}
		wnames[i] = wname
	}

	m.Fid = fid.Fid(f)
	m.Newfid = fid.Fid(newf)
	m.Wname = wnames

	return n1 + n2 + n3 + n4, nil
}

/*RWalk is the response to a TWalk.

Wqid contains the qids of the Wnames in a successfull walk.
If the walk fails at the first element an error is returned,
if it fails at a later point, the qids of the successfully walked
elements are returned (len(Wqid) < len(Wname).

See also: http://man.cat-v.org/inferno/5/walk
*/
type RWalk struct {
	Wqid []qid.Qid
}

func (m *RWalk) encode(w io.Writer) (int64, error) {
	nwqid := uint16(len(m.Wqid))
	n1, err := basic.Uint16Encode(w, nwqid)
	if err != nil {
		return n1, err
	}

	var n2 int64
	for _, v := range m.Wqid {
		n, err := v.Encode(w)
		n2 += n
		if err != nil {
			return n1 + n2, err
		}
	}

	return n1 + n2, nil
}

func (m *RWalk) decode(r io.Reader) (int64, error) {
	nwqid, n1, err := basic.Uint16Decode(r)
	if err != nil {
		return n1, err
	}

	wqids := make([]qid.Qid, nwqid)

	var n2 int64
	for i := uint16(0); i < nwqid; i++ {
		n, err := wqids[i].Decode(r)
		n2 += n
		if err != nil {
			return n1 + n2, err
		}
	}

	m.Wqid = wqids

	return n1 + n2, nil
}

A  => message/walk_test.go +63 -0
@@ 1,63 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"bytes"
	"strings"
	"testing"
)

func TestTWalkEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TWalk{Fid: 1, Newfid: 2, Wname: []string{"dead", "beef"}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TWalk{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if a.Fid != b.Fid || a.Newfid != b.Newfid {
		t.Log(a, b)
		t.Fail()
	}

	if len(a.Wname) != len(b.Wname) {
		t.Fail()
	}

	if strings.Join(a.Wname, "") != strings.Join(b.Wname, "") {
		t.Fail()
	}
}

func TestRWalkEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RWalk{Wqid: []qid.Qid{qid.Qid{1, 2, 3}, qid.Qid{4, 5, 6}}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RWalk{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if len(a.Wqid) != len(b.Wqid) {
		t.Logf("wrong wqid len, a: %v, b: %v", len(a.Wqid), len(b.Wqid))
		t.Fail()
	} else {
		for i, v := range a.Wqid {
			if b.Wqid[i] != v {
				t.Logf("wqids differ, a.Wqid[i]: %v, b.Wqid[i]: %v", v, b.Wqid[i])
				t.Fail()
			}
		}
	}
}

A  => message/write.go +109 -0
@@ 1,109 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"io"
)

/*TWrite requests data to be written to a file.

Write Count bytes of Data at Offset bytes from the beginning
of the file. The file which is represented by Fid must be
opened for writing before this request, when opened in append
mode, the write will be to the end of the file, ignoring Offset.
Writes to directories are forbidden.

See also: http://man.cat-v.org/inferno/5/read
*/
type TWrite struct {
	Fid    fid.Fid
	Offset uint64
	Count  uint32
	Data   []byte
}

func (m *TWrite) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint64Encode(w, m.Offset)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.Uint32Encode(w, m.Count)
	if err != nil {
		return n1 + n2 + n3, err
	}

	n4, err := w.Write(m.Data)
	if err != nil {
		return n1 + n2 + n3 + int64(n4), err
	}

	return n1 + n2 + n3 + int64(n4), nil
}

func (m *TWrite) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	offset, n2, err := basic.Uint64Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	count, n3, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	m.Fid = fid.Fid(f)
	m.Offset = offset
	m.Count = count

	m.Data = make([]byte, count)

	n4, err := r.Read(m.Data)
	if err != nil {
		return n1 + n2 + n3 + int64(n4), err
	}

	return n1 + n2 + n3 + int64(n4), nil
}

/*RWrite is the reply to a TWrite.

Count is the number of bytes written. It usually should be
the same as requested.

See also: http://man.cat-v.org/inferno/5/read
*/
type RWrite struct {
	Count uint32
}

func (m *RWrite) encode(w io.Writer) (int64, error) {
	n, err := basic.Uint32Encode(w, m.Count)
	if err != nil {
		return n, err
	}

	return n, nil
}

func (m *RWrite) decode(r io.Reader) (int64, error) {
	count, n, err := basic.Uint32Decode(r)
	if err != nil {
		return n, err
	}

	m.Count = count

	return n, nil
}

A  => message/write_test.go +51 -0
@@ 1,51 @@
package message

import (
	"bytes"
	"testing"
)

func TestTWriteEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TWrite{Fid: 1, Offset: 2, Count: 4, Data: []byte{0xDE, 0xAD, 0xBE, 0xEF}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TWrite{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if a.Fid != b.Fid || a.Offset != b.Offset || a.Count != b.Count {
		t.Log(a, b)
		t.Fail()
	}

	if !bytes.Equal(a.Data, b.Data) {
		t.Log(a, b)
		t.Log(a.Data, b.Data)
		t.Fail()
	}
}

func TestRWriteEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RWrite{Count: 1}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RWrite{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Fail()
	}
}

A  => message/wstat.go +109 -0
@@ 1,109 @@
package message

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/fid"
	"github.com/rbns/neinp/stat"
	"bytes"
	"io"
)

/*TWstat requests to change some of the file status information.

Name can be changed by anyone with write permissions for the parent directory
and if the name isn't already in use in the parent directory.

The length of the file can be changed by anyone with write
permissions and will reflect in the actual length of the file.
The length of directories can't be set to anything than zero.

Servers may refuse length changes.

Mode and mtime may be changed by the owner or group.

All permission and mode bits can be changed, except for
the directory bit.

The gid can be changed by the owner if he is member of the
new group or by the group leader of both the current and new group.

The other data can't be changed using wstat. It is illegal to change
the owner of a file.

Changes are performed atomic, either all changes are applied with the
call succeeding, or none.

Modification of properties can be avoided using zero values:
Strings of length zero and the maximum value for integers.

If every property is set to such a zero value, it is to be handled
as a request to sync the file to stable storage.

See also: http://man.cat-v.org/inferno/5/stat
*/
type TWstat struct {
	Fid  fid.Fid
	Stat stat.Stat
}

func (m *TWstat) encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint32Encode(w, uint32(m.Fid))
	if err != nil {
		return n1, err
	}

	var buf bytes.Buffer

	_, err = m.Stat.Encode(&buf)
	if err != nil {
		return n1, err
	}

	size := uint16(buf.Len())
	n2, err := basic.Uint16Encode(w, size)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := m.Stat.Encode(w)
	if err != nil {
		return n1 + n2 + n3, err
	}

	return n1 + n2 + n3, err
}

func (m *TWstat) decode(r io.Reader) (int64, error) {
	f, n1, err := basic.Uint32Decode(r)
	if err != nil {
		return n1, err
	}

	_, n2, err := basic.Uint16Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := m.Stat.Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	m.Fid = fid.Fid(f)

	return n1 + n2 + n3, nil
}

/*RWstat is the reply for a successfull TWstat request.

See also: http://man.cat-v.org/inferno/5/stat
*/
type RWstat struct{}

func (m *RWstat) encode(w io.Writer) (int64, error) {
	return 0, nil
}

func (m *RWstat) decode(r io.Reader) (int64, error) {
	return 0, nil
}

A  => message/wstat_test.go +49 -0
@@ 1,49 @@
package message

import (
	"github.com/rbns/neinp/qid"
	"github.com/rbns/neinp/stat"
	"bytes"
	"testing"
	"time"
)

func TestTWstatEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &TWstat{Fid: 1, Stat: stat.Stat{Typ: 1, Dev: 2, Qid: qid.Qid{3, 4, 5}, Mode: 6, Atime: time.Unix(1234567, 0), Mtime: time.Unix(1234567, 0), Length: 6, Name: "deadbeef", Uid: "foo", Gid: "bar", Muid: "baz"}}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &TWstat{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

func TestRWstatEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &RWstat{}
	_, err := a.encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &RWstat{}
	_, err = b.decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => nopfs.go +67 -0
@@ 1,67 @@
package neinp

import (
	"github.com/rbns/neinp/message"
	"context"
	"errors"
)

// NopP2000 is a dummy impementation of interface P2000, returning error for every call.
// It can be embedded into own implementations if some functions aren't required.
// For a working mount (with p9p 9pfuse or linux kernel module), at least Version, Attach,
// Stat, Walk, Open, Read, and Clunk need to be implemented.
type NopP2000 struct{}

var errNop = errors.New("not implemented")

func (f *NopP2000) Version(context.Context, message.TVersion) (message.RVersion, error) {
	return message.RVersion{}, errNop
}

func (f *NopP2000) Auth(context.Context, message.TAuth) (message.RAuth, error) {
	return message.RAuth{}, errNop
}

func (f *NopP2000) Attach(context.Context, message.TAttach) (message.RAttach, error) {
	return message.RAttach{}, errNop
}

func (f *NopP2000) Walk(context.Context, message.TWalk) (message.RWalk, error) {
	return message.RWalk{}, errNop
}

func (f *NopP2000) Open(context.Context, message.TOpen) (message.ROpen, error) {
	return message.ROpen{}, errNop
}

func (f *NopP2000) Create(context.Context, message.TCreate) (message.RCreate, error) {
	return message.RCreate{}, errNop
}

func (f *NopP2000) Read(context.Context, message.TRead) (message.RRead, error) {
	return message.RRead{}, errNop
}

func (f *NopP2000) Write(context.Context, message.TWrite) (message.RWrite, error) {
	return message.RWrite{}, errNop
}

func (f *NopP2000) Clunk(context.Context, message.TClunk) (message.RClunk, error) {
	return message.RClunk{}, errNop
}

func (f *NopP2000) Remove(context.Context, message.TRemove) (message.RRemove, error) {
	return message.RRemove{}, errNop
}

func (f *NopP2000) Stat(context.Context, message.TStat) (message.RStat, error) {
	return message.RStat{}, errNop
}

func (f *NopP2000) Wstat(context.Context, message.TWstat) (message.RWstat, error) {
	return message.RWstat{}, errNop
}

func (f *NopP2000) Close() error {
	return nil
}

A  => qid/consts.go +30 -0
@@ 1,30 @@
package qid

//Type represents the type this qid symbolizes.
type Type uint8

const (
	// TypeFile is a plain file
	TypeFile Type = 0x00

	// TypeSymlink is a symbolic link
	TypeSymlink Type = 0x02

	// TypeTmp is a non-backed-up file
	TypeTmp Type = 0x04

	// TypeAuth is an authentication file
	TypeAuth Type = 0x08

	// TypeMount is a mounted channel
	TypeMount Type = 0x10

	// TypeExcl is a exclusive use file
	TypeExcl Type = 0x20

	// TypeAppend is a append only file
	TypeAppend Type = 0x40

	// TypeDir is a directory
	TypeDir Type = 0x80
)

A  => qid/fileinfo.go +51 -0
@@ 1,51 @@
package qid

import (
	"hash/fnv"
	"os"
	"syscall"
)

// FileInfo creates a neinp.Qid from os.FileInfo, using Sys of FileInfo if possible.
func FileInfo(fi os.FileInfo) Qid {
	s, ok := fi.Sys().(*syscall.Stat_t)
	if !ok {
		return Generic(fi)
	}

	t := TypeFile
	if fi.IsDir() {
		t = TypeDir
	}

	// fvn hashes match the size of version and path fields. they are no cryptographical hashes, but should work good enough.
	v := fnv.New32a()
	v.Write([]byte{uint8(s.Mtim.Sec), uint8(s.Mtim.Sec >> 8), uint8(s.Mtim.Sec >> 16), uint8(s.Mtim.Sec >> 24), uint8(s.Mtim.Sec >> 32), uint8(s.Mtim.Sec >> 40), uint8(s.Mtim.Sec >> 48), uint8(s.Mtim.Sec >> 56)})
	v.Write([]byte{uint8(s.Mtim.Nsec), uint8(s.Mtim.Nsec >> 8), uint8(s.Mtim.Nsec >> 16), uint8(s.Mtim.Nsec >> 24), uint8(s.Mtim.Nsec >> 32), uint8(s.Mtim.Nsec >> 40), uint8(s.Mtim.Nsec >> 48), uint8(s.Mtim.Nsec >> 56)})

	p := fnv.New64a()
	p.Write([]byte{uint8(s.Dev), uint8(s.Dev >> 8), uint8(s.Dev >> 16), uint8(s.Dev >> 24), uint8(s.Dev >> 32), uint8(s.Dev >> 40), uint8(s.Dev >> 48), uint8(s.Dev >> 56)})
	p.Write([]byte{uint8(s.Ino), uint8(s.Ino >> 8), uint8(s.Ino >> 16), uint8(s.Ino >> 24), uint8(s.Ino >> 32), uint8(s.Ino >> 40), uint8(s.Ino >> 48), uint8(s.Ino >> 56)})

	return Qid{Type: t, Version: v.Sum32(), Path: p.Sum64()}
}

// Generic creates a neinp.Qid not using FileInfo.Sys().
func Generic(fi os.FileInfo) Qid {
	t := TypeFile
	if fi.IsDir() {
		t = TypeDir
	}

	sec := fi.ModTime().Unix()
	nsec := fi.ModTime().UnixNano()

	v := fnv.New32a()
	v.Write([]byte{uint8(sec), uint8(sec >> 8), uint8(sec >> 16), uint8(sec >> 24), uint8(sec >> 32), uint8(sec >> 40), uint8(sec >> 48), uint8(sec >> 56)})
	v.Write([]byte{uint8(nsec), uint8(nsec >> 8), uint8(nsec >> 16), uint8(nsec >> 24), uint8(nsec >> 32), uint8(nsec >> 40), uint8(nsec >> 48), uint8(nsec >> 56)})

	p := fnv.New64a()
	p.Write([]byte(fi.Name()))

	return Qid{Type: t, Version: v.Sum32(), Path: p.Sum64()}
}

A  => qid/qid.go +71 -0
@@ 1,71 @@
//Package qid provides the qid type and supporting functionality.
package qid

import (
	"github.com/rbns/neinp/basic"
	"io"
)

/*Qid represents the server's unique identification for a file.

Two files on the same server hierarchy are
the same if and only if their Qids are the same. (The
client may have multiple Fids pointing to a single file on a
server and hence having a single Qid.) The Qid fields hold a type,
specifying whether the file is a directory, append-only file, etc.,
the Qid Version and the Qid Path. The Path is an integer unique
among all files in the hierarchy.  If a file is deleted and recreated with
the same name in the same directory, the old and new path
components of the Qids should be different. The Version is
a version number for a file; typically, it is incremented
every time the file is modified.
*/
type Qid struct {
	Type    Type
	Version uint32
	Path    uint64
}

//Encode writes the 9p representation of the qid to an io.Writer.
func (q *Qid) Encode(w io.Writer) (int64, error) {
	n1, err := basic.Uint8Encode(w, uint8(q.Type))
	if err != nil {
		return n1, err
	}

	n2, err := basic.Uint32Encode(w, q.Version)
	if err != nil {
		return n1 + n2, err
	}

	n3, err := basic.Uint64Encode(w, q.Path)
	if err != nil {
		return n1 + n2 + n3, err
	}

	return n1 + n2 + n3, nil
}

//Decode reads the Qid data from an io.Reader.
func (q *Qid) Decode(r io.Reader) (int64, error) {
	typ, n1, err := basic.Uint8Decode(r)
	if err != nil {
		return n1, err
	}

	version, n2, err := basic.Uint32Decode(r)
	if err != nil {
		return n1 + n2, err
	}

	path, n3, err := basic.Uint64Decode(r)
	if err != nil {
		return n1 + n2 + n3, err
	}

	q.Type = Type(typ)
	q.Version = version
	q.Path = path

	return n1 + n2 + n3, err
}

A  => qid/qid_test.go +26 -0
@@ 1,26 @@
package qid

import (
	"bytes"
	"testing"
)

func TestQidEncodeDecode(t *testing.T) {
	var buf bytes.Buffer
	a := &Qid{1, 2, 3}
	_, err := a.Encode(&buf)
	if err != nil {
		t.Error(err)
	}

	b := &Qid{}
	_, err = b.Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if *a != *b {
		t.Log(a, b)
		t.Fail()
	}
}

A  => server.go +303 -0
@@ 1,303 @@
package neinp

import (
	"github.com/rbns/neinp/message"
	"context"
	"fmt"
	"io"
	"log"
	"sync"
	"sync/atomic"
)

// P2000 is the interface which file systems must implement to be used with Server.
// To ease implementation of new filesystems, NopP2000 can be embedded. See the
// types defined in message for documentation of what they do.
type P2000 interface {
	Version(context.Context, message.TVersion) (message.RVersion, error)
	Auth(context.Context, message.TAuth) (message.RAuth, error)
	Attach(context.Context, message.TAttach) (message.RAttach, error)
	Walk(context.Context, message.TWalk) (message.RWalk, error)
	Open(context.Context, message.TOpen) (message.ROpen, error)
	Create(context.Context, message.TCreate) (message.RCreate, error)
	Read(context.Context, message.TRead) (message.RRead, error)
	Write(context.Context, message.TWrite) (message.RWrite, error)
	Clunk(context.Context, message.TClunk) (message.RClunk, error)
	Remove(context.Context, message.TRemove) (message.RRemove, error)
	Stat(context.Context, message.TStat) (message.RStat, error)
	Wstat(context.Context, message.TWstat) (message.RWstat, error)
	Close() error
}

// Server muxes and demuxes 9p messages from a connection,
// calling the corresponding handlers of a P2000 interface.
type Server struct {
	fs    P2000
	msize uint32

	tags  map[uint16]context.CancelFunc
	tagsm sync.Mutex

	done chan struct{}

	Debug bool
}

// NewServer returns a Server initialized to use fs for message handling.
func NewServer(fs P2000) *Server {
	return &Server{
		fs:    fs,
		msize: 4096, // initial msize, shoud suffice to read the version message
		tags:  make(map[uint16]context.CancelFunc),
		done:  make(chan struct{}),
	}
}

// rcv reads from an io.Reader and sends the decoded messages to a channel.
func (s *Server) rcv(done <-chan struct{}, r io.Reader) (<-chan message.Message, <-chan error) {
	in := make(chan message.Message)
	rcverr := make(chan error)

	go func() {
		defer close(in)
		defer close(rcverr)
		for {
			select {
			case <-done:
				return
			default:
				req := message.Message{}
				_, err := req.Decode(io.LimitReader(r, int64(atomic.LoadUint32(&s.msize))))
				if err != nil {
					rcverr <- err
					return
				}

				if s.Debug {
					log.Printf("<- %#v", req.Content)
				}

				in <- req
			}
		}
	}()

	return in, rcverr
}

// process receives messages from a channel and calls the corresponding handler of the
// implementing P2000 interface.
//
// process is supposed to be called as goroutine from handle. the methods of the implementing
// P2000 interface are called together with a context usable for cancelation, so that they can be canceled by
// a later flush message. NB: it may be better to create the context in the caller instead
// of creating it here, but as they are put in a mutexed map here this _should_ work.
func (s *Server) process(req message.Message) (message.Message, error) {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	s.tagsm.Lock()
	s.tags[req.Tag] = cancel
	s.tagsm.Unlock()

	defer func() {
		s.tagsm.Lock()
		delete(s.tags, req.Tag)
		s.tagsm.Unlock()
	}()

	var c message.Content
	var err error

	switch t := req.Content.(type) {
	case *message.TVersion:
		// set msize to what the implementing file system wants
		var rVersion message.RVersion
		rVersion, err = s.fs.Version(ctx, *t)
		atomic.StoreUint32(&s.msize, rVersion.Msize)
		c = &rVersion
	case *message.TAuth:
		var rAuth message.RAuth
		rAuth, err = s.fs.Auth(ctx, *t)
		c = &rAuth
	case *message.TAttach:
		var rAttach message.RAttach
		rAttach, err = s.fs.Attach(ctx, *t)
		c = &rAttach
	case *message.TFlush:
		s.tagsm.Lock()
		cancel, ok := s.tags[t.Oldtag]
		s.tagsm.Unlock()
		if ok {
			cancel()
		}
		c = &message.RFlush{}
	case *message.TWalk:
		var rWalk message.RWalk
		rWalk, err = s.fs.Walk(ctx, *t)
		c = &rWalk
	case *message.TOpen:
		var rOpen message.ROpen
		rOpen, err = s.fs.Open(ctx, *t)
		c = &rOpen
	case *message.TCreate:
		var rCreate message.RCreate
		rCreate, err = s.fs.Create(ctx, *t)
		c = &rCreate
	case *message.TRead:
		var rRead message.RRead
		rRead, err = s.fs.Read(ctx, *t)
		c = &rRead
	case *message.TWrite:
		var rWrite message.RWrite
		rWrite, err = s.fs.Write(ctx, *t)
		c = &rWrite
	case *message.TClunk:
		var rClunk message.RClunk
		rClunk, err = s.fs.Clunk(ctx, *t)
		c = &rClunk
	case *message.TRemove:
		var rRemove message.RRemove
		rRemove, err = s.fs.Remove(ctx, *t)
		c = &rRemove
	case *message.TStat:
		var rStat message.RStat
		rStat, err = s.fs.Stat(ctx, *t)
		c = &rStat
	case *message.TWstat:
		var rWstat message.RWstat
		rWstat, err = s.fs.Wstat(ctx, *t)
		c = &rWstat
	default:
		// Return empty message and error for unexpected types. The empty message will
		// trigger the error being send to the handleErr channel.
		return message.Message{}, fmt.Errorf("unexpected message type %T", req.Content)
	}

	res := req.Response()
	res.Content = c

	// put errors from fs handers into the message
	if err != nil {
		res.Content = &message.RError{Ename: err.Error()}
	}

	return res, nil
}

// handle creates a process-goroutine per incoming message.
func (s *Server) handle(done <-chan struct{}, in <-chan message.Message) (<-chan <-chan message.Message, <-chan <-chan error) {
	out := make(chan (<-chan message.Message))
	handleErr := make(chan (<-chan error))

	go func() {
		defer close(out)
		for {
			select {
			case <-done:
				return
			case req := <-in:
				processRes := make(chan message.Message)
				processErr := make(chan error)

				go func() {
					defer close(processRes)
					defer close(processErr)

					res, err := s.process(req)
					processRes <- res
					if err != nil {
						processErr <- err
					}
				}()

				out <- processRes
				handleErr <- processErr
			}
		}
	}()

	return out, handleErr
}

// send sends the replys to the client.
func (s *Server) send(done <-chan struct{}, out <-chan (<-chan message.Message), w io.Writer) <-chan error {
	senderr := make(chan error)

	go func() {
		defer close(senderr)
		for {
			select {
			case <-done:
				return
			case x := <-out:
				for msg := range x {
					if s.Debug {
						log.Printf("-> %#v", msg.Content)
					}

					_, err := msg.Encode(w)
					if err != nil {
						senderr <- err
						return
					}
				}
			}
		}
	}()

	return senderr
}

//Serve the filesystem on the connection given by rw.
func (s *Server) Serve(rw io.ReadWriter) error {
	done := make(chan struct{})

	in, rcvErr := s.rcv(done, rw)
	out, handleErr := s.handle(done, in)
	sendErr := s.send(done, out, rw)

	defer s.cleanup()

	for {
		select {
		case err := <-rcvErr:
			close(done)
			return err
		case processErr := <-handleErr:
			// the channel may be closed or error nil
			err := <-processErr
			if err != nil {
				close(done)
				return err
			}
		case err := <-sendErr:
			close(done)
			return err
		case <-s.done:
			close(done)
			return nil
		}
	}
}

//Shutdown serving the filesystem.
func (s *Server) Shutdown() error {
	select {
	case <-s.done:
		return fmt.Errorf("shutdown but not serving")
	default:
		close(s.done)
	}
	return nil
}

// close the implementing fs
func (s *Server) cleanup() {
	if s.Debug {
		log.Println("cleanup: start")
	}
	err := s.fs.Close()
	if err != nil {
		log.Println("cleanup:", err)
	}
}

A  => server_test.go +63 -0
@@ 1,63 @@
package neinp

import (
	"github.com/rbns/neinp/message"
	"bytes"
	"context"
	"io"
	"testing"
)

type MsizeFS struct {
	NopP2000
}

func (f *MsizeFS) Version(ctx context.Context, req message.TVersion) (message.RVersion, error) {
	return message.RVersion{Msize: req.Msize, Version: req.Version}, nil
}

func TestMsize(t *testing.T) {
	s := NewServer(&MsizeFS{})

	content := message.TVersion{Msize: 1234, Version: "9P2000"}
	msg, err := message.New(0x1, &content)
	if err != nil {
		t.Error(err)
	}

	s.process(msg)

	if s.msize != content.Msize {
		t.Logf("Want: %v Have: %v", content.Msize, s.msize)
		t.Fail()
	}
}

func TestRcv(t *testing.T) {
	buf := bytes.NewReader([]byte{0x1b, 0x0, 0x0, 0x0, 0x68, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x74, 0x65, 0x73, 0x74, 0x4, 0x0, 0x74, 0x65, 0x73, 0x74})

	done := make(chan struct{})

	s := &Server{}

	msgs, errs := s.rcv(done, buf)

	select {
	case x := <-msgs:
		y, ok := x.Content.(*message.TAttach)
		if !ok {
			t.Fail()
		}
		if y.Uname != "test" {
			t.Fail()
		}
		if y.Aname != "test" {
			t.Fail()
		}
	case err := <-errs:
		if err != io.EOF {
			t.Error(err)
		}
	}

}

A  => stat/consts.go +143 -0
@@ 1,143 @@
package stat

import (
	"os"
)

/*Mode represents modes of a directory entry.*/
type Mode uint32

const (
	// SetGid is setgid (Unix, 9P2000.u)
	SetGid Mode = 0x00040000

	// SetUid is setuid (Unix, 9P2000.u)
	SetUid Mode = 0x00080000

	// Socket is a socket (Unix, 9P2000.u)
	Socket Mode = 0x00100000

	// NamedPipe is a named pipe (Unix, 9P2000.u)
	NamedPipe Mode = 0x00200000

	// Device is a device file (Unix, 9P2000.u)
	Device Mode = 0x00800000

	// Symlink is a symbolic link (Unix, 9P2000.u)
	Symlink Mode = 0x02000000

	// Tmp is a non-backed-up file
	Tmp Mode = 0x04000000

	// Auth is a authentication file
	Auth Mode = 0x08000000

	// Mount is a mounted channel
	Mount Mode = 0x10000000

	// Excl is a exclusive use file
	Excl Mode = 0x20000000

	// Append is a append only file
	Append Mode = 0x40000000

	// Dir is a directory
	Dir Mode = 0x80000000
)

//NeinMode converts os.FileMode to Mode.
func NeinMode(osmode os.FileMode) Mode {
	var mode Mode

	if osmode&os.ModeDir == os.ModeDir {
		mode |= Dir
	}

	if osmode&os.ModeAppend == os.ModeAppend {
		mode |= Append
	}

	if osmode&os.ModeExclusive == os.ModeExclusive {
		mode |= Excl
	}

	if osmode%os.ModeTemporary == os.ModeTemporary {
		mode |= Tmp
	}

	if osmode&os.ModeSymlink == os.ModeSymlink {
		mode |= Symlink
	}

	if osmode&os.ModeDevice == os.ModeDevice {
		mode |= Device
	}

	if osmode&os.ModeNamedPipe == os.ModeNamedPipe {
		mode |= NamedPipe
	}

	if osmode&os.ModeSocket == os.ModeSocket {
		mode |= Socket
	}

	if osmode&os.ModeSetgid == os.ModeSetgid {
		mode |= SetGid
	}

	if osmode&os.ModeSetuid == os.ModeSetuid {
		mode |= SetUid
	}

	mode |= Mode(uint32(osmode) & 0777)

	return mode
}

//OsMode translates Mode to os.FileMode.
func OsMode(mode Mode) os.FileMode {
	var osmode os.FileMode
	if mode&Dir == Dir {
		osmode |= os.ModeDir
	}

	if mode&Append == Append {
		osmode |= os.ModeAppend
	}

	if mode&Excl == Excl {
		osmode |= os.ModeExclusive
	}

	if mode&Tmp == Tmp {
		osmode |= os.ModeTemporary
	}

	if mode&Symlink == Symlink {
		osmode |= os.ModeSymlink
	}

	if mode&Device == Device {
		osmode |= os.ModeDevice
	}

	if mode&NamedPipe == NamedPipe {
		osmode |= os.ModeNamedPipe
	}

	if mode&Socket == Socket {
		osmode |= os.ModeSocket
	}

	if mode&SetGid == SetGid {
		osmode |= os.ModeSetgid
	}

	if mode&SetUid == SetUid {
		osmode |= os.ModeSetuid
	}

	osmode |= os.FileMode(mode & 0777)

	return os.FileMode(osmode)
}

A  => stat/fileinfo.go +77 -0
@@ 1,77 @@
package stat

import (
	"github.com/rbns/neinp/qid"
	"os"
	"os/user"
	"strconv"
	"syscall"
	"time"
)

// FileInfo creates Stat using os.FileInfo.Sys(). If using the information
// returned by Sys() fails, it returns a stat like returned by GenericStat.
func FileInfo(fi os.FileInfo) Stat {
	s, ok := fi.Sys().(*syscall.Stat_t)
	if !ok {
		return Generic(fi)
	}

	size := fi.Size()
	if fi.IsDir() {
		size = 0
	}

	var uid, gid string

	x, err := user.LookupId(strconv.Itoa(int(s.Uid)))
	if err != nil {
		return Generic(fi)
	}
	uid = x.Name

	y, err := user.LookupGroupId(strconv.Itoa(int(s.Gid)))
	if err != nil {
		return Generic(fi)
	}
	gid = y.Name

	stat := Stat{
		Qid:    qid.FileInfo(fi),
		Mode:   Mode(fi.Mode()),
		Atime:  time.Unix(s.Atim.Sec, s.Atim.Nsec),
		Mtime:  time.Unix(s.Mtim.Sec, s.Mtim.Nsec),
		Length: uint64(size),
		Name:   fi.Name(),
		Uid:    uid,
		Gid:    gid,
		Muid:   uid,
	}

	return stat
}

// Generic creates a neinp.Stat not using FileInfo.Sys().
func Generic(fi os.FileInfo) Stat {
	size := fi.Size()
	if fi.IsDir() {
		size = 0
	}

	uid := "nobody"
	gid := "nogroup"

	stat := Stat{
		Qid:    qid.FileInfo(fi),
		Mode:   NeinMode(fi.Mode()),
		Atime:  fi.ModTime(),
		Mtime:  fi.ModTime(),
		Length: uint64(size),
		Name:   fi.Name(),
		Uid:    uid,
		Gid:    gid,
		Muid:   uid,
	}

	return stat
}

A  => stat/reader.go +72 -0
@@ 1,72 @@
package stat

import (
	"bytes"
	"fmt"
	"io"
)

/*Reader is a reader for multiple Stats, aka directories.*/
type Reader struct {
	stats   []Stat
	pos     int
	bytepos int64
}

/*NewReader returns a new Reader initialized with stats and ready for reading.*/
func NewReader(stats ...Stat) *Reader {
	return &Reader{stats: stats}
}

func (s *Reader) Read(p []byte) (int, error) {
	var outbuf bytes.Buffer
	var buf bytes.Buffer
	npos := s.pos

	if s.pos == len(s.stats) || len(s.stats) == 0 {
		return 0, io.EOF
	}

	for _, stat := range s.stats[s.pos:] {
		_, err := stat.Encode(&buf)
		if err != nil {
			return 0, err
		}

		if buf.Len()+outbuf.Len() > len(p) {
			break
		}

		_, err = buf.WriteTo(&outbuf)
		if err != nil {
			return 0, err
		}

		npos++
	}

	n := copy(p, outbuf.Bytes())

	s.bytepos += int64(n)
	s.pos = npos

	return n, nil
}

//Seek interface implementation.
func (s *Reader) Seek(offset int64, whence int) (int64, error) {
	if whence != io.SeekStart {
		return s.bytepos, fmt.Errorf("Unsupported whence %v", whence)
	}

	switch offset {
	case 0:
		s.pos = 0
		s.bytepos = 0
	case s.bytepos:
	default:
		return s.bytepos, fmt.Errorf("can't seek there")
	}

	return s.bytepos, nil
}

A  => stat/reader_test.go +69 -0
@@ 1,69 @@
package stat

import (
	"bytes"
	"io"
	"testing"
	"time"
)

var stats = []Stat{
	Stat{Name: "a", Atime: time.Unix(1234567, 0), Mtime: time.Unix(1234567, 0)},
	Stat{Name: "b", Atime: time.Unix(1234567, 0), Mtime: time.Unix(1234567, 0)},
}

func TestReaderRead(t *testing.T) {
	r := NewReader(stats...)

	var buf bytes.Buffer
	buf.ReadFrom(r)

	for _, oldstat := range stats {
		var newstat Stat
		newstat.Decode(&buf)

		if newstat != oldstat {
			t.Fail()
		}
	}
}

func TestReaderSeek(t *testing.T) {
	r := NewReader(stats...)

	// We need to read into a buffer because stat.decode does multiple reads,
	// and StatReader only supports reading a whole stat at once.
	var buf bytes.Buffer
	var a0 Stat
	var a1 Stat

	// Read the encoded stats to the buffer
	buf.ReadFrom(r)

	_, err := a0.Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	// Seek to start.
	_, err = r.Seek(0, io.SeekStart)
	if err != nil {
		t.Error(err)
	}

	// Reset the buffer, then read again.
	// The read now should contain the first stat again.
	buf.Reset()
	buf.ReadFrom(r)

	_, err = a1.Decode(&buf)
	if err != nil {
		t.Error(err)
	}

	if a0 != a1 {
		t.Logf("\n%v\n%v", a0, a1)
		t.Fail()
	}

}

A  => stat/stat.go +265 -0
@@ 1,265 @@
//Package stat provides functionality to handle 9p stat values.
package stat

import (
	"github.com/rbns/neinp/basic"
	"github.com/rbns/neinp/qid"
	"bytes"
	"io"
	"time"
)

/*Stat contains file attributes.

Typ and Dev are usually unused. Qid contains the Qid of this file.
Mode is a combination of UNIX-permission bits for owner, group and others.
Atime contains the time of last access, Mtime the time of last modification.
Length is the file length in bytes, directories have a conventional
length of 0. Name is the file name and must be "/" if the file is the root
directory of the server. Uid is the owners name (not UNIX uid), group is the
groups name. Muid is the name of the last user who modified the file.

http://man.cat-v.org/inferno/5/stat
*/
type Stat struct {
	//	Size   uint16
	Typ    uint16
	Dev    uint32
	Qid    qid.Qid
	Mode   Mode
	Atime  time.Time
	Mtime  time.Time
	Length uint64
	Name   string
	Uid    string
	Gid    string
	Muid   string
}

//IsDir returns true if Dir is set.
func (s *Stat) IsDir() bool {
	return s.Mode&Dir == Dir
}

//IsAppend returns true if Append is set.
func (s *Stat) IsAppend() bool {
	return s.Mode&Append == Append
}

//IsExcl returns true if Excl is set.
func (s *Stat) IsExcl() bool {
	return s.Mode&Excl == Excl
}

//IsMount returns true if Mount is set.
func (s *Stat) IsMount() bool {
	return s.Mode&Mount == Mount
}

//IsAuth returns true if Auth is set.
func (s *Stat) IsAuth() bool {
	return s.Mode&Auth == Auth
}

//IsTmp returns true if Tmp is set.
func (s *Stat) IsTmp() bool {
	return s.Mode&Tmp == Tmp
}

//IsSymlink returns true if Symlink is set.
func (s *Stat) IsSymlink() bool {
	return s.Mode&Symlink == Symlink
}

//IsDevice returns true if Device is set.
func (s *Stat) IsDevice() bool {
	return s.Mode&Device == Device
}

//IsNamedPipe returns true if NamedPipe is set.
func (s *Stat) IsNamedPipe() bool {
	return s.Mode&NamedPipe == NamedPipe
}

//IsSocket returns true if Socket is set.
func (s *Stat) IsSocket() bool {
	return s.Mode&Socket == Socket
}

//IsSetUid returns true if SetUid is set.
func (s *Stat) IsSetUid() bool {
	return s.Mode&SetUid == SetUid
}

//IsSetGid returns true if SetGid is set
func (s *Stat) IsSetGid() bool {
	return s.Mode&SetGid == SetGid
}

func (s *Stat) Encode(w io.Writer) (int64, error) {
	var buf bytes.Buffer

	_, err := basic.Uint16Encode(&buf, s.Typ)
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint32Encode(&buf, s.Dev)
	if err != nil {
		return 0, err
	}

	_, err = s.Qid.Encode(&buf)
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint32Encode(&buf, uint32(s.Mode))
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint32Encode(&buf, uint32(s.Atime.Unix()))
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint32Encode(&buf, uint32(s.Mtime.Unix()))
	if err != nil {
		return 0, err
	}

	_, err = basic.Uint64Encode(&buf, s.Length)
	if err != nil {
		return 0, err
	}

	_, err = basic.StringEncode(&buf, s.Name)
	if err != nil {
		return 0, err
	}

	_, err = basic.StringEncode(&buf, s.Uid)
	if err != nil {
		return 0, err
	}

	_, err = basic.StringEncode(&buf, s.Gid)
	if err != nil {
		return 0, err
	}

	_, err = basic.StringEncode(&buf, s.Muid)
	if err != nil {
		return 0, err
	}

	/* From plan9 manual:
	BUGS
	To make the contents of a directory, such as returned by
	read(9P), easy to parse, each directory entry begins with a
	size field.  For consistency, the entries in Twstat and
	Rstat messages also contain their size, which means the size
	appears twice.  For example, the Rstat message is formatted
	as ``(4+1+2+2+n)[4] Rstat tag[2] n[2] (n-2)[2] type[2]
	dev[4]...,'' where n is the value returned by convD2M.
	*/
	size := uint16(buf.Len()) // we don't need to subtract 2 because size isn't in buf

	n1, err := basic.Uint16Encode(w, size)
	if err != nil {
		return n1, err
	}

	n2, err := buf.WriteTo(w)
	if err != nil {
		return n1 + n2, err
	}

	return n1 + n2, nil
}

func (s *Stat) Decode(r io.Reader) (int64, error) {
	size, n1, err := basic.Uint16Decode(r)
	if err != nil {
		return n1, err
	}

	b := make([]byte, size)
	n2, err := r.Read(b)
	if err != nil {
		return n1 + int64(n2), err
	}

	n := n1 + int64(n2)

	buf := bytes.NewBuffer(b)

	typ, _, err := basic.Uint16Decode(buf)
	if err != nil {
		return n, err
	}

	dev, _, err := basic.Uint32Decode(buf)
	if err != nil {
		return n, err
	}

	_, err = s.Qid.Decode(buf)
	if err != nil {
		return n, err
	}

	mod, _, err := basic.Uint32Decode(buf)
	if err != nil {
		return n, err
	}

	atime, _, err := basic.Uint32Decode(buf)
	if err != nil {
		return n, err
	}

	mtime, _, err := basic.Uint32Decode(buf)
	if err != nil {
		return n, err
	}

	length, _, err := basic.Uint64Decode(buf)
	if err != nil {
		return n, err
	}

	name, _, err := basic.StringDecode(buf)
	if err != nil {
		return n, err
	}

	uid, _, err := basic.StringDecode(buf)
	if err != nil {
		return n, err
	}

	gid, _, err := basic.StringDecode(buf)
	if err != nil {
		return n, err
	}

	muid, _, err := basic.StringDecode(buf)
	if err != nil {
		return n, err
	}

	s.Typ = typ
	s.Dev = dev
	s.Mode = Mode(mod)
	s.Atime = time.Unix(int64(atime), 0)
	s.Mtime = time.Unix(int64(mtime), 0)
	s.Length = length
	s.Name = name
	s.Uid = uid
	s.Gid = gid
	s.Muid = muid

	return n, nil
}