package protocol
import (
"encoding/binary"
"fmt"
)
// Message is the interface implemented by all 9P messages
type Message interface {
Type() uint8
Encode(buf []byte) int
}
// Request messages (T-messages)
// TversionMsg negotiates protocol version
type TversionMsg struct {
Msize uint32 // maximum message size
Version string // protocol version string
}
func (m *TversionMsg) Type() uint8 { return Tversion }
func (m *TversionMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Msize)
return 4 + EncodeString(buf[4:], m.Version)
}
func DecodeTversion(buf []byte) (*TversionMsg, error) {
if len(buf) < 6 {
return nil, fmt.Errorf("Tversion too short")
}
m := &TversionMsg{
Msize: binary.LittleEndian.Uint32(buf[0:4]),
}
m.Version, _ = DecodeString(buf[4:])
return m, nil
}
// RversionMsg is the response to Tversion
type RversionMsg struct {
Msize uint32
Version string
}
func (m *RversionMsg) Type() uint8 { return Rversion }
func (m *RversionMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Msize)
return 4 + EncodeString(buf[4:], m.Version)
}
// TattachMsg attaches to a filesystem
type TattachMsg struct {
Fid uint32 // fid to use for this connection
Afid uint32 // auth fid (NoFid if no auth)
Uname string // user name
Aname string // attach name (filesystem to attach)
}
func (m *TattachMsg) Type() uint8 { return Tattach }
func (m *TattachMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
binary.LittleEndian.PutUint32(buf[4:8], m.Afid)
n := 8
n += EncodeString(buf[n:], m.Uname)
n += EncodeString(buf[n:], m.Aname)
return n
}
func DecodeTattach(buf []byte) (*TattachMsg, error) {
if len(buf) < 12 {
return nil, fmt.Errorf("Tattach too short")
}
m := &TattachMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
Afid: binary.LittleEndian.Uint32(buf[4:8]),
}
n := 8
var sn int
m.Uname, sn = DecodeString(buf[n:])
n += sn
m.Aname, _ = DecodeString(buf[n:])
return m, nil
}
// RattachMsg is the response to Tattach
type RattachMsg struct {
Qid Qid
}
func (m *RattachMsg) Type() uint8 { return Rattach }
func (m *RattachMsg) Encode(buf []byte) int {
return m.Qid.Encode(buf)
}
// TwalkMsg walks a path
type TwalkMsg struct {
Fid uint32 // starting fid
Newfid uint32 // fid for the result
Names []string // path components to walk
}
func (m *TwalkMsg) Type() uint8 { return Twalk }
func (m *TwalkMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
binary.LittleEndian.PutUint32(buf[4:8], m.Newfid)
binary.LittleEndian.PutUint16(buf[8:10], uint16(len(m.Names)))
n := 10
for _, name := range m.Names {
n += EncodeString(buf[n:], name)
}
return n
}
func DecodeTwalk(buf []byte) (*TwalkMsg, error) {
if len(buf) < 10 {
return nil, fmt.Errorf("Twalk too short")
}
m := &TwalkMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
Newfid: binary.LittleEndian.Uint32(buf[4:8]),
}
nwname := binary.LittleEndian.Uint16(buf[8:10])
m.Names = make([]string, nwname)
n := 10
for i := range m.Names {
var sn int
m.Names[i], sn = DecodeString(buf[n:])
n += sn
}
return m, nil
}
// RwalkMsg is the response to Twalk
type RwalkMsg struct {
Qids []Qid // qids for each successfully walked element
}
func (m *RwalkMsg) Type() uint8 { return Rwalk }
func (m *RwalkMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint16(buf[0:2], uint16(len(m.Qids)))
n := 2
for i := range m.Qids {
n += m.Qids[i].Encode(buf[n:])
}
return n
}
// TopenMsg opens a file
type TopenMsg struct {
Fid uint32
Mode uint8
}
func (m *TopenMsg) Type() uint8 { return Topen }
func (m *TopenMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
buf[4] = m.Mode
return 5
}
func DecodeTopen(buf []byte) (*TopenMsg, error) {
if len(buf) < 5 {
return nil, fmt.Errorf("Topen too short")
}
return &TopenMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
Mode: buf[4],
}, nil
}
// RopenMsg is the response to Topen
type RopenMsg struct {
Qid Qid
Iounit uint32
}
func (m *RopenMsg) Type() uint8 { return Ropen }
func (m *RopenMsg) Encode(buf []byte) int {
n := m.Qid.Encode(buf)
binary.LittleEndian.PutUint32(buf[n:n+4], m.Iounit)
return n + 4
}
// TreadMsg reads from a file
type TreadMsg struct {
Fid uint32
Offset uint64
Count uint32
}
func (m *TreadMsg) Type() uint8 { return Tread }
func (m *TreadMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
binary.LittleEndian.PutUint64(buf[4:12], m.Offset)
binary.LittleEndian.PutUint32(buf[12:16], m.Count)
return 16
}
func DecodeTread(buf []byte) (*TreadMsg, error) {
if len(buf) < 16 {
return nil, fmt.Errorf("Tread too short")
}
return &TreadMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
Offset: binary.LittleEndian.Uint64(buf[4:12]),
Count: binary.LittleEndian.Uint32(buf[12:16]),
}, nil
}
// RreadMsg is the response to Tread
type RreadMsg struct {
Data []byte
}
func (m *RreadMsg) Type() uint8 { return Rread }
func (m *RreadMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(m.Data)))
copy(buf[4:], m.Data)
return 4 + len(m.Data)
}
// TwriteMsg writes to a file
type TwriteMsg struct {
Fid uint32
Offset uint64
Data []byte
}
func (m *TwriteMsg) Type() uint8 { return Twrite }
func (m *TwriteMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
binary.LittleEndian.PutUint64(buf[4:12], m.Offset)
binary.LittleEndian.PutUint32(buf[12:16], uint32(len(m.Data)))
copy(buf[16:], m.Data)
return 16 + len(m.Data)
}
func DecodeTwrite(buf []byte) (*TwriteMsg, error) {
if len(buf) < 16 {
return nil, fmt.Errorf("Twrite too short")
}
count := binary.LittleEndian.Uint32(buf[12:16])
if len(buf) < int(16+count) {
return nil, fmt.Errorf("Twrite data truncated")
}
return &TwriteMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
Offset: binary.LittleEndian.Uint64(buf[4:12]),
Data: buf[16 : 16+count],
}, nil
}
// RwriteMsg is the response to Twrite
type RwriteMsg struct {
Count uint32
}
func (m *RwriteMsg) Type() uint8 { return Rwrite }
func (m *RwriteMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Count)
return 4
}
// TclunkMsg closes a fid
type TclunkMsg struct {
Fid uint32
}
func (m *TclunkMsg) Type() uint8 { return Tclunk }
func (m *TclunkMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
return 4
}
func DecodeTclunk(buf []byte) (*TclunkMsg, error) {
if len(buf) < 4 {
return nil, fmt.Errorf("Tclunk too short")
}
return &TclunkMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
}, nil
}
// RclunkMsg is the response to Tclunk
type RclunkMsg struct{}
func (m *RclunkMsg) Type() uint8 { return Rclunk }
func (m *RclunkMsg) Encode(buf []byte) int {
return 0
}
// TstatMsg requests file stats
type TstatMsg struct {
Fid uint32
}
func (m *TstatMsg) Type() uint8 { return Tstat }
func (m *TstatMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint32(buf[0:4], m.Fid)
return 4
}
func DecodeTstat(buf []byte) (*TstatMsg, error) {
if len(buf) < 4 {
return nil, fmt.Errorf("Tstat too short")
}
return &TstatMsg{
Fid: binary.LittleEndian.Uint32(buf[0:4]),
}, nil
}
// RstatMsg is the response to Tstat
type RstatMsg struct {
Stat Stat
}
func (m *RstatMsg) Type() uint8 { return Rstat }
func (m *RstatMsg) Encode(buf []byte) int {
// Rstat has an extra 2-byte length prefix for the stat
statBuf := buf[2:]
n := m.Stat.Encode(statBuf)
binary.LittleEndian.PutUint16(buf[0:2], uint16(n))
return 2 + n
}
// RerrorMsg indicates an error
type RerrorMsg struct {
Ename string
}
func (m *RerrorMsg) Type() uint8 { return Rerror }
func (m *RerrorMsg) Encode(buf []byte) int {
return EncodeString(buf, m.Ename)
}
// TflushMsg cancels a pending request
type TflushMsg struct {
Oldtag uint16
}
func (m *TflushMsg) Type() uint8 { return Tflush }
func (m *TflushMsg) Encode(buf []byte) int {
binary.LittleEndian.PutUint16(buf[0:2], m.Oldtag)
return 2
}
func DecodeTflush(buf []byte) (*TflushMsg, error) {
if len(buf) < 2 {
return nil, fmt.Errorf("Tflush too short")
}
return &TflushMsg{
Oldtag: binary.LittleEndian.Uint16(buf[0:2]),
}, nil
}
// RflushMsg is the response to Tflush
type RflushMsg struct{}
func (m *RflushMsg) Type() uint8 { return Rflush }
func (m *RflushMsg) Encode(buf []byte) int {
return 0
}