github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/util/protorpc/protorpc.go (about)

     1  // Copyright (c) 2014, Kevin Walsh.  All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package protorpc implements a protobuf-based ClientCodec and ServerCodec for
    16  // the rpc package. Clients can make concurrent or asynchronous requests, and
    17  // these are handled in whatever order the servers chooses.
    18  //
    19  // All service methods take two protobuf message pointers: a request and a
    20  // response. RPC service method strings, sequence numbers, and response errors
    21  // are sent over the connection separately from the requests and responses.
    22  //
    23  // Wire format: A request or response is encoded on the wire as a 32-bit length
    24  // (in network byte order), followed by a marshalled protobuf for the header,
    25  // followed by another 32-bit length, then a marshaled protobuf for the body.
    26  // Separate length fields are used for framing because the protobuf encoding does
    27  // not preserve message boundaries. Except for I/O errors, protobufs are encoded
    28  // in pairs: first the header, then the request or response body.
    29  package protorpc
    30  
    31  import (
    32  	"errors"
    33  	"io"
    34  	"net/rpc"
    35  	"sync"
    36  
    37  	"github.com/golang/protobuf/proto"
    38  	"github.com/jlmucb/cloudproxy/go/util"
    39  )
    40  
    41  // clientCodec is a net/rpc client codec for protobuf messages
    42  type clientCodec struct {
    43  	m       *util.MessageStream
    44  	sending sync.Mutex
    45  }
    46  
    47  // NewClientCodec returns a new rpc.ClientCodec using protobuf messages on conn.
    48  func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec {
    49  	m, ok := conn.(*util.MessageStream)
    50  	if !ok {
    51  		// The given conn lacks framing, so add some.
    52  		m = util.NewMessageStream(conn)
    53  	}
    54  	return &clientCodec{m, sync.Mutex{}}
    55  }
    56  
    57  // NewClient returns a new rpc.Client to handle requests to the set of services
    58  // at the other end of the connection.
    59  func NewClient(conn io.ReadWriteCloser) *rpc.Client {
    60  	return rpc.NewClientWithCodec(NewClientCodec(conn))
    61  }
    62  
    63  // Error types for the protorpc package.
    64  var (
    65  	ErrBadRequestType  = errors.New("protorpc: bad request type")
    66  	ErrMissingRequest  = errors.New("protorpc: missing request")
    67  	ErrBadResponseType = errors.New("protorpc: bad response type")
    68  	ErrMissingResponse = errors.New("protorpc: missing response")
    69  )
    70  
    71  // WriteRequest encodes and sends a net/rpc request header r with body x.
    72  func (c *clientCodec) WriteRequest(r *rpc.Request, x interface{}) error {
    73  	body, ok := x.(proto.Message)
    74  	if !ok || body == nil {
    75  		// TODO(kwalsh) Not clear if this is legal, but I think not.
    76  		// Don't send anything.
    77  		return util.Logged(ErrBadRequestType)
    78  	}
    79  	var hdr ProtoRPCRequestHeader
    80  	hdr.Op = proto.String(r.ServiceMethod)
    81  	hdr.Seq = proto.Uint64(r.Seq)
    82  	c.sending.Lock()
    83  	_, err := c.m.WriteMessage(&hdr) // writes htonl(length), marshal(hdr)
    84  	if err == nil {
    85  		_, err = c.m.WriteMessage(body) // writes htonl(length), marshal(body)
    86  	}
    87  	c.sending.Unlock()
    88  	return util.Logged(err)
    89  }
    90  
    91  // ReadResponseHeader receives and decodes a net/rpc response header r.
    92  func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
    93  	var err error
    94  	var hdr ProtoRPCResponseHeader
    95  	if err = c.m.ReadMessage(&hdr); err != nil {
    96  		return util.Logged(err)
    97  	}
    98  	r.Seq = *hdr.Seq
    99  	r.ServiceMethod = *hdr.Op
   100  	if hdr.Error != nil {
   101  		r.Error = *hdr.Error
   102  	}
   103  	return nil
   104  }
   105  
   106  // ReadResponseBody receives and decodes a net/rpc response body x.
   107  func (c *clientCodec) ReadResponseBody(x interface{}) error {
   108  	if x == nil {
   109  		// rpc.Client is telling us to read and discard the response, perhaps
   110  		// because response header contains an error (in which case the server would
   111  		// have encoded a blank message body).
   112  		_, err := c.m.ReadString()
   113  		return util.Logged(err)
   114  	}
   115  	body, ok := x.(proto.Message)
   116  	if !ok || body == nil {
   117  		// TODO(kwalsh) Not clear if this is legal, but I think not.
   118  		// Read and discard the response body.
   119  		c.m.ReadString()
   120  		return util.Logged(ErrBadResponseType)
   121  	}
   122  	return util.Logged(c.m.ReadMessage(body))
   123  }
   124  
   125  // Close closes the channel used by the client codec.
   126  func (c *clientCodec) Close() error {
   127  	return c.m.Close()
   128  }
   129  
   130  // serverCodec is a net/rpc server codec for protobuf messages
   131  type serverCodec struct {
   132  	m       *util.MessageStream
   133  	sending sync.Mutex
   134  }
   135  
   136  // NewServerCodec returns a new rpc.ServerCodec using protobuf messages on conn.
   137  func NewServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec {
   138  	m, ok := conn.(*util.MessageStream)
   139  	if !ok {
   140  		// The given conn lacks framing, so add some.
   141  		m = util.NewMessageStream(conn)
   142  	}
   143  	return &serverCodec{m, sync.Mutex{}}
   144  }
   145  
   146  // ReadRequestHeader receives and decodes a net/rpc request header r.
   147  func (c *serverCodec) ReadRequestHeader(r *rpc.Request) error {
   148  	// This is almost identical to ReadResponseHeader(), above.
   149  	var err error
   150  	var hdr ProtoRPCRequestHeader
   151  	if err = c.m.ReadMessage(&hdr); err != nil {
   152  		// Don't log an error here, since this is where normal EOF
   153  		// happens over net/rpc channels, e.g., if a client finishes and
   154  		// disconnects.
   155  		return err
   156  	}
   157  	r.Seq = *hdr.Seq
   158  	r.ServiceMethod = *hdr.Op
   159  	return nil
   160  }
   161  
   162  // ReadRequestBody receives and decodes a net/rpc request body x.
   163  func (c *serverCodec) ReadRequestBody(x interface{}) error {
   164  	// This is almost identical to ReadResponseBody(), above.
   165  	if x == nil {
   166  		// rpc.Server is telling us to read and discard the request, perhaps because
   167  		// response header was read successfully but contained an unexpected service
   168  		// method string. The client would have encoded an actual message body.
   169  		_, err := c.m.ReadString()
   170  		return util.Logged(err)
   171  	}
   172  	body, ok := x.(proto.Message)
   173  	if !ok || body == nil {
   174  		// TODO(kwalsh) Not clear if this is legal, but I think not.
   175  		// Read and discard the request body.
   176  		c.m.ReadString()
   177  		return util.Logged(ErrBadRequestType)
   178  	}
   179  	return util.Logged(c.m.ReadMessage(body))
   180  }
   181  
   182  // WriteResponse encodes and sends a net/rpc response header r with body x.
   183  func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error {
   184  	// This is similar to WriteRequest(), above.
   185  	var encodeErr error
   186  	var hdr ProtoRPCResponseHeader
   187  	hdr.Op = proto.String(r.ServiceMethod)
   188  	hdr.Seq = proto.Uint64(r.Seq)
   189  	var body proto.Message
   190  	var ok bool
   191  	if r.Error != "" {
   192  		// Error responses have empty body. In this case, x can be an empty struct
   193  		// from net/rpc.Server, and net/rpc.Client will discard the body in any
   194  		// case, so leave body == nil.
   195  		hdr.Error = proto.String(r.Error)
   196  	} else if body, ok = x.(proto.Message); !ok || body == nil {
   197  		// If x isn't a protobuf, or is a nil protobuf, turn reply into an error and
   198  		// leave body == nil.
   199  		encodeErr = ErrBadResponseType
   200  		msg := encodeErr.Error()
   201  		hdr.Error = &msg
   202  	}
   203  
   204  	c.sending.Lock()
   205  	_, err := c.m.WriteMessage(&hdr) // writes htonl(length), marshal(hdr)
   206  	if err == nil {
   207  		_, err = c.m.WriteMessage(body) // writes htonl(length), marshal(body)
   208  	}
   209  	c.sending.Unlock()
   210  	if encodeErr != nil {
   211  		err = encodeErr
   212  	}
   213  	return util.Logged(err)
   214  }
   215  
   216  // Close closes the channel used by the server codec.
   217  func (c *serverCodec) Close() error {
   218  	return c.m.Close()
   219  }