Optimize socketclient adapter and add various code improvements 71/27571/1
authorOndrej Fabry <ofabry@cisco.com>
Tue, 16 Jun 2020 08:40:34 +0000 (10:40 +0200)
committerOndrej Fabry <ofabry@cisco.com>
Tue, 16 Jun 2020 08:40:34 +0000 (10:40 +0200)
This commit includes:

Features
- optimized [socketclient](adapter/socketclient) adapter and add method to set client name
- added list of compatible messages to `CompatibilityError`

Fixes
- `MsgCodec` will recover panic occurring during a message decoding
- calling `Unsubscibe` will close the notification channel

Other
- improved log messages to provide more relevant info

Examples
- added more code samples of working with unions in [union example](examples/union-example)
- added profiling mode to [perf bench](examples/perf-bench) example
- improved [simple client](examples/simple-client) example to work properly even with multiple runs

Dependencies
- updated `github.com/sirupsen/logrus` dep to `v1.6.0`
- updated `github.com/lunixbochs/struc` dep to `v0.0.0-20200521075829-a4cb8d33dbbe`

Change-Id: I136a3968ccf9e93760d7ee2b9902fc7e6390a09d
Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
17 files changed:
CHANGELOG.md
Makefile
adapter/socketclient/doc.go
adapter/socketclient/socketclient.go
api/binapi.go
codec/msg_codec.go
core/channel.go
core/channel_test.go
core/connection.go
core/log.go
core/request_handler.go
examples/perf-bench/perf-bench.go
examples/simple-client/simple_client.go
examples/union-example/union_example.go
go.mod
go.sum
version/version.go

index 145d1b1..86e3edc 100644 (file)
@@ -11,6 +11,29 @@ This file lists changes for the GoVPP releases.
 -
 -->
 
+## 0.4.0 (in development)
+> _NOT RELEASED YET_
+
+### Features
+- optimized [socketclient](adapter/socketclient) adapter and add method to set client name
+- added list of compatible messages to `CompatibilityError`
+
+### Fixes
+- `MsgCodec` will recover panic occurring during a message decoding  
+- calling `Unsubscibe` will close the notification channel 
+
+### Other
+- improved log messages to provide more relevant info
+
+#### Examples
+- added more code samples of working with unions in [union example](examples/union-example)
+- added profiling mode to [perf bench](examples/perf-bench) example
+- improved [simple client](examples/simple-client) example to work properly even with multiple runs
+
+#### Dependencies
+- updated `github.com/sirupsen/logrus` dep to `v1.6.0`
+- updated `github.com/lunixbochs/struc` dep to `v0.0.0-20200521075829-a4cb8d33dbbe`
+
 ## 0.3.5
 > _18 May 2020_
 
index 4297746..e5bd0bd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -101,7 +101,7 @@ gen-binapi-docker: install-generator ## Generate binapi code (using Docker)
                -v "$(shell pwd):/govpp" -w /govpp \
                -u "$(shell id -u):$(shell id -g)" \
                "${VPP_IMG}" \
-         sh -xc "cd $(BINAPI_DIR) && $(cmds)"
+         sh -exc "cd $(BINAPI_DIR) && $(cmds)"
 
 extras:
        @make -C extras
index 0f93c56..cbb00a2 100644 (file)
 //
 // Requirements
 //
-// The socketclient will connect to /run/vpp-api.sock by default. However this
-// is not enabled in VPP configuration by default.
+// The socketclient connects to unix domain socket defined in VPP configuration.
 //
-// To enable the socket in VPP, add following section to VPP config.
+// It is enabled by default at /run/vpp/api.sock by the following config section:
 //
 //     socksvr {
-//             default
+//             socket-name default
+//     }
+//
+// If you want to use custom socket path:
+//
+//     socksvr {
+//             socket-name /run/vpp/api.sock
 //     }
 //
 package socketclient
