blob: 11d334c354962f65a453457370fec78d2a5af20b [file] [log] [blame]
// Copyright 2017 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
import (
"errors"
"io"
"syscall/mx"
"syscall/mx/mxio"
"syscall/mx/mxnet"
)
// Socket is an MXIO socket.
type Socket struct {
ctrl mx.Channel
socket mx.Handle // mx.Socket for TCP, mx.Channel for UDP
dgram bool
// r has the same handles as the socket, so we can borrow
// the typical RemoteIO method implementations.
r *RemoteIO
}
// NewSocket creates a new MXIO Socket.
func NewSocket(handles []mx.Handle) (*Socket, error) {
r, err := New(handles)
if err != nil {
return nil, err
}
s := &Socket{
ctrl: mx.Channel{Handle: handles[0]},
r: r,
}
if len(handles) > 1 {
s.socket = handles[1]
}
return s, nil
}
func (s *Socket) Read(data []byte) (n int, err error) {
if s.dgram {
b, _, _, _, err := s.RecvMsg(len(data))
n = copy(data, b)
return n, err
}
// c.f. mxsio_read_stream in magenta/system/ulib/mxio/remoteio.c
for {
n, err = mx.Socket(s.socket).Read(data, 0)
switch mxStatus(err) {
case mx.ErrOk:
return n, nil
case mx.ErrRemoteClosed:
return 0, io.EOF
case mx.ErrShouldWait:
obs, err := s.socket.WaitOne(mx.SignalSocketReadable|mx.SignalSocketPeerClosed, mx.TimensecInfinite)
switch mxStatus(err) {
case mx.ErrOk:
switch {
case obs&mx.SignalSocketReadable != 0:
continue
case obs&mx.SignalSocketPeerClosed != 0:
return 0, io.EOF
}
case mx.ErrBadHandle, mx.ErrHandleClosed:
return 0, io.EOF
default:
return 0, err
}
default:
return 0, err
}
}
}
func (s *Socket) ReadAt(data []byte, off int64) (int, error) {
return 0, mx.Error{Status: mx.ErrNotSupported, Text: "rio.Socket.ReadAt"}
}
func (s *Socket) Write(data []byte) (int, error) {
if s.dgram {
n, err := s.SendMsg(data, "", 0)
return n, err
}
// c.f. mxsio_write_stream
for {
n, err := mx.Socket(s.socket).Write(data, 0)
switch mxStatus(err) {
case mx.ErrOk:
return n, nil
case mx.ErrShouldWait:
_, err := s.socket.WaitOne(mx.SignalSocketWritable, mx.TimensecInfinite)
if err != nil {
return n, err
}
continue
}
}
}
func (s *Socket) WriteAt(data []byte, off int64) (int, error) {
return 0, mx.Error{Status: mx.ErrNotSupported, Text: "rio.Socket.WriteAt"}
}
func (s *Socket) RecvMsg(maxLen int) (b []byte, flags int, addr string, port uint16, err error) {
mlen := maxLen + mxnet.SockmsgHdrLen
msgdata := make([]byte, mlen)
n, err := s.recvMsg(msgdata)
if err != nil {
return nil, 0, "", 0, err
}
msgdata = msgdata[:n]
addrstr, port, flags, err := mxnet.DecodeSockmsgHdr(msgdata)
if err != nil {
return nil, 0, "", 0, err
}
return msgdata[mxnet.SockmsgHdrLen:], flags, string(addrstr), port, nil
}
func (s *Socket) recvMsg(data []byte) (n int, err error) {
ch := mx.Channel{s.socket}
for {
n, _, err := ch.Read(data, nil, 0)
switch mxStatus(err) {
case mx.ErrOk:
return int(n), err
case mx.ErrShouldWait:
obs, err := s.socket.WaitOne(mx.SignalChannelReadable|mx.SignalChannelPeerClosed, mx.TimensecInfinite)
if err != nil {
return 0, err
}
if obs&mx.SignalChannelReadable != 0 {
continue
}
if obs&mx.SignalChannelPeerClosed != 0 {
return 0, mx.Error{Status: mx.ErrRemoteClosed, Text: "rio.Socket.recvMsg"}
}
return 0, mx.Error{Status: mx.ErrInternal, Text: "rio.Socket.recvMsg"}
default:
return 0, err
}
}
}
func (s *Socket) SendMsg(b []byte, addr string, port uint16) (n int, err error) {
data := make([]byte, len(b)+mxnet.SockmsgHdrLen)
err = mxnet.EncodeSockmsgHdr(data, mxnet.Addr(addr), port, 0)
if err != nil {
return 0, err
}
copy(data[mxnet.SockmsgHdrLen:], b)
ch := mx.Channel{s.socket}
err = ch.Write(data, nil, 0)
if err != nil {
return 0, err
}
return n, nil
}
func (s *Socket) Seek(offset int64, whence int) (int64, error) {
return 0, mx.Error{Status: mx.ErrNotSupported, Text: "rio.Socket.Seek"}
}
func (s *Socket) Close() error {
s.ctrl.Close()
if err := s.socket.Close(); err != nil {
return err
}
s.ctrl = mx.Channel{}
s.socket = 0
return nil
}
func (s *Socket) Open(path string, flags int32, mode uint32) (mxio.MXIO, error) {
return s.r.Open(path, flags, mode)
}
func (*Socket) Clone() ([]mx.Handle, error) {
return nil, mx.Error{Status: mx.ErrNotSupported, Text: "rio.Socket.Clone"}
}
func (s *Socket) Ioctl(op uint32, in, out []byte) ([]mx.Handle, error) {
return s.r.Ioctl(op, in, out)
}
func (s *Socket) IoctlSetHandle(op uint32, in mx.Handle) error {
return s.r.IoctlSetHandle(op, in)
}
func (s *Socket) Misc(op uint32, off int64, in, out []byte, handles []mx.Handle) (n int, err error) {
return s.r.Misc(op, off, in, out, handles)
}
// SetDgram marks a *Socket as a datagram (UDP) socket.
// A different protocol is used internally.
func SetDgram(m mxio.MXIO) error {
if m == nil {
return errors.New("rio.SetDgram: nil argument")
}
s, ok := m.(*Socket)
if !ok {
return errors.New("rio.SetDgram: argument is not a *Socket")
}
s.dgram = true
return nil
}
// Bind is BSD Sockets bind on a *Socket.
func Bind(m mxio.MXIO, addr mxnet.Addr, port uint16) error {
if m == nil {
return errors.New("rio.Bind: nil argument")
}
b := make([]byte, mxnet.SockaddrLen)
addrlen, err := mxnet.EncodeSockaddr(b, addr, port)
if err != nil {
return err
}
b = b[:addrlen]
s, ok := m.(*Socket)
if !ok {
return errors.New("rio.Connect: argument is not a *Socket")
}
_, err = s.Misc(OpBind, 0, b, nil, nil)
return err
}
// Connect is BSD Sockets connect on a *Socket.
func Connect(m mxio.MXIO, addr mxnet.Addr, port uint16) error {
if m == nil {
return errors.New("rio.Connect: nil argument")
}
b := make([]byte, mxnet.SockaddrLen)
addrlen, err := mxnet.EncodeSockaddr(b, addr, port)
if err != nil {
return err
}
b = b[:addrlen]
s, ok := m.(*Socket)
if !ok {
return errors.New("rio.Connect: argument is not a *Socket")
}
_, err = s.Misc(OpConnect, 0, b, nil, nil)
if err != nil && mxStatus(err) != mx.ErrShouldWait {
return err
}
if !s.dgram {
_, err = s.socket.WaitOne(mxnet.MXSIO_SIGNAL_CONNECTED, 0)
return err
}
return nil
}
func ListenStream(m mxio.MXIO, backlog int) error {
if m == nil {
return errors.New("rio.ListenStream: nil argument")
}
s, ok := m.(*Socket)
if !ok {
return errors.New("rio.ListenStream: argument is not a *Socket")
}
b := make([]byte, 4)
b[0] = byte(backlog)
b[1] = byte(backlog >> 8)
b[2] = byte(backlog >> 16)
b[3] = byte(backlog >> 24)
_, err := s.Misc(OpListen, 0, b, nil, nil)
return err
}
func Accept(m mxio.MXIO) (newm mxio.MXIO, err error) {
if m == nil {
return nil, errors.New("rio.Accept: nil argument")
}
s, ok := m.(*Socket)
if !ok {
return nil, errors.New("rio.Accept: argument is not a *Socket")
}
for {
newm, err = m.Open("accept", 0, 0)
if err == nil {
break
}
if mxStatus(err) != mx.ErrShouldWait {
return nil, err
}
// TODO: non-blocking support, pass this to the poller
const MXSIO_SIGNAL_INCOMING = mx.SignalUser0
_, err = s.socket.WaitOne(mxnet.MXSIO_SIGNAL_INCOMING, mx.TimensecInfinite)
if err != nil {
return nil, err
}
}
return newm, nil
}
func getName(m mxio.MXIO, op uint32, debug string) (addr mxnet.Addr, port uint16, err error) {
if m == nil {
return "", 0, errors.New("rio." + debug + ": nil argument")
}
s, ok := m.(*Socket)
if !ok {
return "", 0, errors.New("rio." + debug + ": argument is not a *Socket")
}
out := make([]byte, mxnet.SockaddrReplyLen)
_, err = s.Misc(OpGetSockname, 0, nil, out, nil)
if err != nil {
return "", 0, err
}
addr, port, err = mxnet.DecodeSockaddr(out)
if err != nil {
return "", 0, err
}
return addr, port, nil
}
func GetSockName(m mxio.MXIO) (addr mxnet.Addr, port uint16, err error) {
return getName(m, OpGetSockname, "GetSockName")
}
func GetPeerName(m mxio.MXIO) (addr mxnet.Addr, port uint16, err error) {
return getName(m, OpGetPeerName, "GetPeerName")
}
func mxStatus(err error) mx.Status {
if err == nil {
return mx.ErrOk
}
if status, ok := err.(mx.Error); ok {
return status.Status
}
return mx.ErrInternal
}