// 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 }