github.com/gdamore/mangos@v1.4.0/conn.go (about) 1 // Copyright 2018 The Mangos Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use 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 mangos 16 17 import ( 18 "encoding/binary" 19 "io" 20 "net" 21 "sync" 22 ) 23 24 // conn implements the Pipe interface on top of net.Conn. The 25 // assumption is that transports using this have similar wire protocols, 26 // and conn is meant to be used as a building block. 27 type conn struct { 28 c net.Conn 29 proto Protocol 30 sock Socket 31 open bool 32 props map[string]interface{} 33 maxrx int64 34 sync.Mutex 35 } 36 37 // connipc is *almost* like a regular conn, but the IPC protocol insists 38 // on stuffing a leading byte (valued 1) in front of messages. This is for 39 // compatibility with nanomsg -- the value cannot ever be anything but 1. 40 type connipc struct { 41 conn 42 } 43 44 // Recv implements the Pipe Recv method. The message received is expected as 45 // a 64-bit size (network byte order) followed by the message itself. 46 func (p *conn) Recv() (*Message, error) { 47 48 var sz int64 49 var err error 50 var msg *Message 51 52 if err = binary.Read(p.c, binary.BigEndian, &sz); err != nil { 53 return nil, err 54 } 55 56 // Limit messages to the maximum receive value, if not 57 // unlimited. This avoids a potential denaial of service. 58 if sz < 0 || (p.maxrx > 0 && sz > p.maxrx) { 59 return nil, ErrTooLong 60 } 61 msg = NewMessage(int(sz)) 62 msg.Body = msg.Body[0:sz] 63 if _, err = io.ReadFull(p.c, msg.Body); err != nil { 64 msg.Free() 65 return nil, err 66 } 67 return msg, nil 68 } 69 70 // Send implements the Pipe Send method. The message is sent as a 64-bit 71 // size (network byte order) followed by the message itself. 72 func (p *conn) Send(msg *Message) error { 73 74 l := uint64(len(msg.Header) + len(msg.Body)) 75 76 if msg.Expired() { 77 msg.Free() 78 return nil 79 } 80 81 // send length header 82 if err := binary.Write(p.c, binary.BigEndian, l); err != nil { 83 return err 84 } 85 if _, err := p.c.Write(msg.Header); err != nil { 86 return err 87 } 88 // hope this works 89 if _, err := p.c.Write(msg.Body); err != nil { 90 return err 91 } 92 msg.Free() 93 return nil 94 } 95 96 // LocalProtocol returns our local protocol number. 97 func (p *conn) LocalProtocol() uint16 { 98 return p.proto.Number() 99 } 100 101 // RemoteProtocol returns our peer's protocol number. 102 func (p *conn) RemoteProtocol() uint16 { 103 return p.proto.PeerNumber() 104 } 105 106 // Close implements the Pipe Close method. 107 func (p *conn) Close() error { 108 p.Lock() 109 defer p.Unlock() 110 if p.IsOpen() { 111 p.open = false 112 return p.c.Close() 113 } 114 return nil 115 } 116 117 // IsOpen implements the PipeIsOpen method. 118 func (p *conn) IsOpen() bool { 119 return p.open 120 } 121 122 func (p *conn) GetProp(n string) (interface{}, error) { 123 if v, ok := p.props[n]; ok { 124 return v, nil 125 } 126 return nil, ErrBadProperty 127 } 128 129 // NewConnPipe allocates a new Pipe using the supplied net.Conn, and 130 // initializes it. It performs the handshake required at the SP layer, 131 // only returning the Pipe once the SP layer negotiation is complete. 132 // 133 // Stream oriented transports can utilize this to implement a Transport. 134 // The implementation will also need to implement PipeDialer, PipeAccepter, 135 // and the Transport enclosing structure. Using this layered interface, 136 // the implementation needn't bother concerning itself with passing actual 137 // SP messages once the lower layer connection is established. 138 func NewConnPipe(c net.Conn, sock Socket, props ...interface{}) (Pipe, error) { 139 p := &conn{c: c, proto: sock.GetProtocol(), sock: sock} 140 141 if err := p.handshake(props); err != nil { 142 return nil, err 143 } 144 145 return p, nil 146 } 147 148 // connHeader is exchanged during the initial handshake. 149 type connHeader struct { 150 Zero byte // must be zero 151 S byte // 'S' 152 P byte // 'P' 153 Version byte // only zero at present 154 Proto uint16 155 Rsvd uint16 // always zero at present 156 } 157 158 // handshake establishes an SP connection between peers. Both sides must 159 // send the header, then both sides must wait for the peer's header. 160 // As a side effect, the peer's protocol number is stored in the conn. 161 // Also, various properties are initialized. 162 func (p *conn) handshake(props []interface{}) error { 163 var err error 164 165 p.props = make(map[string]interface{}) 166 p.props[PropLocalAddr] = p.c.LocalAddr() 167 p.props[PropRemoteAddr] = p.c.RemoteAddr() 168 169 for len(props) >= 2 { 170 switch name := props[0].(type) { 171 case string: 172 p.props[name] = props[1] 173 default: 174 return ErrBadProperty 175 } 176 props = props[2:] 177 } 178 179 if v, e := p.sock.GetOption(OptionMaxRecvSize); e == nil { 180 // socket guarantees this is an integer 181 p.maxrx = int64(v.(int)) 182 } 183 184 h := connHeader{S: 'S', P: 'P', Proto: p.proto.Number()} 185 if err = binary.Write(p.c, binary.BigEndian, &h); err != nil { 186 return err 187 } 188 if err = binary.Read(p.c, binary.BigEndian, &h); err != nil { 189 p.c.Close() 190 return err 191 } 192 if h.Zero != 0 || h.S != 'S' || h.P != 'P' || h.Rsvd != 0 { 193 p.c.Close() 194 return ErrBadHeader 195 } 196 // The only version number we support at present is "0", at offset 3. 197 if h.Version != 0 { 198 p.c.Close() 199 return ErrBadVersion 200 } 201 202 // The protocol number lives as 16-bits (big-endian) at offset 4. 203 if h.Proto != p.proto.PeerNumber() { 204 p.c.Close() 205 return ErrBadProto 206 } 207 p.open = true 208 return nil 209 }