|
| 1 | +/* |
| 2 | +Package protocols is an extension to p2p. It offers a user friendly simple way to define |
| 3 | +devp2p subprotocols by abstracting away code standardly shared by protocols. |
| 4 | +The subprotocol architecture is inspired by the node package. Similar to a node |
| 5 | +the standard protocol (class) registers service contructors that are instantiated as service |
| 6 | +instances on the protocol isntance that is launched on a p2p peer connection. |
| 7 | +
|
| 8 | +By mounting various protocol modules protocols can encapsulate vertical slices of business logic |
| 9 | +without duplicating code related to protocol communication. |
| 10 | +Standard protocol supports: |
| 11 | +
|
| 12 | +* mounting services instantiated with the remote peer when a protocol instance is launched on a newly |
| 13 | + established peer connection |
| 14 | +* registering module-specific handshakes and offers validation and renegotiation of handshakes |
| 15 | +* registering multiple handlers for incoming messages |
| 16 | +* automate assigments of code indexes to messages |
| 17 | +* automate RLP decoding/encoding based on reflecting |
| 18 | +* provide the forever loop to read incoming messages |
| 19 | +* standardise error handling related to communication |
| 20 | +* enables access to sister services of the same peer connection analogous to node.Service |
| 21 | +* TODO: automatic generation of wire protocol specification for peers |
| 22 | +* peerPool abstracting out peer management by defining a peerPool that is called to register/unregister |
| 23 | + peers as they connect and drop (ideally the peerPool also implements the peerPool interface that the |
| 24 | + p2p server needs to suggest peers to connect to in server-as-initiator mode of operation |
| 25 | + see https://github.com/ethereum/go-ethereum/issues/2254 for the peer management/connectivity related |
| 26 | + aspect |
| 27 | +
|
| 28 | +*/ |
| 29 | + |
| 30 | +package protocols |
| 31 | + |
| 32 | +import ( |
| 33 | + "fmt" |
| 34 | + "reflect" |
| 35 | + |
| 36 | + "github.com/ethereum/go-ethereum/logger" |
| 37 | + "github.com/ethereum/go-ethereum/logger/glog" |
| 38 | + "github.com/ethereum/go-ethereum/p2p" |
| 39 | +) |
| 40 | + |
| 41 | +// error codes used by this protocol scheme |
| 42 | +const ( |
| 43 | + ErrMsgTooLong = iota |
| 44 | + ErrDecode |
| 45 | + ErrWrite |
| 46 | + ErrInvalidMsgCode |
| 47 | + ErrInvalidMsgType |
| 48 | + ErrLocalHandshake |
| 49 | + ErrRemoteHandshake |
| 50 | + ErrHandler |
| 51 | +) |
| 52 | + |
| 53 | +// error description strings associated with the codes |
| 54 | +var errorToString = map[int]string{ |
| 55 | + ErrMsgTooLong: "Message too long", |
| 56 | + ErrDecode: "Invalid message (RLP error)", |
| 57 | + ErrWrite: "Error sending message", |
| 58 | + ErrInvalidMsgCode: "Invalid message code", |
| 59 | + ErrInvalidMsgType: "Invalid message type", |
| 60 | + ErrLocalHandshake: "Local handshake error", |
| 61 | + ErrRemoteHandshake: "Remote handshake error", |
| 62 | + ErrHandler: "Message handler error", |
| 63 | +} |
| 64 | + |
| 65 | +/* |
| 66 | +Error implements the standard go error interface. |
| 67 | +Use: |
| 68 | +
|
| 69 | + errorf(code, format, params ...interface{}) |
| 70 | +
|
| 71 | +Prints as: |
| 72 | +
|
| 73 | + <description>: <details> |
| 74 | +
|
| 75 | +where description is given by code in errorToString |
| 76 | +and details is fmt.Sprintf(format, params...) |
| 77 | +
|
| 78 | +exported field Code can be checked |
| 79 | +*/ |
| 80 | +type Error struct { |
| 81 | + Code int |
| 82 | + message string |
| 83 | + format string |
| 84 | + params []interface{} |
| 85 | +} |
| 86 | + |
| 87 | +func (self Error) Error() (message string) { |
| 88 | + if len(message) == 0 { |
| 89 | + name, ok := errorToString[self.Code] |
| 90 | + if !ok { |
| 91 | + panic("invalid message code") |
| 92 | + } |
| 93 | + self.message = name |
| 94 | + if self.format != "" { |
| 95 | + self.message += ": " + fmt.Sprintf(self.format, self.params...) |
| 96 | + } |
| 97 | + } |
| 98 | + return self.message |
| 99 | +} |
| 100 | + |
| 101 | +func errorf(code int, format string, params ...interface{}) *Error { |
| 102 | + self := &Error{ |
| 103 | + Code: code, |
| 104 | + format: format, |
| 105 | + params: params, |
| 106 | + } |
| 107 | + |
| 108 | + return self |
| 109 | +} |
| 110 | + |
| 111 | +// implements the code table spec |
| 112 | +// listing the message codes and types etc |
| 113 | +// and further metadata about the protocol |
| 114 | +type CodeMap struct { |
| 115 | + Name string // name of the protocol |
| 116 | + Version uint // version |
| 117 | + MaxMsgSize int // max length of message payload size |
| 118 | + codes []reflect.Type // index of codes to msg types - to create zero values |
| 119 | + messages map[reflect.Type]uint // index of types to codes, for sending by type |
| 120 | +} |
| 121 | + |
| 122 | +func NewCodeMap(name string, version uint, maxMsgSize int, msgs ...interface{}) *CodeMap { |
| 123 | + self := &CodeMap{ |
| 124 | + Name: name, |
| 125 | + Version: version, |
| 126 | + MaxMsgSize: maxMsgSize, |
| 127 | + messages: make(map[reflect.Type]uint), |
| 128 | + } |
| 129 | + self.Register(msgs...) |
| 130 | + return self |
| 131 | +} |
| 132 | + |
| 133 | +func (self *CodeMap) Length() uint64 { |
| 134 | + return uint64(len(self.codes)) |
| 135 | +} |
| 136 | + |
| 137 | +func (self *CodeMap) Register(msgs ...interface{}) { |
| 138 | + code := uint(len(self.codes)) |
| 139 | + for _, msg := range msgs { |
| 140 | + typ := reflect.TypeOf(msg) |
| 141 | + _, found := self.messages[typ] |
| 142 | + if found { |
| 143 | + // ignore duplicates |
| 144 | + continue |
| 145 | + } |
| 146 | + // next code assigned to message type typ |
| 147 | + self.messages[typ] = code |
| 148 | + self.codes = append(self.codes, typ) |
| 149 | + code++ |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +// A Peer represents a remote peer or protocol instance that is running on a peer connection with |
| 154 | +// a remote peer |
| 155 | +type Peer struct { |
| 156 | + ct *CodeMap // CodeMap for the protocol |
| 157 | + *p2p.Peer // the p2p.Peer object representing the remote |
| 158 | + rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from |
| 159 | + handlers map[reflect.Type][]func(interface{}) error // message type -> message handler callback(s) map |
| 160 | + disconnect func() // Disconnect function set differently for testing |
| 161 | + // lastActive time.Time // tracking last active state |
| 162 | +} |
| 163 | + |
| 164 | +// NewPeer returns a new peer |
| 165 | +// this constructor is called by the p2p.Protocol#Run function |
| 166 | +// the first two arguments are comming the arguments passed to p2p.Protocol.Run function |
| 167 | +// the third argument is the CodeMap describing the protocol messages and options |
| 168 | +func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer { |
| 169 | + return newPeer(p, rw, ct, func() { |
| 170 | + p.Disconnect(p2p.DiscSubprotocolError) |
| 171 | + }) |
| 172 | +} |
| 173 | + |
| 174 | +// the disconnect function needs to be set differently for testing |
| 175 | +func NewTestPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer { |
| 176 | + return newPeer(p, rw, ct, func() { |
| 177 | + rw.(*p2p.MsgPipeRW).Close() |
| 178 | + }) |
| 179 | +} |
| 180 | + |
| 181 | +func newPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap, disconn func()) *Peer { |
| 182 | + return &Peer{ |
| 183 | + ct: ct, |
| 184 | + Peer: p, |
| 185 | + rw: rw, |
| 186 | + handlers: make(map[reflect.Type][]func(interface{}) error), |
| 187 | + disconnect: disconn, |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +// Register is called on the peer typically within the constructor of service instances running on peer connections |
| 192 | +// These constructors are called by the p2p.Protocol#Run function |
| 193 | +// It ties handler callbackss for specific message types |
| 194 | +// A message type can have several handlers registered by the same or different protocol services |
| 195 | +// Register is meant to be called once, deregistering is not currently supported therefore |
| 196 | +// handlers are assumed to be static across handshake renegotiations |
| 197 | +// i.e., a service instance either handles a message or not (irrespective of the handshake) |
| 198 | +// it panics if the message type is not defined in the CodeMap |
| 199 | +func (self *Peer) Register(msg interface{}, handler func(interface{}) error) uint { |
| 200 | + typ := reflect.TypeOf(msg) |
| 201 | + code, found := self.ct.messages[typ] |
| 202 | + if !found { |
| 203 | + panic(fmt.Sprintf("message type '%v' unknown ", typ)) |
| 204 | + } |
| 205 | + glog.V(logger.Debug).Infof("registered handle for %v %v", msg, typ) |
| 206 | + self.handlers[typ] = append(self.handlers[typ], handler) |
| 207 | + return code |
| 208 | +} |
| 209 | + |
| 210 | +// Run starts the forever loop that handles incoming messages |
| 211 | +// called within the p2p.Protocol#Run function |
| 212 | +func (self *Peer) Run() error { |
| 213 | + var err error |
| 214 | + for { |
| 215 | + _, err = self.handleIncoming() |
| 216 | + if err != nil { |
| 217 | + return err |
| 218 | + } |
| 219 | + } |
| 220 | +} |
| 221 | + |
| 222 | +// Drop disconnects a peer. |
| 223 | +// falls back to self.disconnect which is set as p2p.Peer#Disconnect except |
| 224 | +// for test peers where it calls p2p.MsgPipe#Close so that the readloop can terminate |
| 225 | +// TODO: may need to implement protocol drop only? don't want to kick off the peer |
| 226 | +// if they are useful for other protocols |
| 227 | +// overwrite Disconnect for testing, so that protocol readloop quits |
| 228 | +func (self *Peer) Drop() { |
| 229 | + self.disconnect() |
| 230 | +} |
| 231 | + |
| 232 | +// Send takes a message, encodes it in RLP, finds the right message code and sends the |
| 233 | +// message off to the peer |
| 234 | +// this low level call will be wrapped by libraries providing routed or broadcast sends |
| 235 | +// but often just used to forward and push messages to directly connected peers |
| 236 | +func (self *Peer) Send(msg interface{}) error { |
| 237 | + typ := reflect.TypeOf(msg) |
| 238 | + code, found := self.ct.messages[typ] |
| 239 | + if !found { |
| 240 | + return errorf(ErrInvalidMsgType, "%v", typ) |
| 241 | + } |
| 242 | + glog.V(logger.Debug).Infof("=> %v %v (%d)", msg, typ, code) |
| 243 | + err := p2p.Send(self.rw, uint64(code), msg) |
| 244 | + if err != nil { |
| 245 | + self.Drop() |
| 246 | + return errorf(ErrWrite, "(msg code: %v): %v", code, err) |
| 247 | + } |
| 248 | + return nil |
| 249 | +} |
| 250 | + |
| 251 | +// handleIncoming(code) |
| 252 | +// is called each cycle of the main forever loop that handles and dispatches incoming messages |
| 253 | +// if this returns an error the loop returns and the peer is disconnected with the error |
| 254 | +// checks message size, out-of-range message codes, handles decoding with reflection, |
| 255 | +// call handlers as callback onside |
| 256 | +func (self *Peer) handleIncoming() (interface{}, error) { |
| 257 | + msg, err := self.rw.ReadMsg() |
| 258 | + glog.V(logger.Debug).Infof("<= %v", msg) |
| 259 | + if err != nil { |
| 260 | + return nil, err |
| 261 | + } |
| 262 | + // make sure that the payload has been fully consumed |
| 263 | + defer msg.Discard() |
| 264 | + |
| 265 | + if msg.Size > uint32(self.ct.MaxMsgSize) { |
| 266 | + return nil, errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.ct.MaxMsgSize) |
| 267 | + } |
| 268 | + |
| 269 | + // check if the message code is correct |
| 270 | + maxMsgCode := uint(len(self.ct.messages)) |
| 271 | + if msg.Code >= uint64(maxMsgCode) { |
| 272 | + return nil, errorf(ErrInvalidMsgCode, "%v (>=%v)", msg.Code, maxMsgCode) |
| 273 | + } |
| 274 | + |
| 275 | + // it is safe to be unsafe here |
| 276 | + typ := self.ct.codes[msg.Code] |
| 277 | + val := reflect.New(typ) |
| 278 | + req := val.Elem() |
| 279 | + req.Set(reflect.Zero(typ)) |
| 280 | + if err := msg.Decode(val.Interface()); err != nil { |
| 281 | + return nil, errorf(ErrDecode, "<= %v: %v", msg, err) |
| 282 | + } |
| 283 | + glog.V(logger.Debug).Infof("<= %v %v (%d)", req, typ, msg.Code) |
| 284 | + |
| 285 | + // call the registered handler callbacks |
| 286 | + // a registered callback take the decoded message as argument as an interface |
| 287 | + // which the handler is supposed to cast to the appropriate type |
| 288 | + // it is entirely safe not to check the cast in the handler since the handler is |
| 289 | + // chosen based on the proper type in the first place |
| 290 | + for _, f := range self.handlers[typ] { |
| 291 | + glog.V(6).Infof("handler for %v", typ) |
| 292 | + err = f(req.Interface()) |
| 293 | + if err != nil { |
| 294 | + return nil, errorf(ErrHandler, "(msg code %v): %v", msg.Code, err) |
| 295 | + } |
| 296 | + } |
| 297 | + return req.Interface(), nil |
| 298 | +} |
| 299 | + |
| 300 | +// Handshake initiates a handshake on the peer connection |
| 301 | +// * the argument is the local handshake to be sent to the remote peer |
| 302 | +// * expects a remote handshake back of the same type |
| 303 | +// returns the remote hs and an error |
| 304 | +func (self *Peer) Handshake(hs interface{}) (interface{}, error) { |
| 305 | + typ := reflect.TypeOf(hs) |
| 306 | + _, found := self.ct.messages[typ] |
| 307 | + if !found { |
| 308 | + return nil, errorf(ErrLocalHandshake, "unknown handshake message type: %v", typ) |
| 309 | + } |
| 310 | + err := self.Send(hs) |
| 311 | + if err != nil { |
| 312 | + return nil, errorf(ErrLocalHandshake, "cannot send: %v", err) |
| 313 | + } |
| 314 | + // receiving and validating remote handshake, expect code |
| 315 | + rhs, err := self.handleIncoming() |
| 316 | + if err != nil { |
| 317 | + return nil, errorf(ErrRemoteHandshake, "'%v': %v", self.ct.Name, err) |
| 318 | + } |
| 319 | + return rhs, nil |
| 320 | +} |
0 commit comments