nanomsg.org/go/mangos/v2@v2.0.9-0.20200203084354-8a092611e461/macat/macat.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 macat implements a command-line interface to send and receive
    16  // data via the mangos implementation of the SP (nanomsg) protocols.  It is
    17  // designed to be suitable for use as a drop-in replacement for nanocat(1).`
    18  package macat
    19  
    20  import (
    21  	"bufio"
    22  	"crypto/tls"
    23  	"crypto/x509"
    24  	"encoding/binary"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"os"
    30  	"strconv"
    31  	"strings"
    32  	"time"
    33  
    34  	"github.com/gdamore/optopia"
    35  	"nanomsg.org/go/mangos/v2"
    36  	"nanomsg.org/go/mangos/v2/protocol/bus"
    37  	"nanomsg.org/go/mangos/v2/protocol/pair"
    38  	"nanomsg.org/go/mangos/v2/protocol/pub"
    39  	"nanomsg.org/go/mangos/v2/protocol/pull"
    40  	"nanomsg.org/go/mangos/v2/protocol/push"
    41  	"nanomsg.org/go/mangos/v2/protocol/rep"
    42  	"nanomsg.org/go/mangos/v2/protocol/req"
    43  	"nanomsg.org/go/mangos/v2/protocol/respondent"
    44  	"nanomsg.org/go/mangos/v2/protocol/star"
    45  	"nanomsg.org/go/mangos/v2/protocol/sub"
    46  	"nanomsg.org/go/mangos/v2/protocol/surveyor"
    47  	"nanomsg.org/go/mangos/v2/transport/all"
    48  )
    49  
    50  // Duration is our internal duration, which parses bare numbers as seconds.
    51  // Otherwise it's just a time.Duration.
    52  type Duration time.Duration
    53  
    54  // UnmarshalText implements the encoding.TextUnmarshaller.  It parses
    55  // bare integers as strings for legacy reasons.
    56  func (d *Duration) UnmarshalText(b []byte) error {
    57  	if val, err := strconv.Atoi(string(b)); err == nil {
    58  		*d = Duration(val) * Duration(time.Second)
    59  		return nil
    60  	}
    61  	if dur, err := time.ParseDuration(string(b)); err == nil {
    62  		*d = Duration(dur)
    63  		return nil
    64  	}
    65  	return errors.New("value is not a duration")
    66  }
    67  
    68  // App is an instance of the macat application.
    69  type App struct {
    70  	verbose       int
    71  	dialAddr      []string
    72  	bindAddr      []string
    73  	subscriptions []string
    74  	recvTimeout   Duration
    75  	sendTimeout   Duration
    76  	sendInterval  Duration
    77  	sendDelay     Duration
    78  	sendData      []byte
    79  	printFormat   string
    80  	sock          mangos.Socket
    81  	tlsCfg        tls.Config
    82  	certFile      string
    83  	keyFile       string
    84  	noVerifyTLS   bool
    85  	options       *optopia.Options
    86  	count         int
    87  	countSet      bool
    88  	stdOut        io.Writer
    89  }
    90  
    91  func mustSucceed(e error) {
    92  	if e != nil {
    93  		panic(e.Error())
    94  	}
    95  }
    96  
    97  func (a *App) setSocket(f func() (mangos.Socket, error)) error {
    98  	var err error
    99  	if a.sock != nil {
   100  		return errors.New("protocol already selected")
   101  	}
   102  	a.sock, err = f()
   103  	mustSucceed(err)
   104  	all.AddTransports(a.sock)
   105  	return nil
   106  }
   107  
   108  func (a *App) addDial(addr string) error {
   109  	if !strings.Contains(addr, "://") {
   110  		return errors.New("invalid address format")
   111  	}
   112  	a.dialAddr = append(a.dialAddr, addr)
   113  	return nil
   114  }
   115  
   116  func (a *App) addBind(addr string) error {
   117  	if !strings.Contains(addr, "://") {
   118  		return errors.New("invalid address format")
   119  	}
   120  	a.bindAddr = append(a.bindAddr, addr)
   121  	return nil
   122  }
   123  
   124  func (a *App) addBindIPC(path string) error {
   125  	return a.addBind("ipc://" + path)
   126  }
   127  
   128  func (a *App) addDialIPC(path string) error {
   129  	return a.addDial("ipc://" + path)
   130  }
   131  
   132  func (a *App) addBindLocal(port string) error {
   133  	return a.addBind("tcp://127.0.0.1:" + port)
   134  }
   135  
   136  func (a *App) addDialLocal(port string) error {
   137  	return a.addDial("tcp://127.0.0.1:" + port)
   138  }
   139  
   140  func (a *App) addSub(sub string) error {
   141  	a.subscriptions = append(a.subscriptions, sub)
   142  	return nil
   143  }
   144  
   145  func (a *App) setSendData(data string) error {
   146  	if a.sendData != nil {
   147  		return errors.New("data or file already set")
   148  	}
   149  	a.sendData = []byte(data)
   150  	return nil
   151  }
   152  
   153  func (a *App) setSendFile(path string) error {
   154  	if a.sendData != nil {
   155  		return errors.New("data or file already set")
   156  	}
   157  	var err error
   158  	a.sendData, err = ioutil.ReadFile(path)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	return nil
   163  }
   164  
   165  func (a *App) setFormat(f string) error {
   166  	if len(a.printFormat) > 0 {
   167  		return errors.New("output format already set")
   168  	}
   169  	switch f {
   170  	case "no":
   171  	case "raw":
   172  	case "ascii":
   173  	case "quoted":
   174  	case "msgpack":
   175  	default:
   176  		return errors.New("invalid format type: " + f)
   177  	}
   178  	a.printFormat = f
   179  	return nil
   180  }
   181  
   182  func (a *App) setCert(path string) error {
   183  	if len(a.certFile) != 0 {
   184  		return errors.New("certificate file already set")
   185  	}
   186  	a.certFile = path
   187  	return nil
   188  }
   189  
   190  func (a *App) setKey(path string) error {
   191  	if len(a.keyFile) != 0 {
   192  		return errors.New("key file already set")
   193  	}
   194  	a.keyFile = path
   195  	return nil
   196  }
   197  
   198  func (a *App) setCaCert(path string) error {
   199  	if a.tlsCfg.RootCAs != nil {
   200  		return errors.New("cacert already set")
   201  	}
   202  
   203  	pem, err := ioutil.ReadFile(path)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	a.tlsCfg.RootCAs = x509.NewCertPool()
   208  	if !a.tlsCfg.RootCAs.AppendCertsFromPEM(pem) {
   209  		return errors.New("unable to load CA certs")
   210  	}
   211  	a.tlsCfg.ClientCAs = a.tlsCfg.RootCAs
   212  	return nil
   213  }
   214  
   215  func (a *App) getOptions() []*optopia.Option {
   216  	return []*optopia.Option{
   217  		{
   218  			Long:  "verbose",
   219  			Short: 'v',
   220  			Help:  "Increase verbosity",
   221  			Handle: func(string) error {
   222  				a.verbose++
   223  				return nil
   224  			},
   225  		},
   226  		{
   227  			Long:  "silent",
   228  			Short: 'q',
   229  			Help:  "Decrease verbosity",
   230  			Handle: func(string) error {
   231  				a.verbose--
   232  				return nil
   233  			},
   234  		},
   235  		{
   236  			Long: "push",
   237  			Help: "Use PUSH socket type",
   238  			Handle: func(string) error {
   239  				return a.setSocket(push.NewSocket)
   240  			},
   241  		},
   242  		{
   243  			Long: "pull",
   244  			Help: "Use PULL socket type",
   245  			Handle: func(string) error {
   246  				return a.setSocket(pull.NewSocket)
   247  			},
   248  		},
   249  		{
   250  			Long: "pub",
   251  			Help: "Use PUB socket type",
   252  			Handle: func(string) error {
   253  				return a.setSocket(pub.NewSocket)
   254  			},
   255  		},
   256  		{
   257  			Long: "sub",
   258  			Help: "Use SUB socket type",
   259  			Handle: func(string) error {
   260  				return a.setSocket(sub.NewSocket)
   261  			},
   262  		},
   263  		{
   264  			Long: "req",
   265  			Help: "Use REQ socket type",
   266  			Handle: func(string) error {
   267  				return a.setSocket(req.NewSocket)
   268  			},
   269  		},
   270  		{
   271  			Long: "rep",
   272  			Help: "Use REP socket type",
   273  			Handle: func(string) error {
   274  				return a.setSocket(rep.NewSocket)
   275  			},
   276  		},
   277  		{
   278  			Long: "surveyor",
   279  			Help: "Use SURVEYOR socket type",
   280  			Handle: func(string) error {
   281  				return a.setSocket(surveyor.NewSocket)
   282  			},
   283  		},
   284  		{
   285  			Long: "respondent",
   286  			Help: "Use RESPONDENT socket type",
   287  			Handle: func(string) error {
   288  				return a.setSocket(respondent.NewSocket)
   289  			},
   290  		},
   291  		{
   292  			Long: "bus",
   293  			Help: "Use BUS socket type",
   294  			Handle: func(string) error {
   295  				return a.setSocket(bus.NewSocket)
   296  			},
   297  		},
   298  		{
   299  			Long: "pair",
   300  			Help: "Use PAIR socket type",
   301  			Handle: func(string) error {
   302  				return a.setSocket(pair.NewSocket)
   303  			},
   304  		},
   305  		{
   306  			Long: "star",
   307  			Help: "Use STAR socket type",
   308  			Handle: func(string) error {
   309  				return a.setSocket(star.NewSocket)
   310  			},
   311  		},
   312  		{
   313  			Long:    "bind",
   314  			Help:    "Bind socket to ADDR",
   315  			ArgName: "ADDR",
   316  			HasArg:  true,
   317  			Handle:  a.addBind,
   318  		},
   319  		{
   320  			Long:    "connect",
   321  			Help:    "Connect socket to ADDR",
   322  			ArgName: "ADDR",
   323  			HasArg:  true,
   324  			Handle:  a.addDial,
   325  		},
   326  		{
   327  			Long:    "bind-ipc",
   328  			Short:   'X',
   329  			Help:    "Bind socket to IPC PATH",
   330  			ArgName: "PATH",
   331  			HasArg:  true,
   332  			Handle:  a.addBindIPC,
   333  		},
   334  		{
   335  			Long:    "connect-ipc",
   336  			Short:   'x',
   337  			Help:    "Connect socket to IPC PATH",
   338  			ArgName: "PATH",
   339  			HasArg:  true,
   340  			Handle:  a.addDialIPC,
   341  		},
   342  		{
   343  			Long:    "bind-local",
   344  			Short:   'L',
   345  			Help:    "Bind socket to localhost PORT",
   346  			ArgName: "PORT",
   347  			HasArg:  true,
   348  			Handle:  a.addBindLocal,
   349  		},
   350  		{
   351  			Long:    "connect-local",
   352  			Short:   'l',
   353  			Help:    "Connect socket to localhost PATH",
   354  			ArgName: "PORT",
   355  			HasArg:  true,
   356  			Handle:  a.addDialLocal,
   357  		},
   358  		{
   359  			Long:    "subscribe",
   360  			Help:    "Subscribe to PREFIX (default is wildcard)",
   361  			ArgName: "PREFIX",
   362  			HasArg:  true,
   363  			Handle:  a.addSub,
   364  		},
   365  		{
   366  			Long:    "count",
   367  			ArgName: "COUNT",
   368  			HasArg:  true,
   369  			ArgP:    &a.count,
   370  			Help:    "Repeat COUNT times",
   371  			Handle: func(string) error {
   372  				a.countSet = true
   373  				return nil
   374  			},
   375  		},
   376  		{
   377  			Long:    "recv-timeout",
   378  			Help:    "Set receive timeout",
   379  			ArgName: "DUR",
   380  			HasArg:  true,
   381  			ArgP:    &a.recvTimeout,
   382  		},
   383  		{
   384  			Long:    "send-timeout",
   385  			Help:    "Set send timeout",
   386  			ArgName: "DUR",
   387  			HasArg:  true,
   388  			ArgP:    &a.sendTimeout,
   389  		},
   390  		{
   391  			Long:    "send-delay",
   392  			Short:   'd',
   393  			Help:    "Set send delay",
   394  			ArgName: "DUR",
   395  			HasArg:  true,
   396  			ArgP:    &a.sendDelay,
   397  		},
   398  		{
   399  			Long:    "send-interval",
   400  			Short:   'i',
   401  			Help:    "Set send interval",
   402  			ArgName: "DUR",
   403  			HasArg:  true,
   404  			ArgP:    &a.sendInterval,
   405  			Handle: func(string) error {
   406  				if !a.countSet {
   407  					a.count = -1
   408  				}
   409  				return nil
   410  			},
   411  		},
   412  		{
   413  			Long: "raw",
   414  			Help: "Raw output, no delimiters",
   415  			Handle: func(string) error {
   416  				return a.setFormat("raw")
   417  			},
   418  		},
   419  		{
   420  			Long:  "ascii",
   421  			Short: 'A',
   422  			Help:  "ASCII output, one per line",
   423  			Handle: func(string) error {
   424  				return a.setFormat("ascii")
   425  			},
   426  		},
   427  		{
   428  			Long:  "quoted",
   429  			Short: 'Q',
   430  			Help:  "quoted output, one per line",
   431  			Handle: func(string) error {
   432  				return a.setFormat("quoted")
   433  			},
   434  		},
   435  		{
   436  			Long: "msgpack",
   437  			Help: "MsgPack binary output (see msgpack.org)",
   438  			Handle: func(string) error {
   439  				return a.setFormat("msgpack")
   440  			},
   441  		},
   442  		{
   443  			Long:   "format",
   444  			Help:   "Set output format to FORMAT",
   445  			Handle: a.setFormat,
   446  			HasArg: true,
   447  		},
   448  		{
   449  			Long:    "data",
   450  			Short:   'D',
   451  			Help:    "Data to send",
   452  			ArgName: "DATA",
   453  			HasArg:  true,
   454  			Handle:  a.setSendData,
   455  		},
   456  		{
   457  			Long:    "file",
   458  			Short:   'F',
   459  			Help:    "Send contents of FILE",
   460  			ArgName: "FILE",
   461  			HasArg:  true,
   462  			Handle:  a.setSendFile,
   463  		},
   464  		{
   465  			Long:    "cert",
   466  			Short:   'E',
   467  			Help:    "Use self certificate in FILE for TLS",
   468  			ArgName: "FILE",
   469  			HasArg:  true,
   470  			Handle:  a.setCert,
   471  		},
   472  		{
   473  			Long:    "key",
   474  			Help:    "Use private key in FILE for TLS",
   475  			ArgName: "FILE",
   476  			HasArg:  true,
   477  			Handle:  a.setKey,
   478  		},
   479  		{
   480  			Long:    "cacert",
   481  			Help:    "Use CA certificate(s) in FILE for TLS",
   482  			ArgName: "FILE",
   483  			HasArg:  true,
   484  			Handle:  a.setCaCert,
   485  		},
   486  		{
   487  			Long:  "insecure",
   488  			Short: 'k',
   489  			Help:  "Do not validate TLS peer",
   490  			Handle: func(string) error {
   491  				a.noVerifyTLS = true
   492  				return nil
   493  			},
   494  		},
   495  	}
   496  }
   497  
   498  // Initialize initializes an instance of the app.
   499  func (a *App) Initialize() {
   500  	a.stdOut = os.Stdout
   501  	a.sock = nil
   502  	a.recvTimeout = Duration(-1)
   503  	a.sendTimeout = Duration(-1)
   504  	a.sendInterval = Duration(-1)
   505  	a.sendDelay = Duration(-1)
   506  	a.count = 1
   507  	a.options = &optopia.Options{}
   508  	opts := a.getOptions()
   509  	mustSucceed(a.options.Add(opts...))
   510  }
   511  
   512  /*
   513  The macat command is a command-line interface to send and receive
   514  data via the mangos implementation of the SP (nanomsg) protocols.  It is
   515  designed to be suitable for use as a drop-in replacement for nanocat(1).`
   516  
   517  Summary = "command line interface to the mangos messaging library"
   518  */
   519  
   520  // Help returns a help string.
   521  func (a *App) Help() string {
   522  	return a.options.Help()
   523  }
   524  
   525  func (a *App) printMsg(msg *mangos.Message) {
   526  	if a.printFormat == "no" {
   527  		return
   528  	}
   529  	bw := bufio.NewWriter(a.stdOut)
   530  	switch a.printFormat {
   531  	case "raw":
   532  		_, _ = bw.Write(msg.Body)
   533  	case "ascii":
   534  		for i := 0; i < len(msg.Body); i++ {
   535  			if strconv.IsPrint(rune(msg.Body[i])) {
   536  				_ = bw.WriteByte(msg.Body[i])
   537  			} else {
   538  				_ = bw.WriteByte('.')
   539  			}
   540  		}
   541  		_, _ = bw.WriteString("\n")
   542  	case "quoted":
   543  		for i := 0; i < len(msg.Body); i++ {
   544  			switch msg.Body[i] {
   545  			case '\n':
   546  				_, _ = bw.WriteString("\\n")
   547  			case '\r':
   548  				_, _ = bw.WriteString("\\r")
   549  			case '\\':
   550  				_, _ = bw.WriteString("\\\\")
   551  			case '"':
   552  				_, _ = bw.WriteString("\\\"")
   553  			default:
   554  				if strconv.IsPrint(rune(msg.Body[i])) {
   555  					_ = bw.WriteByte(msg.Body[i])
   556  				} else {
   557  					_, _ = bw.WriteString(fmt.Sprintf("\\x%02x",
   558  						msg.Body[i]))
   559  				}
   560  			}
   561  		}
   562  		_, _ = bw.WriteString("\n")
   563  
   564  	case "msgpack":
   565  		enc := make([]byte, 5)
   566  		switch {
   567  		case len(msg.Body) < 256:
   568  			enc = enc[:2]
   569  			enc[0] = 0xc4
   570  			enc[1] = byte(len(msg.Body))
   571  
   572  		case len(msg.Body) < 65536:
   573  			enc = enc[:3]
   574  			enc[0] = 0xc5
   575  			binary.BigEndian.PutUint16(enc[1:], uint16(len(msg.Body)))
   576  		default:
   577  			enc = enc[:5]
   578  			enc[0] = 0xc6
   579  			binary.BigEndian.PutUint32(enc[1:], uint32(len(msg.Body)))
   580  		}
   581  		_, _ = bw.Write(enc)
   582  		_, _ = bw.Write(msg.Body)
   583  	}
   584  	_ = bw.Flush()
   585  }
   586  
   587  func (a *App) recvLoop() error {
   588  	sock := a.sock
   589  	for {
   590  		msg, err := sock.RecvMsg()
   591  		switch err {
   592  		case mangos.ErrProtoState:
   593  			return nil // Survey completion
   594  		case mangos.ErrRecvTimeout:
   595  			return nil
   596  		case nil:
   597  		default:
   598  			return fmt.Errorf("recv: %v", err)
   599  		}
   600  		a.printMsg(msg)
   601  		msg.Free()
   602  	}
   603  }
   604  
   605  func (a *App) sendLoop() error {
   606  	sock := a.sock
   607  	count := a.count
   608  	if a.sendData == nil {
   609  		return errors.New("no data to send")
   610  	}
   611  	for {
   612  		switch count {
   613  		case -1:
   614  		case 0:
   615  			return nil
   616  		default:
   617  			count--
   618  		}
   619  		msg := mangos.NewMessage(len(a.sendData))
   620  		msg.Body = append(msg.Body, a.sendData...)
   621  		err := sock.SendMsg(msg)
   622  
   623  		if err != nil {
   624  			return fmt.Errorf("send: %v", err)
   625  		}
   626  
   627  		if a.sendInterval >= 0 && count != 0 {
   628  			time.Sleep(time.Duration(a.sendInterval))
   629  		}
   630  	}
   631  }
   632  
   633  func (a *App) sendRecvLoop() error {
   634  	sock := a.sock
   635  	count := a.count
   636  	for {
   637  		switch count {
   638  		case -1:
   639  		case 0:
   640  			return nil
   641  		default:
   642  			count--
   643  		}
   644  
   645  		msg := mangos.NewMessage(len(a.sendData))
   646  		msg.Body = append(msg.Body, a.sendData...)
   647  		err := sock.SendMsg(msg)
   648  
   649  		if err != nil {
   650  			return fmt.Errorf("send: %v", err)
   651  		}
   652  
   653  		if a.sendInterval < 0 {
   654  			a.count++
   655  			return a.recvLoop()
   656  		}
   657  
   658  		now := time.Now()
   659  
   660  		// maximum wait time is upper bound of recvTimeout and
   661  		// sendInterval
   662  
   663  		if a.recvTimeout < 0 || a.recvTimeout > a.sendInterval {
   664  			_ = sock.SetOption(mangos.OptionRecvDeadline,
   665  				time.Duration(a.sendInterval))
   666  		}
   667  		msg, err = sock.RecvMsg()
   668  		switch err {
   669  		case mangos.ErrProtoState:
   670  		case mangos.ErrRecvTimeout:
   671  		case nil:
   672  			a.printMsg(msg)
   673  			msg.Free()
   674  		default:
   675  			return fmt.Errorf("recv: %v", err)
   676  		}
   677  		if count != 0 {
   678  			time.Sleep(time.Duration(a.sendInterval) - time.Since(now))
   679  		}
   680  	}
   681  }
   682  
   683  func (a *App) replyLoop() error {
   684  	sock := a.sock
   685  	if a.sendData == nil {
   686  		return a.recvLoop()
   687  	}
   688  	for {
   689  		msg, err := sock.RecvMsg()
   690  		switch err {
   691  		case mangos.ErrRecvTimeout:
   692  			return nil
   693  		case nil:
   694  		default:
   695  			return fmt.Errorf("recv: %v", err)
   696  		}
   697  		a.printMsg(msg)
   698  		msg.Free()
   699  
   700  		msg = mangos.NewMessage(len(a.sendData))
   701  		msg.Body = append(msg.Body, a.sendData...)
   702  		err = sock.SendMsg(msg)
   703  
   704  		if err != nil {
   705  			return fmt.Errorf("send: %v", err)
   706  		}
   707  	}
   708  }
   709  
   710  func (a *App) cleanup() {
   711  	if a.sock != nil {
   712  		time.Sleep(time.Millisecond * 20) // for draining
   713  		_ = a.sock.Close()
   714  	}
   715  }
   716  
   717  // Run runs the instance of the application.
   718  func (a *App) Run(args ...string) error {
   719  
   720  	defer a.cleanup()
   721  
   722  	args, e := a.options.Parse(args)
   723  	if e != nil {
   724  		return e
   725  	}
   726  	if len(args) > 0 {
   727  		return fmt.Errorf("usage: extra arguments")
   728  	}
   729  
   730  	if a.certFile != "" {
   731  		if a.keyFile == "" {
   732  			a.keyFile = a.certFile
   733  		}
   734  		c, e := tls.LoadX509KeyPair(a.certFile, a.keyFile)
   735  		if e != nil {
   736  			return fmt.Errorf("failed loading cert/key: %v", e)
   737  		}
   738  		a.tlsCfg.Certificates = make([]tls.Certificate, 0, 1)
   739  		a.tlsCfg.Certificates = append(a.tlsCfg.Certificates, c)
   740  	}
   741  	if a.tlsCfg.RootCAs != nil {
   742  		a.tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert
   743  		a.tlsCfg.InsecureSkipVerify = false
   744  	} else {
   745  		a.tlsCfg.ClientAuth = tls.NoClientCert
   746  		a.tlsCfg.InsecureSkipVerify = a.noVerifyTLS
   747  	}
   748  
   749  	if a.sock == nil {
   750  		return errors.New("protocol not specified")
   751  	}
   752  
   753  	if len(a.bindAddr) == 0 && len(a.dialAddr) == 0 {
   754  		return errors.New("no address specified")
   755  	}
   756  
   757  	if a.sock.Info().Self != mangos.ProtoSub {
   758  		if len(a.subscriptions) > 0 {
   759  			return errors.New("subscription only valid with SUB protocol")
   760  		}
   761  	} else {
   762  		if len(a.subscriptions) > 0 {
   763  			for i := range a.subscriptions {
   764  				err := a.sock.SetOption(mangos.OptionSubscribe,
   765  					[]byte(a.subscriptions[i]))
   766  				mustSucceed(err)
   767  			}
   768  		} else {
   769  			err := a.sock.SetOption(mangos.OptionSubscribe, []byte{})
   770  			mustSucceed(err)
   771  		}
   772  	}
   773  
   774  	for _, addr := range a.bindAddr {
   775  		var opts = make(map[string]interface{})
   776  
   777  		// TLS addresses require a certificate to be supplied.
   778  		if strings.HasPrefix(addr, "tls") ||
   779  			strings.HasPrefix(addr, "wss") {
   780  			if len(a.tlsCfg.Certificates) == 0 {
   781  				return errors.New("no server cert specified")
   782  			}
   783  			opts[mangos.OptionTLSConfig] = &a.tlsCfg
   784  		}
   785  		err := a.sock.ListenOptions(addr, opts)
   786  		if err != nil {
   787  			return fmt.Errorf("bind(%s): %v", addr, err)
   788  		}
   789  	}
   790  
   791  	for _, addr := range a.dialAddr {
   792  		var opts = make(map[string]interface{})
   793  
   794  		if strings.HasPrefix(addr, "tls") ||
   795  			strings.HasPrefix(addr, "wss") {
   796  			if a.tlsCfg.RootCAs == nil && !a.noVerifyTLS {
   797  				return errors.New("no CA cert specified")
   798  			}
   799  			opts[mangos.OptionTLSConfig] = &a.tlsCfg
   800  		}
   801  		err := a.sock.DialOptions(addr, opts)
   802  		if err != nil {
   803  			return fmt.Errorf("dial(%s): %v", addr, err)
   804  		}
   805  	}
   806  
   807  	// XXX: ugly hack - work around TCP slow start
   808  	time.Sleep(time.Millisecond * 20)
   809  	time.Sleep(time.Duration(a.sendDelay))
   810  
   811  	if dur := time.Duration(a.recvTimeout); dur >= 0 {
   812  		_ = a.sock.SetOption(mangos.OptionRecvDeadline, dur)
   813  	}
   814  	if dur := time.Duration(a.sendTimeout); dur >= 0 {
   815  		_ = a.sock.SetOption(mangos.OptionSendDeadline, dur)
   816  	}
   817  
   818  	// Start main processing
   819  	switch a.sock.Info().Self {
   820  
   821  	case mangos.ProtoPull:
   822  		fallthrough
   823  	case mangos.ProtoSub:
   824  		return a.recvLoop()
   825  
   826  	case mangos.ProtoPush:
   827  		fallthrough
   828  	case mangos.ProtoPub:
   829  		return a.sendLoop()
   830  
   831  	case mangos.ProtoPair:
   832  		fallthrough
   833  	case mangos.ProtoStar:
   834  		fallthrough
   835  	case mangos.ProtoBus:
   836  		if a.sendData != nil {
   837  			return a.sendRecvLoop()
   838  		}
   839  		return a.recvLoop()
   840  
   841  	case mangos.ProtoSurveyor:
   842  		fallthrough
   843  	case mangos.ProtoReq:
   844  		return a.sendRecvLoop()
   845  
   846  	case mangos.ProtoRep:
   847  		fallthrough
   848  	case mangos.ProtoRespondent:
   849  		return a.replyLoop()
   850  
   851  	default:
   852  		return errors.New("unknown protocol")
   853  	}
   854  }