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"
29 "github.com/lunixbochs/struc"
36 useRepliesQueue // use replies in the queue
37 useReplyHandlers // use reply handler
40 // VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
41 type VppAdapter struct {
42 callback adapter.MsgCallback
46 msgNameToIds map[string]uint16
47 msgIDsToName map[uint16]string
48 binAPITypes map[string]reflect.Type
50 repliesLock sync.Mutex // mutex for the queue
51 replies []reply // FIFO queue of messages
52 replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
53 mode replyMode // mode in which the mock operates
56 // defaultReply is a default reply message that mock adapter returns for a request.
57 type defaultReply struct {
61 // MessageDTO is a structure used for propagating information to ReplyHandlers.
62 type MessageDTO struct {
69 // reply for one request (can be multipart, contain replies to previously timeouted requests, etc.)
74 // MsgWithContext encapsulates reply message with possibly sequence number and is-multipart flag.
75 type MsgWithContext struct {
80 /* set by mock adapter */
84 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
85 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
86 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
89 defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
92 // NewVppAdapter returns a new mock adapter.
93 func NewVppAdapter() adapter.VppAdapter {
97 // Connect emulates connecting the process to VPP.
98 func (a *VppAdapter) Connect() error {
102 // Disconnect emulates disconnecting the process from VPP.
103 func (a *VppAdapter) Disconnect() {
107 // GetMsgNameByID returns message name for specified message ID.
108 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
111 return "control_ping", true
113 return "control_ping_reply", true
115 return "sw_interface_dump", true
117 return "sw_interface_details", true
121 defer a.access.Unlock()
123 msgName, found := a.msgIDsToName[msgID]
125 return msgName, found
128 // RegisterBinAPITypes registers binary API message types in the mock adapter.
129 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
131 defer a.access.Unlock()
133 for _, v := range binAPITypes {
134 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
135 a.binAPITypes[msg.GetMessageName()] = v
140 // ReplyTypeFor returns reply message type for given request message name.
141 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
142 replyName, foundName := binapi.ReplyNameFor(requestMsgName)
144 if reply, found := a.binAPITypes[replyName]; found {
145 msgID, err := a.GetMsgID(replyName, "")
147 return reply, msgID, found
155 // ReplyFor returns reply message for given request message name.
156 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
157 replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
159 msgVal := reflect.New(replType)
160 if msg, ok := msgVal.Interface().(api.Message); ok {
161 log.Println("FFF ", replType, msgID, foundReplType)
162 return msg, msgID, true
169 // ReplyBytes encodes the mocked reply into binary format.
170 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
171 replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
173 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
177 log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
179 buf := new(bytes.Buffer)
180 struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
181 struc.Pack(buf, reply)
183 return buf.Bytes(), nil
186 // GetMsgID returns mocked message ID for the given message name and CRC.
187 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
191 case "control_ping_reply":
193 case "sw_interface_dump":
195 case "sw_interface_details":
200 defer a.access.Unlock()
203 msgID, found := a.msgNameToIds[msgName]
210 a.msgNameToIds[msgName] = msgID
211 a.msgIDsToName[msgID] = msgName
216 // initMaps initializes internal maps (if not already initialized).
217 func (a *VppAdapter) initMaps() {
218 if a.msgIDsToName == nil {
219 a.msgIDsToName = map[uint16]string{}
220 a.msgNameToIds = map[string]uint16{}
224 if a.binAPITypes == nil {
225 a.binAPITypes = map[string]reflect.Type{}
229 // SendMsg emulates sending a binary-encoded message to VPP.
230 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
232 case useReplyHandlers:
234 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
235 replyHandler := a.replyHandlers[i]
237 buf := bytes.NewReader(data)
238 reqHeader := codec.VppRequestHeader{}
239 struc.Unpack(buf, &reqHeader)
242 reqMsgName := a.msgIDsToName[reqHeader.VlMsgID]
245 reply, msgID, finished := replyHandler(MessageDTO{
246 MsgID: reqHeader.VlMsgID,
252 a.callback(msgID, clientID, reply)
258 case useRepliesQueue:
260 defer a.repliesLock.Unlock()
262 // pop the first reply
263 if len(a.replies) > 0 {
264 reply := a.replies[0]
265 for _, msg := range reply.msgs {
266 msgID, _ := a.GetMsgID(msg.Msg.GetMessageName(), msg.Msg.GetCrcString())
267 buf := new(bytes.Buffer)
270 context = setMultipart(context, msg.Multipart)
271 context = setSeqNum(context, msg.SeqNum)
273 if msg.Msg.GetMessageType() == api.ReplyMessage {
274 struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: context})
275 } else if msg.Msg.GetMessageType() == api.RequestMessage {
276 struc.Pack(buf, &codec.VppRequestHeader{VlMsgID: msgID, Context: context})
277 } else if msg.Msg.GetMessageType() == api.EventMessage {
278 struc.Pack(buf, &codec.VppEventHeader{VlMsgID: msgID})
280 struc.Pack(buf, &codec.VppOtherHeader{VlMsgID: msgID})
282 struc.Pack(buf, msg.Msg)
283 a.callback(msgID, context, buf.Bytes())
286 a.replies = a.replies[1:]
287 if len(a.replies) == 0 && len(a.replyHandlers) > 0 {
288 // Switch back to handlers once the queue is empty to revert back
289 // the fallthrough effect.
290 a.mode = useReplyHandlers
297 // return default reply
298 buf := new(bytes.Buffer)
299 msgID := uint16(defaultReplyMsgID)
300 struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: clientID})
301 struc.Pack(buf, &defaultReply{})
302 a.callback(msgID, clientID, buf.Bytes())
307 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
308 func (a *VppAdapter) SetMsgCallback(cb adapter.MsgCallback) {
312 // WaitReady mocks waiting for VPP
313 func (a *VppAdapter) WaitReady() error {
317 // MockReply stores a message or a list of multipart messages to be returned when
318 // the next request comes. It is a FIFO queue - multiple replies can be pushed into it,
319 // the first message or the first set of multi-part messages will be popped when
320 // some request comes.
321 // Using of this method automatically switches the mock into the useRepliesQueue mode.
323 // Note: multipart requests are implemented using two requests actually - the multipart
324 // request itself followed by control ping used to tell which multipart message
325 // is the last one. A mock reply to a multipart request has to thus consist of
326 // exactly two calls of this method.
329 // mockVpp.MockReply( // push multipart messages all at once
330 // &interfaces.SwInterfaceDetails{SwIfIndex:1},
331 // &interfaces.SwInterfaceDetails{SwIfIndex:2},
332 // &interfaces.SwInterfaceDetails{SwIfIndex:3},
334 // mockVpp.MockReply(&vpe.ControlPingReply{})
336 // Even if the multipart request has no replies, MockReply has to be called twice:
338 // mockVpp.MockReply() // zero multipart messages
339 // mockVpp.MockReply(&vpe.ControlPingReply{})
340 func (a *VppAdapter) MockReply(msgs ...api.Message) {
342 defer a.repliesLock.Unlock()
345 for _, msg := range msgs {
346 r.msgs = append(r.msgs, MsgWithContext{Msg: msg, hasCtx: false})
348 a.replies = append(a.replies, r)
349 a.mode = useRepliesQueue
352 // MockReplyWithContext queues next reply like MockReply() does, except that the
353 // sequence number and multipart flag (= context minus channel ID) can be customized
354 // and not necessarily match with the request.
355 // The purpose of this function is to test handling of sequence numbers and as such
356 // it is not really meant to be used outside the govpp UTs.
357 func (a *VppAdapter) MockReplyWithContext(msgs ...MsgWithContext) {
359 defer a.repliesLock.Unlock()
362 for _, msg := range msgs {
363 r.msgs = append(r.msgs,
364 MsgWithContext{Msg: msg.Msg, SeqNum: msg.SeqNum, Multipart: msg.Multipart, hasCtx: true})
366 a.replies = append(a.replies, r)
367 a.mode = useRepliesQueue
370 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
371 // Using of this method automatically switches the mock into th useReplyHandlers mode.
372 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
374 defer a.repliesLock.Unlock()
376 a.replyHandlers = append(a.replyHandlers, replyHandler)
377 a.mode = useReplyHandlers
380 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
381 context &= 0xffff0000
382 context |= uint32(seqNum)
386 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
387 context &= 0xfffeffff