blob: fcf3723b8acb31e295f7ba82ce769996c1ac10a8 [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build fuchsia
package rio_test
import (
"bytes"
"testing"
"syscall/mx"
"syscall/mx/mxio"
"syscall/mx/mxio/dispatcher"
"syscall/mx/mxio/rio"
)
// Create a new dispatcher, running a RIO server. It is initialized with no clients.
func CreateRIOServer() *dispatcher.Dispatcher {
d, err := dispatcher.New(rio.Handler)
if err != nil {
panic(err)
}
go d.Serve()
return d
}
// Create a pair of handles, and add one end to the RIO server, along with a RIO Server callback.
func CreateRIOClient(d *dispatcher.Dispatcher, cb rio.ServerHandler, cookie int64) *rio.RemoteIO {
h0, h1, err := mx.NewChannel(0)
if err != nil {
panic(err)
}
if err := d.AddHandler(h0.Handle, cb, cookie); err != nil {
panic(err)
}
// TODO(smklein): Add 'event' handle to clientHandles when testing 'wait'
client, err := rio.New([]mx.Handle{h1.Handle})
if err != nil {
panic(err)
}
return client
}
var testData = []struct {
// Input messages
inOp uint32 // Operation (always used)
inArg int32 // 'arg', or 0 if ignored
inArg2 int64 // 'arg2', or 0 if ignored
inData []uint8 // 'data', or 0 if ignored
// Output messages
outRetval mx.Status // Expected return value from server
outArg2 int64 // 'arg2', or 0 if ignored
outData []uint8 // 'data', or empty if ignored
outHandles []mx.Handle // 'handles', or empty if ignored
}{
// IN: Arg: Flags, Arg2: Mode, Data: Name
// OUT: Retval: 0, Arg2: Protocol, Handles: Opened object (set elsewhere)
{rio.OpOpen, 12, 34, []uint8{'f', 'i', 'l', 'e'}, 0, int64(mxio.ProtocolRemote), []uint8{}, []mx.Handle{}},
// IN: Nothing
// OUT: Retval: 0, Arg2: Protocol, Handles: Opened object (set elsewhere)
{rio.OpClone, 0, 0, []uint8{}, 0, int64(mxio.ProtocolRemote), []uint8{}, []mx.Handle{}},
// IN: Arg: Max bytes to read
// OUT: Retval: Bytes read, Arg2: New offset, Data: Data read
{rio.OpRead, 4, 0, []uint8{}, 4, 5, []uint8{'d', 'a', 't', 'a'}, []mx.Handle{}},
// IN: Data: Data to write
// OUT: Retval: Bytes read, Arg2: New offset
{rio.OpWrite, 0, 0, []uint8{'i', 'n', 'p', 'u', 't'}, 5, 10, []uint8{}, []mx.Handle{}},
// IN: Arg: Whence, Arg2: Offset
// OUT: Retval: 0, Arg2: Offset
{rio.OpSeek, 1, 0x1234567, []uint8{}, 0, 0x7654321, []uint8{}, []mx.Handle{}},
// IN: Arg: Max length of "out", Arg2: Opcode (arbitrary), Data: Arbitrary bytes
// OUT: Retval: 0, Data: Arbitrary bytes
{rio.OpIoctl, rio.MessageChunkSize, 1337, []uint8{'i', 'n'}, 0, 0, []uint8{'o', 'u', 't'}, []mx.Handle{}},
// IN: Arg: Max length of "out", Arg2: Opcode (IoctlVFS)
// OUT: Retval: 0, Handles: Opened object (set elsewhere)
{rio.OpIoctl, rio.MessageChunkSize, int64(mxio.IoctlVFSMountFS), []uint8{}, 0, 0, []uint8{}, []mx.Handle{}},
// IN: Nothing
// OUT: Nothing
{rio.OpClose, 0, 0, []uint8{}, 0, 0, []uint8{}, []mx.Handle{}},
}
func checkOpen(client *rio.RemoteIO, opIndex int) {
// Create the expected return handle here, since dummy values would be rejected.
h0, _, err := mx.NewChannel(0)
if err != nil {
panic(err)
}
testData[opIndex].outHandles = []mx.Handle{h0.Handle}
_, err = client.Open(string(testData[opIndex].inData), testData[opIndex].inArg, uint32(testData[opIndex].inArg2))
if err != nil {
panic(err)
}
}
func checkClone(client *rio.RemoteIO, opIndex int) {
// Create the expected return handle here, since dummy values would be rejected.
h0, _, err := mx.NewChannel(0)
if err != nil {
panic(err)
}
testData[opIndex].outHandles = []mx.Handle{h0.Handle}
handles, err := client.Clone()
if err != nil {
panic(err)
} else if len(handles) != len(testData[opIndex].outHandles) {
panic("Unexpected number of returned handles")
} else if handles[0] != testData[opIndex].outHandles[0] {
panic("Unexpected returned handle")
}
}
func checkRead(client *rio.RemoteIO, opIndex int) {
buf := make([]byte, len(testData[opIndex].outData))
count, err := client.Read(buf)
if err != nil {
panic(err)
} else if count != len(buf) {
panic("Unexpected read byte count")
} else if !bytes.Equal(buf, testData[opIndex].outData) {
panic("Unexpected read data")
}
}
func checkWrite(client *rio.RemoteIO, opIndex int) {
count, err := client.Write(testData[opIndex].inData)
if err != nil {
panic(err)
} else if count != len(testData[opIndex].inData) {
panic("Unexpected write byte count")
}
}
func checkSeek(client *rio.RemoteIO, opIndex int) {
off, err := client.Seek(testData[opIndex].inArg2, int(testData[opIndex].inArg))
if err != nil {
panic(err)
} else if off != testData[opIndex].outArg2 {
panic("Unexpected result offset from seek")
}
}
func checkIoctl(client *rio.RemoteIO, opIndex int) {
if testData[opIndex].inArg2 == int64(mxio.IoctlVFSMountFS) {
// Create the expected return handle here, since dummy values would be rejected.
h0, _, err := mx.NewChannel(0)
if err != nil {
panic(err)
}
testData[opIndex].outHandles = []mx.Handle{h0.Handle}
}
outData, outHandles, err := client.Ioctl(uint32(testData[opIndex].inArg2), testData[opIndex].inData)
if err != nil {
panic(err)
} else if !bytes.Equal(testData[opIndex].outData, outData) {
panic("Unexpected ioctl output data")
} else if len(outHandles) != len(testData[opIndex].outHandles) {
panic("Unexpected ioctl output handle count")
}
for i := range outHandles {
if outHandles[i] != testData[opIndex].outHandles[i] {
panic("Unexpected handle")
}
}
}
func checkClose(client *rio.RemoteIO) {
if err := client.Close(); err != nil {
panic(err)
}
}
func TestRIO(t *testing.T) {
testCookie := int64(0x12345678) // Checks callback plumbing
opIndex := 0 // Checks that client / server are in sync
// This function will be called on the "fake" server. Check that everything is plumbed through
// correctly. In a realistic use of RIO, this function might exist in another process
// completely.
serverCb := func(msg *rio.Msg, rh mx.Handle, cookie int64) mx.Status {
// Check incoming message
if cookie != testCookie {
t.Fatalf("%d: unexpected cookie", opIndex)
}
if rh != 0 && msg.Magic != rio.MessageMagic {
// The 'magic' value should only be checked if the message was actually sent from the
// remote side, and not sent as a 'close' notification.
t.Fatalf("%d: unexpected magic", opIndex)
}
if msg.Hcount != 0 {
t.Fatalf("%d: unexpectedly sent a handle to the server", opIndex)
}
if got, want := testData[opIndex].inOp, msg.Op(); got != want {
t.Fatalf("%d: inOp=%v, want %v", got, want)
}
if got, want := testData[opIndex].inArg, msg.Arg; got != want {
t.Fatalf("%d: inArg=%v, want %v", got, want)
}
if got, want := testData[opIndex].inArg2, msg.Off(); got != want {
t.Fatalf("%d: inArg2=%v, want %v", got, want)
}
if got, want := len(testData[opIndex].inData), int(msg.Datalen); got != want {
t.Fatalf("%d: len(inData)=%d, want %d", got, want)
}
if got, want := testData[opIndex].inData, msg.Data[:msg.Datalen]; !bytes.Equal(got, want) {
t.Fatalf("%d: inData=%q, want %q", got, want)
}
// Prepare outgoing message
msg.SetOff(testData[opIndex].outArg2)
copy(msg.Data[:], testData[opIndex].outData)
msg.Datalen = uint32(len(testData[opIndex].outData))
copy(msg.Handle[:], testData[opIndex].outHandles)
msg.Hcount = uint32(len(testData[opIndex].outHandles))
return testData[opIndex].outRetval
}
d := CreateRIOServer()
client := CreateRIOClient(d, serverCb, testCookie)
for range testData {
switch testData[opIndex].inOp {
case rio.OpOpen:
checkOpen(client, opIndex)
case rio.OpClone:
checkClone(client, opIndex)
case rio.OpRead:
checkRead(client, opIndex)
case rio.OpWrite:
checkWrite(client, opIndex)
case rio.OpSeek:
checkSeek(client, opIndex)
case rio.OpIoctl:
checkIoctl(client, opIndex)
case rio.OpClose:
checkClose(client)
default:
t.Fatalf("%d: unexpected test op: %v", opIndex, testData[opIndex].inOp)
}
opIndex++
}
}