index 366163f..1ee067f 100644 (file)
@@ -16,7 +16,8 @@ package socketclient
 
 import (
        "bufio"
-       "bytes"
+       "encoding/binary"
+       "errors"
        "fmt"
        "io"
        "net"
@@ -27,8 +28,7 @@ import (
        "time"
 
        "github.com/fsnotify/fsnotify"
-       "github.com/lunixbochs/struc"
-       logger "github.com/sirupsen/logrus"
+       "github.com/sirupsen/logrus"
 
        "git.fd.io/govpp.git/adapter"
        "git.fd.io/govpp.git/codec"
@@ -36,38 +36,34 @@ import (
 
 const (
        // DefaultSocketName is default VPP API socket file path.
-       DefaultSocketName = adapter.DefaultBinapiSocket
-       legacySocketName  = "/run/vpp-api.sock"
+       DefaultSocketName = "/run/vpp/api.sock"
+       // DefaultClientName is used for identifying client in socket registration
+       DefaultClientName = "govppsock"
 )
 
 var (
+
        // DefaultConnectTimeout is default timeout for connecting
        DefaultConnectTimeout = time.Second * 3
        // DefaultDisconnectTimeout is default timeout for discconnecting
        DefaultDisconnectTimeout = time.Millisecond * 100
-       // MaxWaitReady defines maximum duration before waiting for socket file
-       // times out
+       // MaxWaitReady defines maximum duration of waiting for socket file
        MaxWaitReady = time.Second * 10
-       // ClientName is used for identifying client in socket registration
-       ClientName = "govppsock"
 )
 
 var (
-       // Debug is global variable that determines debug mode
-       Debug = os.Getenv("DEBUG_GOVPP_SOCK") != ""
-       // DebugMsgIds is global variable that determines debug mode for msg ids
-       DebugMsgIds = os.Getenv("DEBUG_GOVPP_SOCKMSG") != ""
+       debug       = strings.Contains(os.Getenv("DEBUG_GOVPP"), "socketclient")
+       debugMsgIds = strings.Contains(os.Getenv("DEBUG_GOVPP"), "msgtable")
 
-       // Log is global logger
-       Log = logger.New()
+       logger = logrus.New()
+       log    = logger.WithField("logger", "govpp/socketclient")
 )
 
-// init initializes global logger, which logs debug level messages to stdout.
+// init initializes global logger
 func init() {
-       Log.Out = os.Stdout
-       if Debug {
-               Log.Level = logger.DebugLevel
-               Log.Debug("govpp/socketclient: enabled debug mode")
+       if debug {
+               logger.Level = logrus.DebugLevel
+               log.Debug("govpp: debug level enabled for socketclient")
        }
 }
 
@@ -88,12 +84,13 @@ const socketMissing = `
 
 var warnOnce sync.Once
 
-func (c *vppClient) printMissingSocketMsg() {
+func (c *socketClient) printMissingSocketMsg() {
        fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
 }
 
-type vppClient struct {
-       sockAddr string
+type socketClient struct {
+       sockAddr   string
+       clientName string
 
        conn   *net.UnixConn
        reader *bufio.Reader
@@ -102,50 +99,63 @@ type vppClient struct {
        connectTimeout    time.Duration
        disconnectTimeout time.Duration
 
-       cb           adapter.MsgCallback
+       msgCallback  adapter.MsgCallback
        clientIndex  uint32
        msgTable     map[string]uint16
        sockDelMsgId uint16
        writeMu      sync.Mutex
 
+       headerPool *sync.Pool
+
        quit chan struct{}
        wg   sync.WaitGroup
 }
 
-func NewVppClient(sockAddr string) *vppClient {
+func NewVppClient(sockAddr string) *socketClient {
        if sockAddr == "" {
                sockAddr = DefaultSocketName
        }
-       return &vppClient{
+       return &socketClient{
                sockAddr:          sockAddr,
+               clientName:        DefaultClientName,
                connectTimeout:    DefaultConnectTimeout,
                disconnectTimeout: DefaultDisconnectTimeout,
-               cb: func(msgID uint16, data []byte) {
-                       Log.Warnf("no callback set, dropping message: ID=%v len=%d", msgID, len(data))
+               headerPool: &sync.Pool{New: func() interface{} {
+                       return make([]byte, 16)
+               }},
+               msgCallback: func(msgID uint16, data []byte) {
+                       log.Debugf("no callback set, dropping message: ID=%v len=%d", msgID, len(data))
                },
        }
 }
 
+// SetClientName sets a client name used for identification.
+func (c *socketClient) SetClientName(name string) {
+       c.clientName = name
+}
+
 // SetConnectTimeout sets timeout used during connecting.
-func (c *vppClient) SetConnectTimeout(t time.Duration) {
+func (c *socketClient) SetConnectTimeout(t time.Duration) {
        c.connectTimeout = t
 }
 
 // SetDisconnectTimeout sets timeout used during disconnecting.
-func (c *vppClient) SetDisconnectTimeout(t time.Duration) {
+func (c *socketClient) SetDisconnectTimeout(t time.Duration) {
        c.disconnectTimeout = t
 }
 
-func (c *vppClient) SetMsgCallback(cb adapter.MsgCallback) {
-       Log.Debug("SetMsgCallback")
-       c.cb = cb
+func (c *socketClient) SetMsgCallback(cb adapter.MsgCallback) {
+       log.Debug("SetMsgCallback")
+       c.msgCallback = cb
 }
 
-func (c *vppClient) checkLegacySocket() bool {
+const legacySocketName = "/run/vpp-api.sock"
+
+func (c *socketClient) checkLegacySocket() bool {
        if c.sockAddr == legacySocketName {
                return false
        }
-       Log.Debugf("checking legacy socket: %s", legacySocketName)
+       log.Debugf("checking legacy socket: %s", legacySocketName)
        // check if socket exists
        if _, err := os.Stat(c.sockAddr); err == nil {
                return false // socket exists
@@ -163,7 +173,7 @@ func (c *vppClient) checkLegacySocket() bool {
 }
 
 // WaitReady checks socket file existence and waits for it if necessary
-func (c *vppClient) WaitReady() error {
+func (c *socketClient) WaitReady() error {
        // check if socket already exists
        if _, err := os.Stat(c.sockAddr); err == nil {
                return nil // socket exists, we are ready
@@ -182,7 +192,7 @@ func (c *vppClient) WaitReady() error {
        }
        defer func() {
                if err := watcher.Close(); err != nil {
-                       Log.Warnf("failed to close file watcher: %v", err)
+                       log.Debugf("failed to close file watcher: %v", err)
                }
        }()
 
@@ -204,7 +214,7 @@ func (c *vppClient) WaitReady() error {
                        return e
 
                case ev := <-watcher.Events:
-                       Log.Debugf("watcher event: %+v", ev)
+                       log.Debugf("watcher event: %+v", ev)
                        if ev.Name == c.sockAddr && (ev.Op&fsnotify.Create) == fsnotify.Create {
                                // socket created, we are ready
                                return nil
@@ -213,7 +223,7 @@ func (c *vppClient) WaitReady() error {
        }
 }
 
-func (c *vppClient) Connect() error {
+func (c *socketClient) Connect() error {
        c.checkLegacySocket()
 
        // check if socket exists
@@ -229,7 +239,7 @@ func (c *vppClient) Connect() error {
        }
 
        if err := c.open(); err != nil {
-               c.disconnect()
+               _ = c.disconnect()
                return err
        }
 
@@ -240,23 +250,23 @@ func (c *vppClient) Connect() error {
        return nil
 }
 
-func (c *vppClient) Disconnect() error {
+func (c *socketClient) Disconnect() error {
        if c.conn == nil {
                return nil
        }
-       Log.Debugf("Disconnecting..")
+       log.Debugf("Disconnecting..")
 
        close(c.quit)
 
        if err := c.conn.CloseRead(); err != nil {
-               Log.Debugf("closing read failed: %v", err)
+               log.Debugf("closing readMsg failed: %v", err)
        }
 
        // wait for readerLoop to return
        c.wg.Wait()
 
        if err := c.close(); err != nil {
-               Log.Debugf("closing failed: %v", err)
+               log.Debugf("closing failed: %v", err)
        }
 
        if err := c.disconnect(); err != nil {
@@ -266,38 +276,40 @@ func (c *vppClient) Disconnect() error {
        return nil
 }
 
-func (c *vppClient) connect(sockAddr string) error {
+const defaultBufferSize = 4096
+
+func (c *socketClient) connect(sockAddr string) error {
        addr := &net.UnixAddr{Name: sockAddr, Net: "unix"}
 
-       Log.Debugf("Connecting to: %v", c.sockAddr)
+       log.Debugf("Connecting to: %v", c.sockAddr)
 
        conn, err := net.DialUnix("unix", nil, addr)
        if err != nil {
                // we try different type of socket for backwards compatbility with VPP<=19.04
                if strings.Contains(err.Error(), "wrong type for socket") {
                        addr.Net = "unixpacket"
-                       Log.Debugf("%s, retrying connect with type unixpacket", err)
+                       log.Debugf("%s, retrying connect with type unixpacket", err)
                        conn, err = net.DialUnix("unixpacket", nil, addr)
                }
                if err != nil {
-                       Log.Debugf("Connecting to socket %s failed: %s", addr, err)
+                       log.Debugf("Connecting to socket %s failed: %s", addr, err)
                        return err
                }
        }
 
        c.conn = conn
-       Log.Debugf("Connected to socket (local addr: %v)", c.conn.LocalAddr().(*net.UnixAddr))
+       log.Debugf("Connected to socket (local addr: %v)", c.conn.LocalAddr().(*net.UnixAddr))
 
-       c.reader = bufio.NewReader(c.conn)
-       c.writer = bufio.NewWriter(c.conn)
+       c.reader = bufio.NewReaderSize(c.conn, defaultBufferSize)
+       c.writer = bufio.NewWriterSize(c.conn, defaultBufferSize)
 
        return nil
 }
 
-func (c *vppClient) disconnect() error {
-       Log.Debugf("Closing socket")
+func (c *socketClient) disconnect() error {
+       log.Debugf("Closing socket")
        if err := c.conn.Close(); err != nil {
-               Log.Debugln("Closing socket failed:", err)
+               log.Debugln("Closing socket failed:", err)
                return err
        }
        return nil
@@ -309,44 +321,40 @@ const (
        deleteMsgContext = byte(124)
 )
 
-func (c *vppClient) open() error {
-       msgCodec := new(codec.MsgCodec)
+func (c *socketClient) open() error {
+       var msgCodec codec.MsgCodec
 
-       req := &SockclntCreate{Name: ClientName}
+       // Request socket client create
+       req := &SockclntCreate{
+               Name: c.clientName,
+       }
        msg, err := msgCodec.EncodeMsg(req, sockCreateMsgId)
        if err != nil {
-               Log.Debugln("Encode error:", err)
+               log.Debugln("Encode  error:", err)
                return err
        }
        // set non-0 context
        msg[5] = createMsgContext
 
-       if err := c.write(msg); err != nil {
-               Log.Debugln("Write error: ", err)
-               return err
-       }
-
-       readDeadline := time.Now().Add(c.connectTimeout)
-       if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+       if err := c.writeMsg(msg); err != nil {
+               log.Debugln("Write error: ", err)
                return err
        }
-       msgReply, err := c.read()
+       msgReply, err := c.readMsgTimeout(nil, c.connectTimeout)
        if err != nil {
-               Log.Println("Read error:", err)
-               return err
-       }
-       // reset read deadline
-       if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+               log.Println("Read error:", err)
                return err
        }
 
        reply := new(SockclntCreateReply)
        if err := msgCodec.DecodeMsg(msgReply, reply); err != nil {
-               Log.Println("Decode error:", err)
+               log.Println("Decoding sockclnt_create_reply failed:", err)
                return err
+       } else if reply.Response != 0 {
+               return fmt.Errorf("sockclnt_create_reply: response error (%d)", reply.Response)
        }
 
-       Log.Debugf("SockclntCreateReply: Response=%v Index=%v Count=%v",
+       log.Debugf("SockclntCreateReply: Response=%v Index=%v Count=%v",
                reply.Response, reply.Index, reply.Count)
 
        c.clientIndex = reply.Index
@@ -358,15 +366,15 @@ func (c *vppClient) open() error {
                if strings.HasPrefix(name, "sockclnt_delete_") {
                        c.sockDelMsgId = x.Index
                }
-               if DebugMsgIds {
-                       Log.Debugf(" - %4d: %q", x.Index, name)
+               if debugMsgIds {
+                       log.Debugf(" - %4d: %q", x.Index, name)
                }
        }
 
        return nil
 }
 
-func (c *vppClient) close() error {
+func (c *socketClient) close() error {
        msgCodec := new(codec.MsgCodec)
 
        req := &SockclntDelete{
@@ -374,133 +382,148 @@ func (c *vppClient) close() error {
        }
        msg, err := msgCodec.EncodeMsg(req, c.sockDelMsgId)
        if err != nil {
-               Log.Debugln("Encode error:", err)
+               log.Debugln("Encode error:", err)
                return err
        }
        // set non-0 context
        msg[5] = deleteMsgContext
 
-       Log.Debugf("sending socklntDel (%d byes): % 0X", len(msg), msg)
-       if err := c.write(msg); err != nil {
-               Log.Debugln("Write error: ", err)
-               return err
-       }
+       log.Debugf("sending socklntDel (%d bytes): % 0X", len(msg), msg)
 
-       readDeadline := time.Now().Add(c.disconnectTimeout)
-       if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+       if err := c.writeMsg(msg); err != nil {
+               log.Debugln("Write error: ", err)
                return err
        }
-       msgReply, err := c.read()
+
+       msgReply, err := c.readMsgTimeout(nil, c.disconnectTimeout)
        if err != nil {
-               Log.Debugln("Read error:", err)
                if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
                        // we accept timeout for reply
                        return nil
                }
-               return err
-       }
-       // reset read deadline
-       if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+               log.Debugln("Read error:", err)
                return err
        }
 
        reply := new(SockclntDeleteReply)
        if err := msgCodec.DecodeMsg(msgReply, reply); err != nil {
-               Log.Debugln("Decode error:", err)
+               log.Debugln("Decoding sockclnt_delete_reply failed:", err)
                return err
+       } else if reply.Response != 0 {
+               return fmt.Errorf("sockclnt_delete_reply: response error (%d)", reply.Response)
        }
 
-       Log.Debugf("SockclntDeleteReply: Response=%v", reply.Response)
-
        return nil
 }
 
-func (c *vppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
-       msg := msgName + "_" + msgCrc
-       msgID, ok := c.msgTable[msg]
-       if !ok {
-               return 0, &adapter.UnknownMsgError{msgName, msgCrc}
+func (c *socketClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+       if msgID, ok := c.msgTable[msgName+"_"+msgCrc]; ok {
+               return msgID, nil
+       }
+       return 0, &adapter.UnknownMsgError{
+               MsgName: msgName,
+               MsgCrc:  msgCrc,
        }
-       return msgID, nil
-}
-
-type reqHeader struct {
-       // MsgID uint16
-       ClientIndex uint32
-       Context     uint32
 }
 
-func (c *vppClient) SendMsg(context uint32, data []byte) error {
-       h := &reqHeader{
-               ClientIndex: c.clientIndex,
-               Context:     context,
-       }
-       buf := new(bytes.Buffer)
-       if err := struc.Pack(buf, h); err != nil {
-               return err
+func (c *socketClient) SendMsg(context uint32, data []byte) error {
+       if len(data) < 10 {
+               return fmt.Errorf("invalid message data, length must be at least 10 bytes")
        }
-       copy(data[2:], buf.Bytes())
+       setMsgRequestHeader(data, c.clientIndex, context)
 
-       Log.Debugf("sendMsg (%d) context=%v client=%d: data: % 02X", len(data), context, c.clientIndex, data)
+       if debug {
+               log.Debugf("sendMsg (%d) context=%v client=%d: % 02X", len(data), context, c.clientIndex, data)
+       }
 
-       if err := c.write(data); err != nil {
-               Log.Debugln("write error: ", err)
+       if err := c.writeMsg(data); err != nil {
+               log.Debugln("writeMsg error: ", err)
                return err
        }
 
        return nil
 }
 
-func (c *vppClient) write(msg []byte) error {
-       h := &msgheader{
-               DataLen: uint32(len(msg)),
+// setMsgRequestHeader sets client index and context in the message request header
+//
+// Message request has following structure:
+//
+//    type msgRequestHeader struct {
+//        MsgID       uint16
+//        ClientIndex uint32
+//        Context     uint32
+//    }
+//
+func setMsgRequestHeader(data []byte, clientIndex, context uint32) {
+       // message ID is already set
+       binary.BigEndian.PutUint32(data[2:6], clientIndex)
+       binary.BigEndian.PutUint32(data[6:10], context)
+}
+
+func (c *socketClient) writeMsg(msg []byte) error {
+       // we lock to prevent mixing multiple message writes
+       c.writeMu.Lock()
+       defer c.writeMu.Unlock()
+
+       header := c.headerPool.Get().([]byte)
+       err := writeMsgHeader(c.writer, header, len(msg))
+       if err != nil {
+               return err
        }
-       buf := new(bytes.Buffer)
-       if err := struc.Pack(buf, h); err != nil {
+       c.headerPool.Put(header)
+
+       if err := writeMsgData(c.writer, msg, c.writer.Size()); err != nil {
                return err
        }
-       header := buf.Bytes()
 
-       // we lock to prevent mixing multiple message sends
-       c.writeMu.Lock()
-       defer c.writeMu.Unlock()
+       if err := c.writer.Flush(); err != nil {
+               return err
+       }
+
+       log.Debugf(" -- writeMsg done")
+
+       return nil
+}
+
+func writeMsgHeader(w io.Writer, header []byte, dataLen int) error {
+       binary.BigEndian.PutUint32(header[8:12], uint32(dataLen))
 
-       if n, err := c.writer.Write(header); err != nil {
+       n, err := w.Write(header)
+       if err != nil {
                return err
-       } else {
-               Log.Debugf(" - header sent (%d/%d): % 0X", n, len(header), header)
+       }
+       if debug {
+               log.Debugf(" - header sent (%d/%d): % 0X", n, len(header), header)
        }
 
-       writerSize := c.writer.Size()
+       return nil
+}
+
+func writeMsgData(w io.Writer, msg []byte, writerSize int) error {
        for i := 0; i <= len(msg)/writerSize; i++ {
                x := i*writerSize + writerSize
                if x > len(msg) {
                        x = len(msg)
                }
-               Log.Debugf(" - x=%v i=%v len=%v mod=%v", x, i, len(msg), len(msg)/writerSize)
-               if n, err := c.writer.Write(msg[i*writerSize : x]); err != nil {
+               if debug {
+                       log.Debugf(" - x=%v i=%v len=%v mod=%v", x, i, len(msg), len(msg)/writerSize)
+               }
+               n, err := w.Write(msg[i*writerSize : x])
+               if err != nil {
                        return err
-               } else {
-                       Log.Debugf(" - msg sent x=%d (%d/%d): % 0X", x, n, len(msg), msg)
+               }
+               if debug {
+                       log.Debugf(" - data sent x=%d (%d/%d): % 0X", x, n, len(msg), msg)
                }
        }
-       if err := c.writer.Flush(); err != nil {
-               return err
-       }
-
-       Log.Debugf(" -- write done")
-
        return nil
 }
 
-type msgHeader struct {
-       MsgID   uint16
-       Context uint32
-}
-
-func (c *vppClient) readerLoop() {
+func (c *socketClient) readerLoop() {
        defer c.wg.Done()
-       defer Log.Debugf("reader quit")
+       defer log.Debugf("reader loop done")
+
+       var buf [8192]byte
 
        for {
                select {
@@ -509,72 +532,118 @@ func (c *vppClient) readerLoop() {
                default:
                }
 
-               msg, err := c.read()
+               msg, err := c.readMsg(buf[:])
                if err != nil {
                        if isClosedError(err) {
                                return
                        }
-                       Log.Debugf("read failed: %v", err)
+                       log.Debugf("readMsg error: %v", err)
                        continue
                }
 
-               h := new(msgHeader)
-               if err := struc.Unpack(bytes.NewReader(msg), h); err != nil {
-                       Log.Debugf("unpacking header failed: %v", err)
-                       continue
+               msgID, context := getMsgReplyHeader(msg)
+               if debug {
+                       log.Debugf("recvMsg (%d) msgID=%d context=%v", len(msg), msgID, context)
                }
 
-               Log.Debugf("recvMsg (%d) msgID=%d context=%v", len(msg), h.MsgID, h.Context)
-               c.cb(h.MsgID, msg)
+               c.msgCallback(msgID, msg)
        }
 }
 
-type msgheader struct {
-       Q               int    `struc:"uint64"`
-       DataLen         uint32 `struc:"uint32"`
-       GcMarkTimestamp uint32 `struc:"uint32"`
+// getMsgReplyHeader gets message ID and context from the message reply header
+//
+// Message reply has following structure:
+//
+//    type msgReplyHeader struct {
+//        MsgID       uint16
+//        Context     uint32
+//    }
+//
+func getMsgReplyHeader(msg []byte) (msgID uint16, context uint32) {
+       msgID = binary.BigEndian.Uint16(msg[0:2])
+       context = binary.BigEndian.Uint32(msg[2:6])
+       return
 }
 
-func (c *vppClient) read() ([]byte, error) {
-       Log.Debug(" reading next msg..")
+func (c *socketClient) readMsgTimeout(buf []byte, timeout time.Duration) ([]byte, error) {
+       // set read deadline
+       readDeadline := time.Now().Add(timeout)
+       if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+               return nil, err
+       }
 
-       header := make([]byte, 16)
+       // read message
+       msgReply, err := c.readMsg(buf)
+       if err != nil {
+               return nil, err
+       }
 
-       n, err := io.ReadAtLeast(c.reader, header, 16)
+       // reset read deadline
+       if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+               return nil, err
+       }
+
+       return msgReply, nil
+}
+
+func (c *socketClient) readMsg(buf []byte) ([]byte, error) {
+       log.Debug("reading msg..")
+
+       header := c.headerPool.Get().([]byte)
+       msgLen, err := readMsgHeader(c.reader, header)
        if err != nil {
                return nil, err
        }
+       c.headerPool.Put(header)
+
+       msg, err := readMsgData(c.reader, buf, msgLen)
+
+       log.Debugf(" -- readMsg done (buffered: %d)", c.reader.Buffered())
+
+       return msg, nil
+}
+
+func readMsgHeader(r io.Reader, header []byte) (int, error) {
+       n, err := io.ReadAtLeast(r, header, 16)
+       if err != nil {
+               return 0, err
+       }
        if n == 0 {
-               Log.Debugln("zero bytes header")
-               return nil, nil
+               log.Debugln("zero bytes header")
+               return 0, nil
        } else if n != 16 {
-               Log.Debugf("invalid header data (%d): % 0X", n, header[:n])
-               return nil, fmt.Errorf("invalid header (expected 16 bytes, got %d)", n)
+               log.Debugf("invalid header (%d bytes): % 0X", n, header[:n])
+               return 0, fmt.Errorf("invalid header (expected 16 bytes, got %d)", n)
        }
-       Log.Debugf(" read header %d bytes: % 0X", n, header)
 
-       h := &msgheader{}
-       if err := struc.Unpack(bytes.NewReader(header[:]), h); err != nil {
-               return nil, err
-       }
-       Log.Debugf(" - decoded header: %+v", h)
+       dataLen := binary.BigEndian.Uint32(header[8:12])
 
-       msgLen := int(h.DataLen)
-       msg := make([]byte, msgLen)
+       return int(dataLen), nil
+}
+
+func readMsgData(r io.Reader, buf []byte, dataLen int) ([]byte, error) {
+       var msg []byte
+       if buf == nil || len(buf) < dataLen {
+               msg = make([]byte, dataLen)
+       } else {
+               msg = buf[0:dataLen]
+       }
 
-       n, err = c.reader.Read(msg)
+       n, err := r.Read(msg)
        if err != nil {
                return nil, err
        }
-       Log.Debugf(" - read msg %d bytes (%d buffered) % 0X", n, c.reader.Buffered(), msg[:n])
+       if debug {
+               log.Debugf(" - read data (%d bytes): % 0X", n, msg[:n])
+       }
 
-       if msgLen > n {
-               remain := msgLen - n
-               Log.Debugf("continue read for another %d bytes", remain)
+       if dataLen > n {
+               remain := dataLen - n
+               log.Debugf("continue reading remaining %d bytes", remain)
                view := msg[n:]
 
                for remain > 0 {
-                       nbytes, err := c.reader.Read(view)
+                       nbytes, err := r.Read(view)
                        if err != nil {
                                return nil, err
                        } else if nbytes == 0 {
@@ -582,19 +651,17 @@ func (c *vppClient) read() ([]byte, error) {
                        }
 
                        remain -= nbytes
-                       Log.Debugf("another data received: %d bytes (remain: %d)", nbytes, remain)
+                       log.Debugf("another data received: %d bytes (remain: %d)", nbytes, remain)
 
                        view = view[nbytes:]
                }
        }
 
-       Log.Debugf(" -- read done (buffered: %d)", c.reader.Buffered())
-
        return msg, nil
 }
 
 func isClosedError(err error) bool {
-       if err == io.EOF {
+       if errors.Is(err, io.EOF) {
                return true
        }
        return strings.HasSuffix(err.Error(), "use of closed network connection")
index d933612..96eb3bf 100644 (file)
@@ -91,42 +91,47 @@ type Channel interface {
        // It will return an error if any of the given messages are not compatible.
        CheckCompatiblity(msgs ...Message) error
 
-       // Close closes the API channel and releases all API channel-related resources in the ChannelProvider.
+       // Close closes the API channel and releases all API channel-related resources
+       // in the ChannelProvider.
        Close()
 }
 
 // RequestCtx is helper interface which allows to receive reply on request.
 type RequestCtx interface {
-       // ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs).
-       // The reply will be decoded into the msg argument. Error will be returned if the response cannot be received or decoded.
+       // ReceiveReply receives a reply from VPP (blocks until a reply is delivered
+       // from VPP, or until an error occurs). The reply will be decoded into the msg
+       // argument. Error will be returned if the response cannot be received or decoded.
        ReceiveReply(msg Message) error
 }
 
 // MultiRequestCtx is helper interface which allows to receive reply on multi-request.
 type MultiRequestCtx interface {
-       // ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs).
-       // The reply will be decoded into the msg argument. If the last reply has been already consumed, lastReplyReceived is
-       // set to true. Do not use the message itself if lastReplyReceived is true - it won't be filled with actual data.
-       // Error will be returned if the response cannot be received or decoded.
+       // ReceiveReply receives a reply from VPP (blocks until a reply is delivered
+       // from VPP, or until an error occurs).The reply will be decoded into the msg
+       // argument. If the last reply has been already consumed, lastReplyReceived is
+       // set to true. Do not use the message itself if lastReplyReceived is
+       // true - it won't be filled with actual data.Error will be returned if the
+       // response cannot be received or decoded.
        ReceiveReply(msg Message) (lastReplyReceived bool, err error)
 }
 
-// SubscriptionCtx is helper interface which allows to control subscription for notification events.
+// SubscriptionCtx is helper interface which allows to control subscription for
+// notification events.
 type SubscriptionCtx interface {
-       // Unsubscribe unsubscribes from receiving the notifications tied to the subscription context.
+       // Unsubscribe unsubscribes from receiving the notifications tied to the
+       // subscription context.
        Unsubscribe() error
 }
 
 // CompatibilityError is the error type usually returned by CheckCompatibility
-// method of Channel. It describes all of the incompatible messages.
+// method of Channel. It contains list of all the compatible/incompatible messages.
 type CompatibilityError struct {
-       // IncompatibleMessages is the list of all messages
-       // that failed compatibility check.
+       CompatibleMessages   []string
        IncompatibleMessages []string
 }
 
 func (c *CompatibilityError) Error() string {
-       return fmt.Sprintf("%d incompatible messages: %v", len(c.IncompatibleMessages), c.IncompatibleMessages)
+       return fmt.Sprintf("%d/%d messages incompatible", len(c.IncompatibleMessages), len(c.CompatibleMessages)+len(c.IncompatibleMessages))
 }
 
 var (
@@ -155,6 +160,6 @@ func GetRegisteredMessageTypes() map[reflect.Type]string {
        return registeredMessageTypes
 }
 
-// GoVppAPIPackageIsVersion1 is referenced from generated binapi files
+// GoVppAPIPackageIsVersionX is referenced from generated binapi files
 // to assert that that code is compatible with this version of the GoVPP api package.
 const GoVppAPIPackageIsVersion1 = true
index 67628a4..3f60cae 100644 (file)
@@ -20,8 +20,9 @@ import (
        "fmt"
        "reflect"
 
-       "git.fd.io/govpp.git/api"
        "github.com/lunixbochs/struc"
+
+       "git.fd.io/govpp.git/api"
 )
 
 // MsgCodec provides encoding and decoding functionality of `api.Message` structs into/from
@@ -65,7 +66,7 @@ func (*MsgCodec) EncodeMsg(msg api.Message, msgID uint16) (data []byte, err erro
                        if err, ok = r.(error); !ok {
                                err = fmt.Errorf("%v", r)
                        }
-                       err = fmt.Errorf("panic occurred: %v", err)
+                       err = fmt.Errorf("panic occurred during encoding message %s: %v", msg.GetMessageName(), err)
                }
        }()
 
@@ -101,11 +102,22 @@ func (*MsgCodec) EncodeMsg(msg api.Message, msgID uint16) (data []byte, err erro
 }
 
 // DecodeMsg decodes binary-encoded data of a message into provided `Message` structure.
-func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) error {
+func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) (err error) {
        if msg == nil {
                return errors.New("nil message passed in")
        }
 
+       // try to recover panic which might possibly occur
+       defer func() {
+               if r := recover(); r != nil {
+                       var ok bool
+                       if err, ok = r.(error); !ok {
+                               err = fmt.Errorf("%v", r)
+                       }
+                       err = fmt.Errorf("panic occurred during decoding message %s: %v", msg.GetMessageName(), err)
+               }
+       }()
+
        var header interface{}
 
        // check which header is expected
@@ -123,7 +135,7 @@ func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) error {
        buf := bytes.NewReader(data)
 
        // decode message header
-       if err := struc.Unpack(buf, header); err != nil {
+       if err = struc.Unpack(buf, header); err != nil {
                return fmt.Errorf("failed to decode message header: %+v, error: %v", header, err)
        }
 
index 363a267..8479d6a 100644 (file)
@@ -37,6 +37,8 @@ type MessageCodec interface {
        EncodeMsg(msg api.Message, msgID uint16) ([]byte, error)
        // DecodeMsg decodes binary-encoded data of a message into provided Message structure.
        DecodeMsg(data []byte, msg api.Message) error
+       // DecodeMsgContext decodes context from message data.
+       DecodeMsgContext(data []byte, msg api.Message) (context uint32, err error)
 }
 
 // MessageIdentifier provides identification of generated API messages.
@@ -84,7 +86,7 @@ type subscriptionCtx struct {
        msgFactory func() api.Message // function that returns a new instance of the specific message that is expected as a notification
 }
 
-// channel is the main communication interface with govpp core. It contains four Go channels, one for sending the requests
+// Channel is the main communication interface with govpp core. It contains four Go channels, one for sending the requests
 // to VPP, one for receiving the replies from it and the same set for notifications. The user can access the Go channels
 // via methods provided by Channel interface in this package. Do not use the same channel from multiple goroutines
 // concurrently, otherwise the responses could mix! Use multiple channels instead.
@@ -150,13 +152,13 @@ func (ch *Channel) CheckCompatiblity(msgs ...api.Message) error {
                _, err := ch.msgIdentifier.GetMessageID(msg)
                if err != nil {
                        if uerr, ok := err.(*adapter.UnknownMsgError); ok {
-                               m := fmt.Sprintf("%s_%s", uerr.MsgName, uerr.MsgCrc)
-                               comperr.IncompatibleMessages = append(comperr.IncompatibleMessages, m)
+                               comperr.IncompatibleMessages = append(comperr.IncompatibleMessages, getMsgID(uerr.MsgName, uerr.MsgCrc))
                                continue
                        }
                        // other errors return immediatelly
                        return err
                }
+               comperr.CompatibleMessages = append(comperr.CompatibleMessages, getMsgNameWithCrc(msg))
        }
        if len(comperr.IncompatibleMessages) == 0 {
                return nil
@@ -234,6 +236,8 @@ func (sub *subscriptionCtx) Unsubscribe() error {
 
        for i, item := range sub.ch.conn.subscriptions[sub.msgID] {
                if item == sub {
+                       // close notification channel
+                       close(sub.ch.conn.subscriptions[sub.msgID][i].notifChan)
                        // remove i-th item in the slice
                        sub.ch.conn.subscriptions[sub.msgID] = append(sub.ch.conn.subscriptions[sub.msgID][:i], sub.ch.conn.subscriptions[sub.msgID][i+1:]...)
                        return nil
@@ -328,9 +332,9 @@ func (ch *Channel) processReply(reply *vppReply, expSeqNum uint16, msg api.Messa
                        msgNameCrc = getMsgNameWithCrc(replyMsg)
                }
 
-               err = fmt.Errorf("received invalid message ID (seqNum=%d), expected %d (%s), but got %d (%s) "+
+               err = fmt.Errorf("received unexpected message (seqNum=%d), expected %s (ID %d), but got %s (ID %d) "+
                        "(check if multiple goroutines are not sharing single GoVPP channel)",
-                       reply.seqNum, expMsgID, msg.GetMessageName(), reply.msgID, msgNameCrc)
+                       reply.seqNum, msg.GetMessageName(), expMsgID, msgNameCrc, reply.msgID)
                return
        }
 
index b8d07b5..6775519 100644 (file)
@@ -466,5 +466,5 @@ func TestInvalidMessageID(t *testing.T) {
        // second should fail with error invalid message ID
        err = ctx.ch.SendRequest(&ControlPing{}).ReceiveReply(&ControlPingReply{})
        Expect(err).Should(HaveOccurred())
-       Expect(err.Error()).To(ContainSubstring("invalid message ID"))
+       Expect(err.Error()).To(ContainSubstring("unexpected message"))
 }
index 264ec43..917f1cb 100644 (file)
@@ -95,7 +95,7 @@ type Connection struct {
 
        vppConnected uint32 // non-zero if the adapter is connected to VPP
 
-       codec  *codec.MsgCodec        // message codec
+       codec  MessageCodec           // message codec
        msgIDs map[string]uint16      // map of message IDs indexed by message name + CRC
        msgMap map[uint16]api.Message // map of messages indexed by message ID
 
@@ -374,7 +374,11 @@ func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
 }
 
 func getMsgNameWithCrc(x api.Message) string {
-       return x.GetMessageName() + "_" + x.GetCrcString()
+       return getMsgID(x.GetMessageName(), x.GetCrcString())
+}
+
+func getMsgID(name, crc string) string {
+       return name + "_" + crc
 }
 
 func getMsgFactory(msg api.Message) func() api.Message {
@@ -425,9 +429,14 @@ func (c *Connection) retrieveMessageIDs() (err error) {
 
        var n int
        for name, msg := range msgs {
+               typ := reflect.TypeOf(msg).Elem()
+               path := fmt.Sprintf("%s.%s", typ.PkgPath(), typ.Name())
+
                msgID, err := c.GetMessageID(msg)
                if err != nil {
-                       log.Debugf("retrieving msgID for %s failed: %v", name, err)
+                       if debugMsgIDs {
+                               log.Debugf("retrieving message ID for %s failed: %v", path, err)
+                       }
                        continue
                }
                n++
@@ -444,7 +453,8 @@ func (c *Connection) retrieveMessageIDs() (err error) {
                        log.Debugf("message %q (%s) has ID: %d", name, getMsgNameWithCrc(msg), msgID)
                }
        }
-       log.Debugf("retrieved %d/%d msgIDs (took %s)", n, len(msgs), time.Since(t))
+       log.WithField("took", time.Since(t)).
+               Debugf("retrieved IDs for %d messages (registered %d)", n, len(msgs))
 
        return nil
 }
index 5960d6b..dea6cbb 100644 (file)
@@ -2,32 +2,35 @@ package core
 
 import (
        "os"
+       "strings"
 
-       logger "github.com/sirupsen/logrus"
+       "github.com/sirupsen/logrus"
 )
 
 var (
        debug       = os.Getenv("DEBUG_GOVPP") != ""
-       debugMsgIDs = os.Getenv("DEBUG_GOVPP_MSGIDS") != ""
+       debugMsgIDs = strings.Contains(os.Getenv("DEBUG_GOVPP"), "msgid")
 
-       log = logger.New() // global logger
+       log = logrus.New()
 )
 
-// init initializes global logger, which logs debug level messages to stdout.
+// init initializes global logger
 func init() {
-       log.Out = os.Stdout
+       log.Formatter = &logrus.TextFormatter{
+               EnvironmentOverrideColors: true,
+       }
        if debug {
-               log.Level = logger.DebugLevel
-               log.Debugf("govpp/core: debug mode enabled")
+               log.Level = logrus.DebugLevel
+               log.Debugf("govpp: debug level enabled")
        }
 }
 
 // SetLogger sets global logger to l.
-func SetLogger(l *logger.Logger) {
+func SetLogger(l *logrus.Logger) {
        log = l
 }
 
 // SetLogLevel sets global logger level to lvl.
-func SetLogLevel(lvl logger.Level) {
+func SetLogLevel(lvl logrus.Level) {
        log.Level = lvl
 }
index ddd5307..e272c6f 100644 (file)
@@ -17,10 +17,13 @@ package core
 import (
        "errors"
        "fmt"
+       "reflect"
        "sync/atomic"
        "time"
 
        logger "github.com/sirupsen/logrus"
+
+       "git.fd.io/govpp.git/api"
 )
 
 var ReplyChannelTimeout = time.Millisecond * 100
@@ -93,7 +96,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
                        "msg_size": len(data),
                        "seq_num":  req.seqNum,
                        "msg_crc":  req.msg.GetCrcString(),
-               }).Debugf("==> govpp send: %s: %+v", req.msg.GetMessageName(), req.msg)
+               }).Debugf("--> govpp SEND: %s %+v", req.msg.GetMessageName(), req.msg)
        }
 
        // send the request to VPP
@@ -118,7 +121,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
                        "msg_id":   c.pingReqID,
                        "msg_size": len(pingData),
                        "seq_num":  req.seqNum,
-               }).Debug("--> sending control ping")
+               }).Debug(" -> sending control ping")
 
                if err := c.vppClient.SendMsg(context, pingData); err != nil {
                        log.WithFields(logger.Fields{
@@ -156,7 +159,16 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
        }
 
        chanID, isMulti, seqNum := unpackRequestContext(context)
+
        if log.Level == logger.DebugLevel { // for performance reasons - logrus does some processing even if debugs are disabled
+               msg = reflect.New(reflect.TypeOf(msg).Elem()).Interface().(api.Message)
+
+               // decode the message
+               if err = c.codec.DecodeMsg(data, msg); err != nil {
+                       err = fmt.Errorf("decoding message failed: %w", err)
+                       return
+               }
+
                log.WithFields(logger.Fields{
                        "context":  context,
                        "msg_id":   msgID,
@@ -165,7 +177,7 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
                        "is_multi": isMulti,
                        "seq_num":  seqNum,
                        "msg_crc":  msg.GetCrcString(),
-               }).Debugf("<== govpp recv: %s", msg.GetMessageName())
+               }).Debugf("<-- govpp RECEIVE: %s %+v", msg.GetMessageName(), msg)
        }
 
        if context == 0 || c.isNotificationMessage(msgID) {
@@ -210,7 +222,7 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
 func sendReply(ch *Channel, reply *vppReply) {
        select {
        case ch.replyChan <- reply:
-               // reply sent successfully
+       // reply sent successfully
        case <-time.After(ReplyChannelTimeout):
                // receiver still not ready
                log.WithFields(logger.Fields{
index f48c154..81d183c 100644 (file)
@@ -20,6 +20,7 @@ import (
        "flag"
        "fmt"
        "log"
+       "os"
        "time"
 
        "github.com/pkg/profile"
@@ -39,14 +40,14 @@ const (
 
 func main() {
        // parse optional flags
-       var sync, prof bool
+       var sync bool
        var cnt int
-       var sock string
+       var sock, prof string
        flag.BoolVar(&sync, "sync", false, "run synchronous perf test")
-       flag.StringVar(&sock, "socket", socketclient.DefaultSocketName, "Path to VPP API socket")
-       flag.String("socket", statsclient.DefaultSocketName, "Path to VPP stats socket")
+       flag.StringVar(&sock, "api-socket", socketclient.DefaultSocketName, "Path to VPP API socket")
+       flag.String("stats-socket", statsclient.DefaultSocketName, "Path to VPP stats socket")
        flag.IntVar(&cnt, "count", 0, "count of requests to be sent to VPP")
-       flag.BoolVar(&prof, "prof", false, "generate profile data")
+       flag.StringVar(&prof, "prof", "", "enable profiling mode [mem, cpu]")
        flag.Parse()
 
        if cnt == 0 {
@@ -58,8 +59,16 @@ func main() {
                }
        }
 
-       if prof {
-               defer profile.Start().Stop()
+       switch prof {
+       case "mem":
+               defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
+       case "cpu":
+               defer profile.Start(profile.CPUProfile).Stop()
+       case "":
+       default:
+               fmt.Printf("invalid profiling mode: %q\n", prof)
+               flag.Usage()
+               os.Exit(1)
        }
 
        a := socketclient.NewVppClient(sock)
index 6d96ca8..fe7c109 100644 (file)
@@ -65,20 +65,22 @@ func main() {
        }
        defer ch.Close()
 
+       if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
+               log.Fatal(err)
+       }
+
        vppVersion(ch)
 
        if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
                log.Fatal(err)
        }
 
-       createLoopback(ch)
-       createLoopback(ch)
+       idx := createLoopback(ch)
        interfaceDump(ch)
 
-       addIPAddress(ch)
-       ipAddressDump(ch)
-
-       interfaceNotifications(ch)
+       addIPAddress(ch, idx)
+       ipAddressDump(ch, idx)
+       interfaceNotifications(ch, idx)
 
        if len(Errors) > 0 {
                fmt.Printf("finished with %d errors\n", len(Errors))
@@ -109,11 +111,12 @@ func vppVersion(ch api.Channel) {
        fmt.Printf("reply: %+v\n", reply)
 
        fmt.Printf("VPP version: %q\n", cleanString(reply.Version))
-       fmt.Println("ok")
+       fmt.Println("OK")
+       fmt.Println()
 }
 
 // createLoopback sends request to create loopback interface.
-func createLoopback(ch api.Channel) {
+func createLoopback(ch api.Channel) interfaces.InterfaceIndex {
        fmt.Println("Creating loopback interface")
 
        req := &interfaces.CreateLoopback{}
@@ -121,48 +124,54 @@ func createLoopback(ch api.Channel) {
 
        if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
                logError(err, "creating loopback interface")
-               return
+               return 0
        }
        fmt.Printf("reply: %+v\n", reply)
 
-       fmt.Printf("loopback interface index: %v\n", reply.SwIfIndex)
+       fmt.Printf("interface index: %v\n", reply.SwIfIndex)
        fmt.Println("OK")
+       fmt.Println()
+
+       return reply.SwIfIndex
 }
 
 // interfaceDump shows an example of multipart request (multiple replies are expected).
 func interfaceDump(ch api.Channel) {
        fmt.Println("Dumping interfaces")
 
+       n := 0
        reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
        for {
                msg := &interfaces.SwInterfaceDetails{}
                stop, err := reqCtx.ReceiveReply(msg)
+               if stop {
+                       break
+               }
                if err != nil {
                        logError(err, "dumping interfaces")
                        return
                }
-               if stop {
-                       break
-               }
-               fmt.Printf(" - interface: %+v\n", msg)
+               n++
+               fmt.Printf(" - interface #%d: %+v\n", n, msg)
        }
 
        fmt.Println("OK")
+       fmt.Println()
 }
 
 // addIPAddress sends request to add IP address to interface.
-func addIPAddress(ch api.Channel) {
-       fmt.Println("Adding IP address to interface")
+func addIPAddress(ch api.Channel, index interfaces.InterfaceIndex) {
+       fmt.Printf("Adding IP address to interface to interface index %d\n", index)
 
        req := &interfaces.SwInterfaceAddDelAddress{
-               SwIfIndex: 1,
+               SwIfIndex: index,
                IsAdd:     true,
                Prefix: ip_types.AddressWithPrefix{
                        Address: interfaces.Address{
                                Af: ip_types.ADDRESS_IP4,
-                               Un: ip_types.AddressUnionIP4(interfaces.IP4Address{10, 10, 0, 1}),
+                               Un: ip_types.AddressUnionIP4(interfaces.IP4Address{10, 10, 0, uint8(index)}),
                        },
-                       Len: 24,
+                       Len: 32,
                },
        }
        reply := &interfaces.SwInterfaceAddDelAddressReply{}
@@ -174,13 +183,14 @@ func addIPAddress(ch api.Channel) {
        fmt.Printf("reply: %+v\n", reply)
 
        fmt.Println("OK")
+       fmt.Println()
 }
 
-func ipAddressDump(ch api.Channel) {
-       fmt.Println("Dumping IP addresses")
+func ipAddressDump(ch api.Channel, index interfaces.InterfaceIndex) {
+       fmt.Printf("Dumping IP addresses for interface index %d\n", index)
 
        req := &ip.IPAddressDump{
-               SwIfIndex: 1,
+               SwIfIndex: index,
        }
        reqCtx := ch.SendMultiRequest(req)
 
@@ -198,13 +208,14 @@ func ipAddressDump(ch api.Channel) {
        }
 
        fmt.Println("OK")
+       fmt.Println()
 }
 
 // interfaceNotifications shows the usage of notification API. Note that for notifications,
 // you are supposed to create your own Go channel with your preferred buffer size. If the channel's
 // buffer is full, the notifications will not be delivered into it.
-func interfaceNotifications(ch api.Channel) {
-       fmt.Println("Subscribing to notificaiton events")
+func interfaceNotifications(ch api.Channel, index interfaces.InterfaceIndex) {
+       fmt.Printf("Subscribing to notificaiton events for interface index %d\n", index)
 
        notifChan := make(chan api.Message, 100)
 
@@ -225,27 +236,31 @@ func interfaceNotifications(ch api.Channel) {
                return
        }
 
+       // receive notifications
+       go func() {
+               for notif := range notifChan {
+                       fmt.Printf("incoming event: %+v\n", notif.(*interfaces.SwInterfaceEvent))
+               }
+       }()
+
        // generate some events in VPP
        err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
-               SwIfIndex: 1,
+               SwIfIndex: index,
+               Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
        }).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
        if err != nil {
                logError(err, "setting interface flags")
                return
        }
        err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
-               SwIfIndex: 1,
-               Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
+               SwIfIndex: index,
+               Flags:     0,
        }).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
        if err != nil {
                logError(err, "setting interface flags")
                return
        }
 
-       // receive one notification
-       notif := (<-notifChan).(*interfaces.SwInterfaceEvent)
-       fmt.Printf("incoming event: %+v\n", notif)
-
        // disable interface events in VPP
        err = ch.SendRequest(&interfaces.WantInterfaceEvents{
                PID:           uint32(os.Getpid()),
@@ -263,6 +278,7 @@ func interfaceNotifications(ch api.Channel) {
                return
        }
 
+       fmt.Println("OK")
        fmt.Println()
 }
 
index 92c3ec2..9993ee1 100644 (file)
 package main
 
 import (
-       "bytes"
        "fmt"
        "log"
        "net"
+       "reflect"
 
+       "git.fd.io/govpp.git/codec"
        "git.fd.io/govpp.git/examples/binapi/ip"
        "git.fd.io/govpp.git/examples/binapi/ip_types"
-
-       "github.com/lunixbochs/struc"
 )
 
+func init() {
+       log.SetFlags(0)
+}
+
 func main() {
+       constructExample()
+
        encodingExample()
-       usageExample()
+
+       // convert IP from string form into Address type containing union
+       convertIP("10.10.1.1")
+       convertIP("ff80::1")
 }
 
-func encodingExample() {
-       // create union with IPv4 address
-       var unionIP4 ip.AddressUnion
-       unionIP4.SetIP4(ip.IP4Address{192, 168, 1, 10})
-
-       // use it in the Address type
-       addr := &ip.Address{
-               Af: ip_types.ADDRESS_IP4,
-               Un: ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10}),
-       }
-       log.Printf("encoding union IPv4: %v", addr.Un.GetIP4())
+func constructExample() {
+       var union ip_types.AddressUnion
 
-       // encode the address with union
-       data := encode(addr)
-       // decode the address with union
-       addr2 := decode(data)
+       // create AddressUnion with AdressUnionXXX constructors
+       union = ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10})
+       union = ip_types.AddressUnionIP6(ip.IP6Address{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02})
 
-       log.Printf("decoded union IPv4: %v", addr2.Un.GetIP4())
+       // set AddressUnion with SetXXX methods
+       union.SetIP4(ip.IP4Address{192, 168, 1, 10})
+       union.SetIP6(ip.IP6Address{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02})
 }
 
-func encode(addr *ip.Address) []byte {
-       log.Printf("encoding address: %#v", addr)
-       buf := new(bytes.Buffer)
-       if err := struc.Pack(buf, addr); err != nil {
-               panic(err)
+func encodingExample() {
+       var c codec.MsgCodec
+
+       // encode this message
+       var msg = ip.IPPuntRedirect{
+               Punt: ip.PuntRedirect{
+                       Nh: ip_types.Address{
+                               Af: ip_types.ADDRESS_IP4,
+                               Un: ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10}),
+                       },
+               },
+               IsAdd: true,
        }
-       return buf.Bytes()
-}
+       log.Printf("encoding message: %+v", msg)
 
-func decode(data []byte) *ip.Address {
-       addr := new(ip.Address)
-       buf := bytes.NewReader(data)
-       if err := struc.Unpack(buf, addr); err != nil {
-               panic(err)
+       b, err := c.EncodeMsg(&msg, 1)
+       if err != nil {
+               log.Fatal(err)
        }
-       log.Printf("decoded address: %#v", addr)
-       return addr
-}
 
-func usageExample() {
-       var convAddr = func(ip string) {
-               addr, err := ipToAddress(ip)
-               if err != nil {
-                       log.Printf("converting ip %q failed: %v", ip, err)
-               }
-               fmt.Printf("% 0X\n", addr)
+       // decode into this message
+       var msg2 ip.IPPuntRedirect
+       if err := c.DecodeMsg(b, &msg2); err != nil {
+               log.Fatal(err)
        }
+       log.Printf("decoded message: %+v", msg2)
 
-       convAddr("10.10.10.10")
-       convAddr("::1")
-       convAddr("")
+       // compare the messages
+       if !reflect.DeepEqual(msg, msg2) {
+               log.Fatal("messages are not equal")
+       }
+}
+
+func convertIP(ip string) {
+       addr, err := ipToAddress(ip)
+       if err != nil {
+               log.Printf("error converting IP: %v", err)
+               return
+       }
+       fmt.Printf("converted IP %q to: %+v\n", ip, addr)
 }
 
 func ipToAddress(ipstr string) (addr ip.Address, err error) {
@@ -98,7 +107,7 @@ func ipToAddress(ipstr string) (addr ip.Address, err error) {
        } else {
                addr.Af = ip_types.ADDRESS_IP4
                var ip4addr ip.IP4Address
-               copy(ip4addr[:], ip4)
+               copy(ip4addr[:], ip4.To4())
                addr.Un.SetIP4(ip4addr)
        }
        return
diff --git a/go.mod b/go.mod
index cff5c18..09512f5 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,13 @@ require (
        github.com/golang/protobuf v1.3.2 // indirect
        github.com/hpcloud/tail v1.0.0 // indirect
        github.com/kr/pretty v0.1.0 // indirect
-       github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42
+       github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe
        github.com/onsi/ginkgo v1.8.0 // indirect
        github.com/onsi/gomega v1.1.0
        github.com/pkg/profile v1.2.1
-       github.com/sirupsen/logrus v1.0.0
+       github.com/sirupsen/logrus v1.6.0
        github.com/stretchr/testify v1.3.0 // indirect
-       golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db // indirect
+       golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
        gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
        gopkg.in/fsnotify.v1 v1.4.7 // indirect
        gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
diff --git a/go.sum b/go.sum
index 78f5650..188240b 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/bennyscetbun/jsongo v1.1.0 h1:ZDSks3aLP13jhY139lWaUqZaU8G0tELMohzumut
 github.com/bennyscetbun/jsongo v1.1.0/go.mod h1:suxbVmjBV8+A2BBAM5EYVh6Uj8j3rqJhzWf3hv7Ff8U=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v0.0.0-20170329110642-4da3e2cfbabc h1:fqUzyjP8DApxXq0dOZJE/NvqQkyjxiTy9ARNyRwBPEw=
 github.com/fsnotify/fsnotify v0.0.0-20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60=
@@ -10,13 +12,15 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42 h1:PzBD7QuxXSgSu61TKXxRwVGzWO5d9QZ0HxFFpndZMCg=
-github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v1.1.0 h1:e3YP4dN/HYPpGh29X1ZkcxcEICsOls9huyVCRBaxjq8=
@@ -25,13 +29,16 @@ github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.0.0 h1:XM8X4m/9ACaclZMs946FQNEZBZafvToJLTR4007drwo=
-github.com/sirupsen/logrus v1.0.0/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db h1:znurcNjtwV7XblDOBERYCP1TUjpwbp8bi3Szx8gbNBE=
-golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
index eb8063a..8bde72c 100644 (file)
@@ -21,14 +21,17 @@ import (
        "time"
 )
 
+// Following variables should normally be updated via `-ldflags "-X ..."`.
+// However, the version string is hard-coded to ensure it is always included
+// even with bare go build/install.
 var (
-       name        = "govpp"
-       version     = "v0.3.5"
-       commitHash  = "unknown"
-       buildBranch = "HEAD"
-       buildStamp  = ""
-       buildUser   = ""
-       buildHost   = ""
+       name       = "govpp"
+       version    = "v0.4.0-dev"
+       commit     = "unknown"
+       branch     = "HEAD"
+       buildStamp = ""
+       buildUser  = ""
+       buildHost  = ""
 
        buildDate time.Time
 )
@@ -41,6 +44,10 @@ func init() {
        buildDate = time.Unix(buildstampInt64, 0)
 }
 
+func Version() string {
+       return version
+}
+
 func Info() string {
        return fmt.Sprintf(`%s %s`, name, version)
 }
@@ -54,7 +61,7 @@ func Verbose() string {
   Build date:  %s
   Go runtime:  %s (%s/%s)`,
                name,
-               version, buildBranch, commitHash,
+               version, branch, commit,
                buildUser, buildHost, buildDate.Format(time.UnixDate),
                runtime.Version(), runtime.GOOS, runtime.GOARCH,
        )