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  }