diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2014-06-19 09:22:53 +0200 |
---|---|---|
committer | Michael Stapelberg <stapelberg@debian.org> | 2014-06-19 09:22:53 +0200 |
commit | 8a39ee361feb9bf46d728ff1ba4f07ca1d9610b1 (patch) | |
tree | 4449f2036cccf162e8417cc5841a35815b3e7ac5 /src/pkg/syscall/srpc_nacl.go | |
parent | c8bf49ef8a92e2337b69c14b9b88396efe498600 (diff) | |
download | golang-upstream/1.3.tar.gz |
Imported Upstream version 1.3upstream/1.3
Diffstat (limited to 'src/pkg/syscall/srpc_nacl.go')
-rw-r--r-- | src/pkg/syscall/srpc_nacl.go | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/src/pkg/syscall/srpc_nacl.go b/src/pkg/syscall/srpc_nacl.go new file mode 100644 index 000000000..dd07373d1 --- /dev/null +++ b/src/pkg/syscall/srpc_nacl.go @@ -0,0 +1,822 @@ +// Copyright 2013 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. + +// Native Client SRPC message passing. +// This code is needed to invoke SecureRandom, the NaCl equivalent of /dev/random. + +package syscall + +import ( + "errors" + "sync" + "unsafe" +) + +// An srpcClient represents the client side of an SRPC connection. +type srpcClient struct { + fd int // to server + r msgReceiver + s msgSender + service map[string]srpcService // services by name + + outMu sync.Mutex // protects writing to connection + + mu sync.Mutex // protects following fields + muxer bool // is someone reading and muxing responses + pending map[uint32]*srpc + idGen uint32 // generator for request IDs +} + +// An srpcService is a single method that the server offers. +type srpcService struct { + num uint32 // method number + fmt string // argument format; see "parsing of RPC messages" below +} + +// An srpc represents a single srpc issued by a client. +type srpc struct { + Ret []interface{} + Done chan *srpc + Err error + c *srpcClient + id uint32 +} + +// newClient allocates a new SRPC client using the file descriptor fd. +func newClient(fd int) (*srpcClient, error) { + c := new(srpcClient) + c.fd = fd + c.r.fd = fd + c.s.fd = fd + c.service = make(map[string]srpcService) + c.pending = make(map[uint32]*srpc) + + // service discovery request + m := &msg{ + isRequest: 1, + template: []interface{}{[]byte(nil)}, + size: []int{4000}, // max size to accept for returned byte slice + } + if err := m.pack(); err != nil { + return nil, errors.New("Native Client SRPC service_discovery: preparing request: " + err.Error()) + } + c.s.send(m) + m, err := c.r.recv() + if err != nil { + return nil, err + } + m.unpack() + if m.status != uint32(srpcOK) { + return nil, errors.New("Native Client SRPC service_discovery: " + srpcErrno(m.status).Error()) + } + list := m.value[0].([]byte) + var n uint32 + for len(list) > 0 { + var line []byte + i := byteIndex(list, '\n') + if i < 0 { + line, list = list, nil + } else { + line, list = list[:i], list[i+1:] + } + i = byteIndex(line, ':') + if i >= 0 { + c.service[string(line)] = srpcService{n, string(line[i+1:])} + } + n++ + } + + return c, nil +} + +func byteIndex(b []byte, c byte) int { + for i, bi := range b { + if bi == c { + return i + } + } + return -1 +} + +var yourTurn srpc + +func (c *srpcClient) wait(r *srpc) { + var rx *srpc + for rx = range r.Done { + if rx != &yourTurn { + break + } + c.input() + } + return +} + +func (c *srpcClient) input() { + // read message + m, err := c.r.recv() + if err != nil { + println("Native Client SRPC receive error:", err.Error()) + return + } + if m.unpack(); m.status != uint32(srpcOK) { + println("Native Client SRPC receive error: invalid message: ", srpcErrno(m.status).Error()) + return + } + + // deliver to intended recipient + c.mu.Lock() + rpc, ok := c.pending[m.id] + if ok { + delete(c.pending, m.id) + } + + // wake a new muxer if there are more RPCs to read + c.muxer = false + for _, rpc := range c.pending { + c.muxer = true + rpc.Done <- &yourTurn + break + } + c.mu.Unlock() + if !ok { + println("Native Client: unexpected response for ID", m.id) + return + } + rpc.Ret = m.value + rpc.Done <- rpc +} + +// Wait blocks until the RPC has finished. +func (r *srpc) Wait() { + r.c.wait(r) +} + +// Start issues an RPC request for method name with the given arguments. +// The RPC r must not be in use for another pending request. +// To wait for the RPC to finish, receive from r.Done and then +// inspect r.Ret and r.Errno. +func (r *srpc) Start(name string, arg []interface{}) { + r.Err = nil + r.c.mu.Lock() + srv, ok := r.c.service[name] + if !ok { + r.c.mu.Unlock() + r.Err = srpcErrBadRPCNumber + r.Done <- r + return + } + r.c.pending[r.id] = r + if !r.c.muxer { + r.c.muxer = true + r.Done <- &yourTurn + } + r.c.mu.Unlock() + + var m msg + m.id = r.id + m.isRequest = 1 + m.rpc = srv.num + m.value = arg + + // Fill in the return values and sizes to generate + // the right type chars. We'll take most any size. + + // Skip over input arguments. + // We could check them against arg, but the server + // will do that anyway. + i := 0 + for srv.fmt[i] != ':' { + i++ + } + format := srv.fmt[i+1:] + + // Now the return prototypes. + m.template = make([]interface{}, len(format)) + m.size = make([]int, len(format)) + for i := 0; i < len(format); i++ { + switch format[i] { + default: + println("Native Client SRPC: unexpected service type " + string(format[i])) + r.Err = srpcErrBadRPCNumber + r.Done <- r + return + case 'b': + m.template[i] = false + case 'C': + m.template[i] = []byte(nil) + m.size[i] = 1 << 30 + case 'd': + m.template[i] = float64(0) + case 'D': + m.template[i] = []float64(nil) + m.size[i] = 1 << 30 + case 'h': + m.template[i] = int(-1) + case 'i': + m.template[i] = int32(0) + case 'I': + m.template[i] = []int32(nil) + m.size[i] = 1 << 30 + case 's': + m.template[i] = "" + m.size[i] = 1 << 30 + } + } + + if err := m.pack(); err != nil { + r.Err = errors.New("Native Client RPC Start " + name + ": preparing request: " + err.Error()) + r.Done <- r + return + } + + r.c.outMu.Lock() + r.c.s.send(&m) + r.c.outMu.Unlock() +} + +// Call is a convenience wrapper that starts the RPC request, +// waits for it to finish, and then returns the results. +// Its implementation is: +// +// r.Start(name, arg) +// r.Wait() +// return r.Ret, r.Errno +// +func (c *srpcClient) Call(name string, arg ...interface{}) (ret []interface{}, err error) { + r := c.NewRPC(nil) + r.Start(name, arg) + r.Wait() + return r.Ret, r.Err +} + +// NewRPC creates a new RPC on the client connection. +func (c *srpcClient) NewRPC(done chan *srpc) *srpc { + if done == nil { + done = make(chan *srpc, 1) + } + c.mu.Lock() + id := c.idGen + c.idGen++ + c.mu.Unlock() + return &srpc{Done: done, c: c, id: id} +} + +// The current protocol number. +// Kind of useless, since there have been backwards-incompatible changes +// to the wire protocol that did not update the protocol number. +// At this point it's really just a sanity check. +const protocol = 0xc0da0002 + +// An srpcErrno is an SRPC status code. +type srpcErrno uint32 + +const ( + srpcOK srpcErrno = 256 + iota + srpcErrBreak + srpcErrMessageTruncated + srpcErrNoMemory + srpcErrProtocolMismatch + srpcErrBadRPCNumber + srpcErrBadArgType + srpcErrTooFewArgs + srpcErrTooManyArgs + srpcErrInArgTypeMismatch + srpcErrOutArgTypeMismatch + srpcErrInternalError + srpcErrAppError +) + +var srpcErrstr = [...]string{ + srpcOK - srpcOK: "ok", + srpcErrBreak - srpcOK: "break", + srpcErrMessageTruncated - srpcOK: "message truncated", + srpcErrNoMemory - srpcOK: "out of memory", + srpcErrProtocolMismatch - srpcOK: "protocol mismatch", + srpcErrBadRPCNumber - srpcOK: "invalid RPC method number", + srpcErrBadArgType - srpcOK: "unexpected argument type", + srpcErrTooFewArgs - srpcOK: "too few arguments", + srpcErrTooManyArgs - srpcOK: "too many arguments", + srpcErrInArgTypeMismatch - srpcOK: "input argument type mismatch", + srpcErrOutArgTypeMismatch - srpcOK: "output argument type mismatch", + srpcErrInternalError - srpcOK: "internal error", + srpcErrAppError - srpcOK: "application error", +} + +func (e srpcErrno) Error() string { + if e < srpcOK || int(e-srpcOK) >= len(srpcErrstr) { + return "srpcErrno(" + itoa(int(e)) + ")" + } + return srpcErrstr[e-srpcOK] +} + +// A msgHdr is the data argument to the imc_recvmsg +// and imc_sendmsg system calls. +type msgHdr struct { + iov *iov + niov int32 + desc *int32 + ndesc int32 + flags uint32 +} + +// A single region for I/O. +type iov struct { + base *byte + len int32 +} + +const maxMsgSize = 1<<16 - 4*4 + +// A msgReceiver receives messages from a file descriptor. +type msgReceiver struct { + fd int + data [maxMsgSize]byte + desc [8]int32 + hdr msgHdr + iov iov +} + +func (r *msgReceiver) recv() (*msg, error) { + // Init pointers to buffers where syscall recvmsg can write. + r.iov.base = &r.data[0] + r.iov.len = int32(len(r.data)) + r.hdr.iov = &r.iov + r.hdr.niov = 1 + r.hdr.desc = &r.desc[0] + r.hdr.ndesc = int32(len(r.desc)) + n, _, e := Syscall(sys_imc_recvmsg, uintptr(r.fd), uintptr(unsafe.Pointer(&r.hdr)), 0) + if e != 0 { + println("Native Client imc_recvmsg: ", e.Error()) + return nil, e + } + + // Make a copy of the data so that the next recvmsg doesn't + // smash it. The system call did not update r.iov.len. Instead it + // returned the total byte count as n. + m := new(msg) + m.data = make([]byte, n) + copy(m.data, r.data[0:]) + + // Make a copy of the desc too. + // The system call *did* update r.hdr.ndesc. + if r.hdr.ndesc > 0 { + m.desc = make([]int32, r.hdr.ndesc) + copy(m.desc, r.desc[:]) + } + + return m, nil +} + +// A msgSender sends messages on a file descriptor. +type msgSender struct { + fd int + hdr msgHdr + iov iov +} + +func (s *msgSender) send(m *msg) error { + if len(m.data) > 0 { + s.iov.base = &m.data[0] + } + s.iov.len = int32(len(m.data)) + s.hdr.iov = &s.iov + s.hdr.niov = 1 + s.hdr.desc = nil + s.hdr.ndesc = 0 + _, _, e := Syscall(sys_imc_sendmsg, uintptr(s.fd), uintptr(unsafe.Pointer(&s.hdr)), 0) + if e != 0 { + println("Native Client imc_sendmsg: ", e.Error()) + return e + } + return nil +} + +// A msg is the Go representation of an SRPC message. +type msg struct { + data []byte // message data + desc []int32 // message file descriptors + + // parsed version of message + id uint32 + isRequest uint32 + rpc uint32 + status uint32 + value []interface{} + template []interface{} + size []int + format string + broken bool +} + +// reading from a msg + +func (m *msg) uint32() uint32 { + if m.broken { + return 0 + } + if len(m.data) < 4 { + m.broken = true + return 0 + } + b := m.data[:4] + x := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 + m.data = m.data[4:] + return x +} + +func (m *msg) uint64() uint64 { + x := uint64(m.uint32()) | uint64(m.uint32())<<32 + if m.broken { + return 0 + } + return x +} + +func (m *msg) bytes(n int) []byte { + if m.broken { + return nil + } + if len(m.data) < n { + m.broken = true + return nil + } + x := m.data[0:n] + m.data = m.data[n:] + return x +} + +// writing to a msg + +func (m *msg) wuint32(x uint32) { + m.data = append(m.data, byte(x), byte(x>>8), byte(x>>16), byte(x>>24)) +} + +func (m *msg) wuint64(x uint64) { + lo := uint32(x) + hi := uint32(x >> 32) + m.data = append(m.data, byte(lo), byte(lo>>8), byte(lo>>16), byte(lo>>24), byte(hi), byte(hi>>8), byte(hi>>16), byte(hi>>24)) +} + +func (m *msg) wbytes(p []byte) { + m.data = append(m.data, p...) +} + +func (m *msg) wstring(s string) { + m.data = append(m.data, s...) +} + +// Parsing of RPC messages. +// +// Each message begins with +// total_size uint32 +// total_descs uint32 +// fragment_size uint32 +// fragment_descs uint32 +// +// If fragment_size < total_size or fragment_descs < total_descs, the actual +// message is broken up in multiple messages; follow-up messages omit +// the "total" fields and begin with the "fragment" fields. +// We do not support putting fragmented messages back together. +// To do this we would need to change the message receiver. +// +// After that size information, the message header follows: +// protocol uint32 +// requestID uint32 +// isRequest uint32 +// rpcNumber uint32 +// status uint32 +// numValue uint32 +// numTemplate uint32 +// +// After the header come numTemplate fixed-size arguments, +// numValue fixed-size arguments, and then the variable-sized +// part of the values. The templates describe the expected results +// and have no associated variable sized data in the request. +// +// Each fixed-size argument has the form: +// tag uint32 // really a char, like 'b' or 'C' +// pad uint32 // unused +// val1 uint32 +// val2 uint32 +// +// The tags are: +// 'b': bool; val1 == 0 or 1 +// 'C': []byte; val1 == len, data in variable-sized section +// 'd': float64; (val1, val2) is data +// 'D': []float64; val1 == len, data in variable-sized section +// 'h': int; val1 == file descriptor +// 'i': int32; descriptor in next entry in m.desc +// 'I': []int; val1 == len, data in variable-sized section +// 's': string; val1 == len, data in variable-sized section +// + +func (m *msg) pack() error { + m.data = m.data[:0] + m.desc = m.desc[:0] + + // sizes, to fill in later + m.wuint32(0) + m.wuint32(0) + m.wuint32(0) + m.wuint32(0) + + // message header + m.wuint32(protocol) + m.wuint32(m.id) + m.wuint32(m.isRequest) + m.wuint32(m.rpc) + m.wuint32(m.status) + m.wuint32(uint32(len(m.value))) + m.wuint32(uint32(len(m.template))) + + // fixed-size templates + for i, x := range m.template { + var tag, val1, val2 uint32 + switch x.(type) { + default: + return errors.New("unexpected template type") + case bool: + tag = 'b' + case []byte: + tag = 'C' + val1 = uint32(m.size[i]) + case float64: + tag = 'd' + case []float64: + tag = 'D' + val1 = uint32(m.size[i]) + case int: + tag = 'h' + case int32: + tag = 'i' + case []int32: + tag = 'I' + val1 = uint32(m.size[i]) + case string: + tag = 's' + val1 = uint32(m.size[i]) + } + m.wuint32(tag) + m.wuint32(0) + m.wuint32(val1) + m.wuint32(val2) + } + + // fixed-size values + for _, x := range m.value { + var tag, val1, val2 uint32 + switch x := x.(type) { + default: + return errors.New("unexpected value type") + case bool: + tag = 'b' + if x { + val1 = 1 + } + case []byte: + tag = 'C' + val1 = uint32(len(x)) + case float64: + tag = 'd' + v := float64bits(x) + val1 = uint32(v) + val2 = uint32(v >> 32) + case []float64: + tag = 'D' + val1 = uint32(len(x)) + case int32: + tag = 'i' + m.desc = append(m.desc, x) + case []int32: + tag = 'I' + val1 = uint32(len(x)) + case string: + tag = 's' + val1 = uint32(len(x) + 1) + } + m.wuint32(tag) + m.wuint32(0) + m.wuint32(val1) + m.wuint32(val2) + } + + // variable-length data for values + for _, x := range m.value { + switch x := x.(type) { + case []byte: + m.wbytes(x) + case []float64: + for _, f := range x { + m.wuint64(float64bits(f)) + } + case []int32: + for _, j := range x { + m.wuint32(uint32(j)) + } + case string: + m.wstring(x) + m.wstring("\x00") + } + } + + // fill in sizes + data := m.data + m.data = m.data[:0] + m.wuint32(uint32(len(data))) + m.wuint32(uint32(len(m.desc))) + m.wuint32(uint32(len(data))) + m.wuint32(uint32(len(m.desc))) + m.data = data + + return nil +} + +func (m *msg) unpack() error { + totalSize := m.uint32() + totalDesc := m.uint32() + fragSize := m.uint32() + fragDesc := m.uint32() + if totalSize != fragSize || totalDesc != fragDesc { + return errors.New("Native Client: fragmented RPC messages not supported") + } + if m.uint32() != protocol { + return errors.New("Native Client: RPC protocol mismatch") + } + + // message header + m.id = m.uint32() + m.isRequest = m.uint32() + m.rpc = m.uint32() + m.status = m.uint32() + m.value = make([]interface{}, m.uint32()) + m.template = make([]interface{}, m.uint32()) + m.size = make([]int, len(m.template)) + if m.broken { + return errors.New("Native Client: malformed message") + } + + // fixed-size templates + for i := range m.template { + tag := m.uint32() + m.uint32() // padding + val1 := m.uint32() + m.uint32() // val2 + switch tag { + default: + return errors.New("Native Client: unexpected template type " + string(rune(tag))) + case 'b': + m.template[i] = false + case 'C': + m.template[i] = []byte(nil) + m.size[i] = int(val1) + case 'd': + m.template[i] = float64(0) + case 'D': + m.template[i] = []float64(nil) + m.size[i] = int(val1) + case 'i': + m.template[i] = int32(0) + case 'I': + m.template[i] = []int32(nil) + m.size[i] = int(val1) + case 'h': + m.template[i] = int(0) + case 's': + m.template[i] = "" + m.size[i] = int(val1) + } + } + + // fixed-size values + var ( + strsize []uint32 + d int + ) + for i := range m.value { + tag := m.uint32() + m.uint32() // padding + val1 := m.uint32() + val2 := m.uint32() + switch tag { + default: + return errors.New("Native Client: unexpected value type " + string(rune(tag))) + case 'b': + m.value[i] = val1 > 0 + case 'C': + m.value[i] = []byte(nil) + strsize = append(strsize, val1) + case 'd': + m.value[i] = float64frombits(uint64(val1) | uint64(val2)<<32) + case 'D': + m.value[i] = make([]float64, val1) + case 'i': + m.value[i] = int32(val1) + case 'I': + m.value[i] = make([]int32, val1) + case 'h': + m.value[i] = int(m.desc[d]) + d++ + case 's': + m.value[i] = "" + strsize = append(strsize, val1) + } + } + + // variable-sized parts of values + for i, x := range m.value { + switch x := x.(type) { + case []byte: + m.value[i] = m.bytes(int(strsize[0])) + strsize = strsize[1:] + case []float64: + for i := range x { + x[i] = float64frombits(m.uint64()) + } + case []int32: + for i := range x { + x[i] = int32(m.uint32()) + } + case string: + m.value[i] = string(m.bytes(int(strsize[0]))) + strsize = strsize[1:] + } + } + + if len(m.data) > 0 { + return errors.New("Native Client: junk at end of message") + } + return nil +} + +func float64bits(x float64) uint64 { + return *(*uint64)(unsafe.Pointer(&x)) +} + +func float64frombits(x uint64) float64 { + return *(*float64)(unsafe.Pointer(&x)) +} + +// At startup, connect to the name service. +var nsClient = nsConnect() + +func nsConnect() *srpcClient { + var ns int32 = -1 + _, _, errno := Syscall(sys_nameservice, uintptr(unsafe.Pointer(&ns)), 0, 0) + if errno != 0 { + println("Native Client nameservice:", errno.Error()) + return nil + } + + sock, _, errno := Syscall(sys_imc_connect, uintptr(ns), 0, 0) + if errno != 0 { + println("Native Client nameservice connect:", errno.Error()) + return nil + } + + c, err := newClient(int(sock)) + if err != nil { + println("Native Client nameservice init:", err.Error()) + return nil + } + + return c +} + +const ( + nsSuccess = 0 + nsNameNotFound = 1 + nsDuplicateName = 2 + nsInsufficientResources = 3 + nsPermissionDenied = 4 + nsInvalidArgument = 5 +) + +func openNamedService(name string, mode int32) (fd int, err error) { + if nsClient == nil { + return 0, errors.New("no name service") + } + ret, err := nsClient.Call("lookup:si:ih", name, int32(mode)) + if err != nil { + return 0, err + } + status := ret[0].(int32) + fd = ret[1].(int) + switch status { + case nsSuccess: + // ok + case nsNameNotFound: + return -1, ENOENT + case nsDuplicateName: + return -1, EEXIST + case nsInsufficientResources: + return -1, EWOULDBLOCK + case nsPermissionDenied: + return -1, EPERM + case nsInvalidArgument: + return -1, EINVAL + default: + return -1, EINVAL + } + return fd, nil +} |