blob: af2b855f513fcd9687bc60ca84f7c54c8d03b41f [file] [log] [blame]
// Copyright 2009 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.
// SRPC server
package srpc
import (
"bytes"
"log"
"os"
"syscall"
)
// TODO(rsc): I'd prefer to make this
// type Handler func(m *msg) Errno
// but NaCl can't use closures.
// The explicit interface is a way to attach state.
// A Handler is a handler for an SRPC method.
// It reads arguments from arg, checks size for array limits,
// writes return values to ret, and returns an Errno status code.
type Handler interface {
Run(arg, ret []interface{}, size []int) Errno
}
type method struct {
name string
fmt string
handler Handler
}
var rpcMethod []method
// BUG(rsc): Add's format string should be replaced by analyzing the
// type of an arbitrary func passed in an interface{} using reflection.
// Add registers a handler for the named method.
// Fmt is a Native Client format string, a sequence of
// alphabetic characters representing the types of the parameter values,
// a colon, and then a sequence of alphabetic characters
// representing the types of the returned values.
// The format characters and corresponding dynamic types are:
//
// b bool
// C []byte
// d float64
// D []float64
// h int // a file descriptor (aka handle)
// i int32
// I []int32
// s string
//
func Add(name, fmt string, handler Handler) {
n := len(rpcMethod)
if n >= cap(rpcMethod) {
a := make([]method, n, (n+4)*2)
for i := range a {
a[i] = rpcMethod[i]
}
rpcMethod = a
}
rpcMethod = rpcMethod[0 : n+1]
rpcMethod[n] = method{name, fmt, handler}
}
// Serve accepts new SRPC connections from the file descriptor fd
// and answers RPCs issued on those connections.
// It closes fd and returns an error if the imc_accept system call fails.
func Serve(fd int) os.Error {
defer syscall.Close(fd)
for {
cfd, _, e := syscall.Syscall(syscall.SYS_IMC_ACCEPT, uintptr(fd), 0, 0)
if e != 0 {
return os.NewSyscallError("imc_accept", int(e))
}
go serveLoop(int(cfd))
}
panic("unreachable")
}
func serveLoop(fd int) {
c := make(chan *msg)
go sendLoop(fd, c)
var r msgReceiver
r.fd = fd
for {
m, err := r.recv()
if err != nil {
break
}
m.unpackRequest()
if !m.gotHeader {
log.Stderrf("cannot unpack header: %s", m.status)
continue
}
// log.Stdoutf("<- %#v", m);
m.isReq = false // set up for response
go serveMsg(m, c)
}
close(c)
}
func sendLoop(fd int, c <-chan *msg) {
var s msgSender
s.fd = fd
for m := range c {
// log.Stdoutf("-> %#v", m);
m.packResponse()
s.send(m)
}
syscall.Close(fd)
}
func serveMsg(m *msg, c chan<- *msg) {
if m.status != OK {
c <- m
return
}
if m.rpcNumber >= uint32(len(rpcMethod)) {
m.status = ErrBadRPCNumber
c <- m
return
}
meth := &rpcMethod[m.rpcNumber]
if meth.fmt != m.fmt {
switch {
case len(m.fmt) < len(meth.fmt):
m.status = ErrTooFewArgs
case len(m.fmt) > len(meth.fmt):
m.status = ErrTooManyArgs
default:
// There's a type mismatch.
// It's an in-arg mismatch if the mismatch happens
// before the colon; otherwise it's an out-arg mismatch.
m.status = ErrInArgTypeMismatch
for i := 0; i < len(m.fmt) && m.fmt[i] == meth.fmt[i]; i++ {
if m.fmt[i] == ':' {
m.status = ErrOutArgTypeMismatch
break
}
}
}
c <- m
return
}
m.status = meth.handler.Run(m.Arg, m.Ret, m.Size)
c <- m
}
// ServeRuntime serves RPCs issued by the Native Client embedded runtime.
// This should be called by main once all methods have been registered using Add.
func ServeRuntime() os.Error {
// Call getFd to check that we are running embedded.
if _, err := getFd(); err != nil {
return err
}
// We are running embedded.
// The fd returned by getFd is a red herring.
// Accept connections on magic fd 3.
return Serve(3)
}
// getFd runs the srpc_get_fd system call.
func getFd() (fd int, err os.Error) {
r1, _, e := syscall.Syscall(syscall.SYS_SRPC_GET_FD, 0, 0, 0)
return int(r1), os.NewSyscallError("srpc_get_fd", int(e))
}
// Enabled returns true if SRPC is enabled in the Native Client runtime.
func Enabled() bool {
_, err := getFd()
return err == nil
}
// Service #0, service_discovery, returns a list of the other services
// and their argument formats.
type serviceDiscovery struct{}
func (serviceDiscovery) Run(arg, ret []interface{}, size []int) Errno {
var b bytes.Buffer
for _, m := range rpcMethod {
b.WriteString(m.name)
b.WriteByte(':')
b.WriteString(m.fmt)
b.WriteByte('\n')
}
if b.Len() > size[0] {
return ErrNoMemory
}
ret[0] = b.Bytes()
return OK
}
func init() { Add("service_discovery", ":C", serviceDiscovery{}) }