1 // Copyright (c) 2018 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
24 "git.fd.io/govpp.git/api"
25 "github.com/sirupsen/logrus"
29 ErrInvalidRequestCtx = errors.New("invalid request context")
32 // requestCtx is a context for request with single reply
33 type requestCtx struct {
38 // multiRequestCtx is a context for request with multiple responses
39 type multiRequestCtx struct {
44 func (req *requestCtx) ReceiveReply(msg api.Message) error {
45 if req == nil || req.ch == nil {
46 return ErrInvalidRequestCtx
49 lastReplyReceived, err := req.ch.receiveReplyInternal(msg, req.seqNum)
53 if lastReplyReceived {
54 return errors.New("multipart reply recieved while a single reply expected")
60 func (req *multiRequestCtx) ReceiveReply(msg api.Message) (lastReplyReceived bool, err error) {
61 if req == nil || req.ch == nil {
62 return false, ErrInvalidRequestCtx
65 return req.ch.receiveReplyInternal(msg, req.seqNum)
68 // vppRequest is a request that will be sent to VPP.
69 type vppRequest struct {
70 seqNum uint16 // sequence number
71 msg api.Message // binary API message to be send to VPP
72 multi bool // true if multipart response is expected
75 // vppReply is a reply received from VPP.
76 type vppReply struct {
77 seqNum uint16 // sequence number
78 msgID uint16 // ID of the message
79 data []byte // encoded data with the message
80 lastReceived bool // for multi request, true if the last reply has been already received
81 err error // in case of error, data is nil and this member contains error
84 // NotifSubscribeRequest is a request to subscribe for delivery of specific notification messages.
85 type subscriptionRequest struct {
86 sub *api.NotifSubscription // subscription details
87 subscribe bool // true if this is a request to subscribe
90 // channel is the main communication interface with govpp core. It contains four Go channels, one for sending the requests
91 // to VPP, one for receiving the replies from it and the same set for notifications. The user can access the Go channels
92 // via methods provided by Channel interface in this package. Do not use the same channel from multiple goroutines
93 // concurrently, otherwise the responses could mix! Use multiple channels instead.
95 id uint16 // channel ID
97 reqChan chan *vppRequest // channel for sending the requests to VPP
98 replyChan chan *vppReply // channel where VPP replies are delivered to
100 notifSubsChan chan *subscriptionRequest // channel for sending notification subscribe requests
101 notifSubsReplyChan chan error // channel where replies to notification subscribe requests are delivered to
103 msgDecoder api.MessageDecoder // used to decode binary data to generated API messages
104 msgIdentifier api.MessageIdentifier // used to retrieve message ID of a message
106 lastSeqNum uint16 // sequence number of the last sent request
108 delayedReply *vppReply // reply already taken from ReplyChan, buffered for later delivery
109 replyTimeout time.Duration // maximum time that the API waits for a reply from VPP before returning an error, can be set with SetReplyTimeout
112 func (ch *channel) GetID() uint16 {
116 func (ch *channel) nextSeqNum() uint16 {
121 func (ch *channel) SendRequest(msg api.Message) api.RequestCtx {
124 seqNum: ch.nextSeqNum(),
127 return &requestCtx{ch: ch, seqNum: req.seqNum}
130 func (ch *channel) SendMultiRequest(msg api.Message) api.MultiRequestCtx {
133 seqNum: ch.nextSeqNum(),
137 return &multiRequestCtx{ch: ch, seqNum: req.seqNum}
140 func (ch *channel) SubscribeNotification(notifChan chan api.Message, msgFactory func() api.Message) (*api.NotifSubscription, error) {
141 sub := &api.NotifSubscription{
142 NotifChan: notifChan,
143 MsgFactory: msgFactory,
145 // TODO: get rid of notifSubsChan and notfSubsReplyChan,
146 // it's no longer need because we know all message IDs and can store subscription right here
147 ch.notifSubsChan <- &subscriptionRequest{
151 return sub, <-ch.notifSubsReplyChan
154 func (ch *channel) UnsubscribeNotification(subscription *api.NotifSubscription) error {
155 ch.notifSubsChan <- &subscriptionRequest{
159 return <-ch.notifSubsReplyChan
162 func (ch *channel) SetReplyTimeout(timeout time.Duration) {
163 ch.replyTimeout = timeout
166 func (ch *channel) Close() {
167 if ch.reqChan != nil {
172 // receiveReplyInternal receives a reply from the reply channel into the provided msg structure.
173 func (ch *channel) receiveReplyInternal(msg api.Message, expSeqNum uint16) (lastReplyReceived bool, err error) {
176 return false, errors.New("nil message passed in")
179 if vppReply := ch.delayedReply; vppReply != nil {
180 // try the delayed reply
181 ch.delayedReply = nil
182 ignore, lastReplyReceived, err = ch.processReply(vppReply, expSeqNum, msg)
184 return lastReplyReceived, err
188 timer := time.NewTimer(ch.replyTimeout)
191 // blocks until a reply comes to ReplyChan or until timeout expires
192 case vppReply := <-ch.replyChan:
193 ignore, lastReplyReceived, err = ch.processReply(vppReply, expSeqNum, msg)
197 return lastReplyReceived, err
200 err = fmt.Errorf("no reply received within the timeout period %s", ch.replyTimeout)
207 func (ch *channel) processReply(reply *vppReply, expSeqNum uint16, msg api.Message) (ignore bool, lastReplyReceived bool, err error) {
208 // check the sequence number
209 cmpSeqNums := compareSeqNumbers(reply.seqNum, expSeqNum)
210 if cmpSeqNums == -1 {
211 // reply received too late, ignore the message
212 logrus.WithField("sequence-number", reply.seqNum).Warn(
213 "Received reply to an already closed binary API request")
218 ch.delayedReply = reply
219 err = fmt.Errorf("missing binary API reply with sequence number: %d", expSeqNum)
223 if reply.err != nil {
227 if reply.lastReceived {
228 lastReplyReceived = true
234 expMsgID, err = ch.msgIdentifier.GetMessageID(msg)
236 err = fmt.Errorf("message %s with CRC %s is not compatible with the VPP we are connected to",
237 msg.GetMessageName(), msg.GetCrcString())
241 if reply.msgID != expMsgID {
242 var msgNameCrc string
243 if replyMsg, err := ch.msgIdentifier.LookupByID(reply.msgID); err != nil {
244 msgNameCrc = err.Error()
246 msgNameCrc = getMsgNameWithCrc(replyMsg)
249 err = fmt.Errorf("received invalid message ID (seqNum=%d), expected %d (%s), but got %d (%s) "+
250 "(check if multiple goroutines are not sharing single GoVPP channel)",
251 reply.seqNum, expMsgID, msg.GetMessageName(), reply.msgID, msgNameCrc)
255 // decode the message
256 if err = ch.msgDecoder.DecodeMsg(reply.data, msg); err != nil {
260 // check Retval and convert it into VnetAPIError error
261 if strings.HasSuffix(msg.GetMessageName(), "_reply") {
262 // TODO: use categories for messages to avoid checking message name
263 if f := reflect.Indirect(reflect.ValueOf(msg)).FieldByName("Retval"); f.IsValid() {
264 if retval := f.Int(); retval != 0 {
265 err = api.VPPApiError(retval)