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 "github.com/lunixbochs/struc"
27 "git.fd.io/govpp.git/adapter"
28 "git.fd.io/govpp.git/adapter/mock/binapi_reflect"
29 "git.fd.io/govpp.git/api"
32 // VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
33 type VppAdapter struct {
34 callback func(context uint32, msgId uint16, data []byte)
36 msgNameToIds *map[string]uint16
37 msgIdsToName *map[uint16]string
39 binApiTypes map[string]reflect.Type
43 // replyHeader represents a common header of each VPP request message.
44 type requestHeader struct {
50 // replyHeader represents a common header of each VPP reply message.
51 type replyHeader struct {
56 // replyHeader represents a common header of each VPP reply message.
57 type vppOtherHeader struct {
61 // defaultReply is a default reply message that mock adapter returns for a request.
62 type defaultReply struct {
66 // MessageDTO is a structure used for propageating informations to ReplyHandlers
67 type MessageDTO struct {
74 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
75 // Return value prepared is used to signalize that mock reply is calculated.
76 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, prepared bool)
79 //defaultMsgID = 1 // default message ID to be returned from GetMsgId
80 defaultReplyMsgID = 2 // default message ID for the reply to be sent back via callback
83 var replies []api.Message // FIFO queue of messages
84 var replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
85 var repliesLock sync.Mutex // mutex for the queue
88 const useRepliesQueue = 1 // use replies in the queue instead of the default one
89 const useReplyHandlers = 2 //use ReplyHandler
91 // NewVppAdapter returns a new mock adapter.
92 func NewVppAdapter() adapter.VppAdapter {
96 // Connect emulates connecting the process to VPP.
97 func (a *VppAdapter) Connect() error {
101 // Disconnect emulates disconnecting the process from VPP.
102 func (a *VppAdapter) Disconnect() {
106 func (a *VppAdapter) GetMsgNameByID(msgId uint16) (string, bool) {
109 return "control_ping", true
111 return "control_ping_reply", true
113 return "sw_interface_dump", true
115 return "sw_interface_details", true
119 defer a.access.Unlock()
121 msgName, found := (*a.msgIdsToName)[msgId]
123 return msgName, found
126 func (a *VppAdapter) RegisterBinApiTypes(binApiTypes map[string]reflect.Type) {
128 defer a.access.Unlock()
130 for _, v := range binApiTypes {
131 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
132 a.binApiTypes[msg.GetMessageName()] = v
137 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
138 replyName, foundName := binapi_reflect.ReplyNameFor(requestMsgName)
140 if reply, found := a.binApiTypes[replyName]; found {
141 msgID, err := a.GetMsgID(replyName, "")
143 return reply, msgID, found
151 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
152 replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
154 msgVal := reflect.New(replType)
155 if msg, ok := msgVal.Interface().(api.Message); ok {
156 log.Println("FFF ", replType, msgID, foundReplType)
157 return msg, msgID, true
164 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
165 replyMsgId, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
167 log.Println("ReplyBytesE ", replyMsgId, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
171 log.Println("ReplyBytes ", replyMsgId, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
173 buf := new(bytes.Buffer)
174 struc.Pack(buf, &replyHeader{VlMsgID: replyMsgId, Context: request.ClientID})
175 struc.Pack(buf, reply)
177 return buf.Bytes(), nil
180 // GetMsgID returns mocked message ID for the given message name and CRC.
181 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
185 case "control_ping_reply":
187 case "sw_interface_dump":
189 case "sw_interface_details":
194 defer a.access.Unlock()
197 if msgId, found := (*a.msgNameToIds)[msgName]; found {
202 (*a.msgNameToIds)[msgName] = msgId
203 (*a.msgIdsToName)[msgId] = msgName
205 log.Println("VPP GetMessageId ", msgId, " name:", msgName, " crc:", msgCrc)
211 func (a *VppAdapter) initMaps() {
212 if a.msgIdsToName == nil {
213 a.msgIdsToName = &map[uint16]string{}
214 a.msgNameToIds = &map[string]uint16{}
218 if a.binApiTypes == nil {
219 a.binApiTypes = map[string]reflect.Type{}
223 // SendMsg emulates sending a binary-encoded message to VPP.
224 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
226 case useReplyHandlers:
228 for i := len(replyHandlers) - 1; i >= 0; i-- {
229 replyHandler := replyHandlers[i]
231 buf := bytes.NewReader(data)
232 reqHeader := requestHeader{}
233 struc.Unpack(buf, &reqHeader)
236 reqMsgName, _ := (*a.msgIdsToName)[reqHeader.VlMsgID]
239 reply, msgID, finished := replyHandler(MessageDTO{reqHeader.VlMsgID, reqMsgName,
242 a.callback(clientID, msgID, reply)
247 case useRepliesQueue:
249 defer repliesLock.Unlock()
251 // pop all replies from queue
252 for i, reply := range replies {
253 if i > 0 && reply.GetMessageName() == "control_ping_reply" {
254 // hack - do not send control_ping_reply immediately, leave it for the the next callback
255 replies = []api.Message{}
256 replies = append(replies, reply)
259 msgID, _ := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
260 buf := new(bytes.Buffer)
261 if reply.GetMessageType() == api.ReplyMessage {
262 struc.Pack(buf, &replyHeader{VlMsgID: msgID, Context: clientID})
264 struc.Pack(buf, &requestHeader{VlMsgID: msgID, Context: clientID})
266 struc.Pack(buf, reply)
267 a.callback(clientID, msgID, buf.Bytes())
269 if len(replies) > 0 {
270 replies = []api.Message{}
276 // return default reply
277 buf := new(bytes.Buffer)
278 msgID := uint16(defaultReplyMsgID)
279 struc.Pack(buf, &replyHeader{VlMsgID: msgID, Context: clientID})
280 struc.Pack(buf, &defaultReply{})
281 a.callback(clientID, msgID, buf.Bytes())
286 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
287 func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
291 // MockReply stores a message to be returned when the next request comes. It is a FIFO queue - multiple replies
292 // can be pushed into it, the first one will be popped when some request comes.
294 // It is able to also receive callback that calculates the reply
295 func (a *VppAdapter) MockReply(msg api.Message) {
297 defer repliesLock.Unlock()
299 replies = append(replies, msg)
300 mode = useRepliesQueue
303 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
305 defer repliesLock.Unlock()
307 replyHandlers = append(replyHandlers, replyHandler)
308 mode = useReplyHandlers