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 }