go.nanomsg.org/mangos/v3@v3.4.3-0.20240217232803-46464076f1f5/transport/conn.go (about) 1 // Copyright 2019 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 transport 16 17 import ( 18 "encoding/binary" 19 "io" 20 "net" 21 "sync" 22 23 "go.nanomsg.org/mangos/v3" 24 ) 25 26 // conn implements the Pipe interface on top of net.Conn. The 27 // assumption is that transports using this have similar wire protocols, 28 // and conn is meant to be used as a building block. 29 type conn struct { 30 c net.Conn 31 proto ProtocolInfo 32 open bool 33 options map[string]interface{} 34 maxrx int 35 sync.Mutex 36 } 37 38 // connipc is *almost* like a regular conn, but the IPC protocol insists 39 // on stuffing a leading byte (valued 1) in front of messages. This is for 40 // compatibility with nanomsg -- the value cannot ever be anything but 1. 41 type connipc struct { 42 conn 43 } 44 45 // Recv implements the TranPipe Recv method. The message received is expected 46 // as a 64-bit size (network byte order) followed by the message itself. 47 func (p *conn) Recv() (*Message, error) { 48 49 var sz int64 50 var err error 51 var msg *Message 52 53 if err = binary.Read(p.c, binary.BigEndian, &sz); err != nil { 54 return nil, err 55 } 56 57 // Limit messages to the maximum receive value, if not 58 // unlimited. This avoids a potential denial of service. 59 if sz < 0 || (p.maxrx > 0 && sz > int64(p.maxrx)) { 60 return nil, mangos.ErrTooLong 61 } 62 msg = mangos.NewMessage(int(sz)) 63 msg.Body = msg.Body[0:sz] 64 if _, err = io.ReadFull(p.c, msg.Body); err != nil { 65 msg.Free() 66 return nil, err 67 } 68 return msg, nil 69 } 70 71 // Send implements the Pipe Send method. The message is sent as a 64-bit 72 // size (network byte order) followed by the message itself. 73 func (p *conn) Send(msg *Message) error { 74 var buff = net.Buffers{} 75 76 // Serialize the length header 77 l := uint64(len(msg.Header) + len(msg.Body)) 78 lbyte := make([]byte, 8) 79 binary.BigEndian.PutUint64(lbyte, l) 80 81 // Attach the length header along with the actual header and body 82 buff = append(buff, lbyte, msg.Header, msg.Body) 83 84 if _, err := buff.WriteTo(p.c); err != nil { 85 return err 86 } 87 88 msg.Free() 89 return nil 90 } 91 92 // Close implements the Pipe Close method. 93 func (p *conn) Close() error { 94 p.Lock() 95 defer p.Unlock() 96 if p.open { 97 p.open = false 98 return p.c.Close() 99 } 100 return nil 101 } 102 103 func (p *conn) GetOption(n string) (interface{}, error) { 104 switch n { 105 case mangos.OptionMaxRecvSize: 106 return p.maxrx, nil 107 } 108 if v, ok := p.options[n]; ok { 109 return v, nil 110 } 111 return nil, mangos.ErrBadProperty 112 } 113 114 func (p *conn) SetOption(n string, v interface{}) { 115 switch n { 116 case mangos.OptionMaxRecvSize: 117 p.maxrx = v.(int) 118 } 119 p.options[n] = v 120 } 121 122 // ConnPipe is used for stream oriented transports. It is a superset of 123 // Pipe, but adds methods specific for transports. 124 type ConnPipe interface { 125 126 // SetOption just records an option that can be retrieved later. 127 SetOption(string, interface{}) 128 Pipe 129 } 130 131 // NewConnPipe allocates a new Pipe using the supplied net.Conn, and 132 // initializes it. It performs no negotiation -- use a Handshaker to 133 // arrange for that. 134 // 135 // Stream oriented transports can utilize this to implement a Transport. 136 // The implementation will also need to implement PipeDialer, PipeAccepter, 137 // and the Transport enclosing structure. Using this layered interface, 138 // the implementation needn't bother concerning itself with passing actual 139 // SP messages once the lower layer connection is established. 140 func NewConnPipe(c net.Conn, proto ProtocolInfo) ConnPipe { 141 p := &conn{ 142 c: c, 143 proto: proto, 144 options: make(map[string]interface{}), 145 } 146 147 p.options[mangos.OptionMaxRecvSize] = 0 148 p.options[mangos.OptionLocalAddr] = c.LocalAddr() 149 p.options[mangos.OptionRemoteAddr] = c.RemoteAddr() 150 151 return p 152 } 153 154 // connHeader is exchanged during the initial handshake. 155 type connHeader struct { 156 Zero byte // must be zero 157 S byte // 'S' 158 P byte // 'P' 159 Version byte // only zero at present 160 Proto uint16 161 Reserved uint16 // always zero at present 162 } 163 164 // handshake establishes an SP connection between peers. Both sides must 165 // send the header, then both sides must wait for the peer's header. 166 // As a side effect, the peer's protocol number is stored in the conn. 167 // Also, various properties are initialized. 168 func (p *conn) handshake() error { 169 var err error 170 171 h := connHeader{S: 'S', P: 'P', Proto: p.proto.Self} 172 if err = binary.Write(p.c, binary.BigEndian, &h); err != nil { 173 return err 174 } 175 if err = binary.Read(p.c, binary.BigEndian, &h); err != nil { 176 _ = p.c.Close() 177 return err 178 } 179 if h.Zero != 0 || h.S != 'S' || h.P != 'P' || h.Reserved != 0 { 180 _ = p.c.Close() 181 return mangos.ErrBadHeader 182 } 183 // The only version number we support at present is "0", at offset 3. 184 if h.Version != 0 { 185 _ = p.c.Close() 186 return mangos.ErrBadVersion 187 } 188 189 // The protocol number lives as 16-bits (big-endian) at offset 4. 190 if h.Proto != p.proto.Peer { 191 _ = p.c.Close() 192 return mangos.ErrBadProto 193 } 194 p.open = true 195 return nil 196 } 197 198 type connHandshakerPipe interface { 199 handshake() error 200 201 Pipe 202 } 203 204 type connHandshakerItem struct { 205 c connHandshakerPipe 206 e error 207 } 208 type connHandshaker struct { 209 workq map[connHandshakerPipe]bool 210 doneq []*connHandshakerItem 211 closed bool 212 cv *sync.Cond 213 sync.Mutex 214 } 215 216 // NewConnHandshaker returns a Handshaker that works with 217 // Pipes created via NewConnPipe or NewConnPipeIPC. 218 func NewConnHandshaker() Handshaker { 219 h := &connHandshaker{ 220 workq: make(map[connHandshakerPipe]bool), 221 closed: false, 222 } 223 h.cv = sync.NewCond(h) 224 return h 225 } 226 227 func (h *connHandshaker) Wait() (Pipe, error) { 228 h.Lock() 229 defer h.Unlock() 230 for len(h.doneq) == 0 && !h.closed { 231 h.cv.Wait() 232 } 233 if h.closed { 234 return nil, mangos.ErrClosed 235 } 236 item := h.doneq[0] 237 h.doneq = h.doneq[1:] 238 return item.c, item.e 239 } 240 241 func (h *connHandshaker) Start(p Pipe) { 242 // If the following type assertion fails, then its a software bug. 243 conn := p.(connHandshakerPipe) 244 h.Lock() 245 h.workq[conn] = true 246 h.Unlock() 247 go h.worker(conn) 248 } 249 250 func (h *connHandshaker) Close() { 251 h.Lock() 252 h.closed = true 253 h.cv.Broadcast() 254 for conn := range h.workq { 255 _ = conn.Close() 256 } 257 for len(h.doneq) != 0 { 258 item := h.doneq[0] 259 h.doneq = h.doneq[1:] 260 if item.c != nil { 261 _ = item.c.Close() 262 } 263 } 264 h.Unlock() 265 } 266 267 func (h *connHandshaker) worker(conn connHandshakerPipe) { 268 item := &connHandshakerItem{c: conn} 269 item.e = conn.handshake() 270 h.Lock() 271 defer h.Unlock() 272 273 delete(h.workq, conn) 274 275 if item.e != nil { 276 _ = item.c.Close() 277 item.c = nil 278 } else if h.closed { 279 item.e = mangos.ErrClosed 280 _ = item.c.Close() 281 } 282 h.doneq = append(h.doneq, item) 283 h.cv.Broadcast() 284 }