1 // Copyright (c) 2017 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.
15 // Package mock is an alternative VPP adapter aimed for unit/integration testing where the
16 // actual communication with VPP is not demanded.
25 "git.fd.io/govpp.git/adapter"
26 "git.fd.io/govpp.git/adapter/mock/binapi"
27 "git.fd.io/govpp.git/api"
28 "git.fd.io/govpp.git/codec"
35 useRepliesQueue // use replies in the queue
36 useReplyHandlers // use reply handler
39 // VppAPI represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
40 type VppAdapter struct {
41 callback adapter.MsgCallback
45 msgNameToIds map[string]uint16
46 msgIDsToName map[uint16]string
47 binAPITypes map[string]map[string]reflect.Type
49 repliesLock sync.Mutex // mutex for the queue
50 replies []reply // FIFO queue of messages
51 replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
52 mode replyMode // mode in which the mock operates
55 // defaultReply is a default reply message that mock adapter returns for a request.
56 type defaultReply struct {
60 func (*defaultReply) GetMessageName() string { return "mock_default_reply" }
61 func (*defaultReply) GetCrcString() string { return "xxxxxxxx" }
62 func (*defaultReply) GetMessageType() api.MessageType {
63 return api.ReplyMessage
65 func (m *defaultReply) Size() int {
74 func (m *defaultReply) Marshal(b []byte) ([]byte, error) {
77 buf = codec.NewBuffer(make([]byte, m.Size()))
79 buf = codec.NewBuffer(b)
82 buf.EncodeUint32(uint32(m.Retval))
83 return buf.Bytes(), nil
85 func (m *defaultReply) Unmarshal(b []byte) error {
86 buf := codec.NewBuffer(b)
88 m.Retval = int32(buf.DecodeUint32())
92 // MessageDTO is a structure used for propagating information to ReplyHandlers.
93 type MessageDTO struct {
100 // reply for one request (can be multipart, contain replies to previously timeouted requests, etc.)
102 msgs []MsgWithContext
105 // MsgWithContext encapsulates reply message with possibly sequence number and is-multipart flag.
106 type MsgWithContext struct {
111 /* set by mock adapter */
115 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
116 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
117 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
120 defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
123 // NewVppAdapter returns a new mock adapter.
124 func NewVppAdapter() *VppAdapter {
127 msgIDsToName: make(map[uint16]string),
128 msgNameToIds: make(map[string]uint16),
129 binAPITypes: make(map[string]map[string]reflect.Type),
131 a.registerBinAPITypes()
135 // Connect emulates connecting the process to VPP.
136 func (a *VppAdapter) Connect() error {
140 // Disconnect emulates disconnecting the process from VPP.
141 func (a *VppAdapter) Disconnect() error {
145 // GetMsgNameByID returns message name for specified message ID.
146 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
149 return "control_ping", true
151 return "control_ping_reply", true
153 return "sw_interface_dump", true
155 return "sw_interface_details", true
159 defer a.access.Unlock()
160 msgName, found := a.msgIDsToName[msgID]
162 return msgName, found
165 func (a *VppAdapter) registerBinAPITypes() {
167 defer a.access.Unlock()
168 for pkg, msgs := range api.GetRegisteredMessages() {
169 msgMap := make(map[string]reflect.Type)
170 for _, msg := range msgs {
171 msgMap[msg.GetMessageName()] = reflect.TypeOf(msg).Elem()
173 a.binAPITypes[pkg] = msgMap
177 // ReplyTypeFor returns reply message type for given request message name.
178 func (a *VppAdapter) ReplyTypeFor(pkg, requestMsgName string) (reflect.Type, uint16, bool) {
179 replyName, foundName := binapi.ReplyNameFor(requestMsgName)
181 if messages, found := a.binAPITypes[pkg]; found {
182 if reply, found := messages[replyName]; found {
183 msgID, err := a.GetMsgID(replyName, "")
185 return reply, msgID, found
194 // ReplyFor returns reply message for given request message name.
195 func (a *VppAdapter) ReplyFor(pkg, requestMsgName string) (api.Message, uint16, bool) {
196 replType, msgID, foundReplType := a.ReplyTypeFor(pkg, requestMsgName)
198 msgVal := reflect.New(replType)
199 if msg, ok := msgVal.Interface().(api.Message); ok {
200 log.Println("FFF ", replType, msgID, foundReplType)
201 return msg, msgID, true
208 // ReplyBytes encodes the mocked reply into binary format.
209 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
210 replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
212 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
216 log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
218 data, err := codec.DefaultCodec.EncodeMsg(reply, replyMsgID)
222 if reply.GetMessageType() == api.ReplyMessage {
223 binary.BigEndian.PutUint32(data[2:6], request.ClientID)
224 } else if reply.GetMessageType() == api.RequestMessage {
225 binary.BigEndian.PutUint32(data[6:10], request.ClientID)
230 // GetMsgID returns mocked message ID for the given message name and CRC.
231 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
235 case "control_ping_reply":
237 case "sw_interface_dump":
239 case "sw_interface_details":
244 defer a.access.Unlock()
246 msgID, found := a.msgNameToIds[msgName]
253 a.msgNameToIds[msgName] = msgID
254 a.msgIDsToName[msgID] = msgName
259 // SendMsg emulates sending a binary-encoded message to VPP.
260 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
263 a.repliesLock.Unlock()
265 case useReplyHandlers:
266 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
267 replyHandler := a.replyHandlers[i]
269 msgID := binary.BigEndian.Uint16(data[0:2])
272 reqMsgName := a.msgIDsToName[msgID]
275 reply, msgID, finished := replyHandler(MessageDTO{
282 a.callback(msgID, reply)
288 case useRepliesQueue:
290 defer a.repliesLock.Unlock()
292 // pop the first reply
293 if len(a.replies) > 0 {
294 reply := a.replies[0]
295 for _, msg := range reply.msgs {
296 msgID, _ := a.GetMsgID(msg.Msg.GetMessageName(), msg.Msg.GetCrcString())
299 context = setMultipart(context, msg.Multipart)
300 context = setSeqNum(context, msg.SeqNum)
302 data, err := codec.DefaultCodec.EncodeMsg(msg.Msg, msgID)
306 if msg.Msg.GetMessageType() == api.ReplyMessage {
307 binary.BigEndian.PutUint32(data[2:6], context)
308 } else if msg.Msg.GetMessageType() == api.RequestMessage {
309 binary.BigEndian.PutUint32(data[6:10], context)
311 a.callback(msgID, data)
314 a.replies = a.replies[1:]
315 if len(a.replies) == 0 && len(a.replyHandlers) > 0 {
316 // Switch back to handlers once the queue is empty to revert back
317 // the fallthrough effect.
318 a.mode = useReplyHandlers
325 // return default reply
326 msgID := uint16(defaultReplyMsgID)
327 data, err := codec.DefaultCodec.EncodeMsg(&defaultReply{}, msgID)
331 binary.BigEndian.PutUint32(data[2:6], clientID)
332 a.callback(msgID, data)
337 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
338 func (a *VppAdapter) SetMsgCallback(cb adapter.MsgCallback) {
342 // WaitReady mocks waiting for VPP
343 func (a *VppAdapter) WaitReady() error {
347 // MockReply stores a message or a list of multipart messages to be returned when
348 // the next request comes. It is a FIFO queue - multiple replies can be pushed into it,
349 // the first message or the first set of multi-part messages will be popped when
350 // some request comes.
351 // Using of this method automatically switches the mock into the useRepliesQueue mode.
353 // Note: multipart requests are implemented using two requests actually - the multipart
354 // request itself followed by control ping used to tell which multipart message
355 // is the last one. A mock reply to a multipart request has to thus consist of
356 // exactly two calls of this method.
359 // mockVpp.MockReply( // push multipart messages all at once
360 // &interfaces.SwInterfaceDetails{SwIfIndex:1},
361 // &interfaces.SwInterfaceDetails{SwIfIndex:2},
362 // &interfaces.SwInterfaceDetails{SwIfIndex:3},
364 // mockVpp.MockReply(&vpe.ControlPingReply{})
366 // Even if the multipart request has no replies, MockReply has to be called twice:
368 // mockVpp.MockReply() // zero multipart messages
369 // mockVpp.MockReply(&vpe.ControlPingReply{})
370 func (a *VppAdapter) MockReply(msgs ...api.Message) {
372 defer a.repliesLock.Unlock()
375 for _, msg := range msgs {
376 r.msgs = append(r.msgs, MsgWithContext{Msg: msg, hasCtx: false})
378 a.replies = append(a.replies, r)
379 a.mode = useRepliesQueue
382 // MockReplyWithContext queues next reply like MockReply() does, except that the
383 // sequence number and multipart flag (= context minus channel ID) can be customized
384 // and not necessarily match with the request.
385 // The purpose of this function is to test handling of sequence numbers and as such
386 // it is not really meant to be used outside the govpp UTs.
387 func (a *VppAdapter) MockReplyWithContext(msgs ...MsgWithContext) {
389 defer a.repliesLock.Unlock()
392 for _, msg := range msgs {
393 r.msgs = append(r.msgs,
394 MsgWithContext{Msg: msg.Msg, SeqNum: msg.SeqNum, Multipart: msg.Multipart, hasCtx: true})
396 a.replies = append(a.replies, r)
397 a.mode = useRepliesQueue
400 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
401 // Using of this method automatically switches the mock into th useReplyHandlers mode.
402 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
404 defer a.repliesLock.Unlock()
406 a.replyHandlers = append(a.replyHandlers, replyHandler)
407 a.mode = useReplyHandlers
410 // MockClearReplyHanders clears all reply handlers that were registered
411 // Will also set the mode to useReplyHandlers
412 func (a *VppAdapter) MockClearReplyHandlers() {
414 defer a.repliesLock.Unlock()
416 a.replyHandlers = a.replyHandlers[:0]
417 a.mode = useReplyHandlers
420 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
421 context &= 0xffff0000
422 context |= uint32(seqNum)
426 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
427 context &= 0xfffeffff