Pair requests with replies using sequence numbers
[govpp.git] / adapter / mock / mock_adapter.go
1 // Copyright (c) 2017 Cisco and/or its affiliates.
2 //
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:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 // Package mock is an alternative VPP adapter aimed for unit/integration testing where the
16 // actual communication with VPP is not demanded.
17 package mock
18
19 import (
20         "bytes"
21         "log"
22         "reflect"
23         "sync"
24
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/core"
29
30         "github.com/lunixbochs/struc"
31 )
32
33 type replyMode int
34
35 const (
36         _                replyMode = 0
37         useRepliesQueue            = 1 // use replies in the queue
38         useReplyHandlers           = 2 // use reply handler
39 )
40
41 // VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
42 type VppAdapter struct {
43         callback func(context uint32, msgId uint16, data []byte)
44
45         msgNameToIds map[string]uint16
46         msgIDsToName map[uint16]string
47         msgIDSeq     uint16
48         binAPITypes  map[string]reflect.Type
49         access       sync.RWMutex
50
51         replies       []reply            // FIFO queue of messages
52         replyHandlers []ReplyHandler     // callbacks that are able to calculate mock responses
53         repliesLock   sync.Mutex         // mutex for the queue
54         mode          replyMode          // mode in which the mock operates
55 }
56
57 // defaultReply is a default reply message that mock adapter returns for a request.
58 type defaultReply struct {
59         Retval int32
60 }
61
62 // MessageDTO is a structure used for propagating information to ReplyHandlers.
63 type MessageDTO struct {
64         MsgID    uint16
65         MsgName  string
66         ClientID uint32
67         Data     []byte
68 }
69
70 type reply struct {
71         msg       api.Message
72         multipart bool
73         hasSeqNum bool
74         seqNum    uint16
75 }
76
77 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
78 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
79 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
80
81 const (
82         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
83 )
84
85 // NewVppAdapter returns a new mock adapter.
86 func NewVppAdapter() adapter.VppAdapter {
87         return &VppAdapter{}
88 }
89
90 // Connect emulates connecting the process to VPP.
91 func (a *VppAdapter) Connect() error {
92         return nil
93 }
94
95 // Disconnect emulates disconnecting the process from VPP.
96 func (a *VppAdapter) Disconnect() {
97         // no op
98 }
99
100 // GetMsgNameByID returns message name for specified message ID.
101 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
102         switch msgID {
103         case 100:
104                 return "control_ping", true
105         case 101:
106                 return "control_ping_reply", true
107         case 200:
108                 return "sw_interface_dump", true
109         case 201:
110                 return "sw_interface_details", true
111         }
112
113         a.access.Lock()
114         defer a.access.Unlock()
115         a.initMaps()
116         msgName, found := a.msgIDsToName[msgID]
117
118         return msgName, found
119 }
120
121 // RegisterBinAPITypes registers binary API message types in the mock adapter.
122 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
123         a.access.Lock()
124         defer a.access.Unlock()
125         a.initMaps()
126         for _, v := range binAPITypes {
127                 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
128                         a.binAPITypes[msg.GetMessageName()] = v
129                 }
130         }
131 }
132
133 // ReplyTypeFor returns reply message type for given request message name.
134 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
135         replyName, foundName := binapi.ReplyNameFor(requestMsgName)
136         if foundName {
137                 if reply, found := a.binAPITypes[replyName]; found {
138                         msgID, err := a.GetMsgID(replyName, "")
139                         if err == nil {
140                                 return reply, msgID, found
141                         }
142                 }
143         }
144
145         return nil, 0, false
146 }
147
148 // ReplyFor returns reply message for given request message name.
149 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
150         replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
151         if foundReplType {
152                 msgVal := reflect.New(replType)
153                 if msg, ok := msgVal.Interface().(api.Message); ok {
154                         log.Println("FFF ", replType, msgID, foundReplType)
155                         return msg, msgID, true
156                 }
157         }
158
159         return nil, 0, false
160 }
161
162 // ReplyBytes encodes the mocked reply into binary format.
163 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
164         replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
165         if err != nil {
166                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
167                         " ", err)
168                 return nil, err
169         }
170         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
171
172         buf := new(bytes.Buffer)
173         struc.Pack(buf, &core.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
174         struc.Pack(buf, reply)
175
176         return buf.Bytes(), nil
177 }
178
179 // GetMsgID returns mocked message ID for the given message name and CRC.
180 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
181         switch msgName {
182         case "control_ping":
183                 return 100, nil
184         case "control_ping_reply":
185                 return 101, nil
186         case "sw_interface_dump":
187                 return 200, nil
188         case "sw_interface_details":
189                 return 201, nil
190         }
191
192         a.access.Lock()
193         defer a.access.Unlock()
194         a.initMaps()
195
196         msgID, found := a.msgNameToIds[msgName]
197         if found {
198                 return msgID, nil
199         }
200
201         a.msgIDSeq++
202         msgID = a.msgIDSeq
203         a.msgNameToIds[msgName] = msgID
204         a.msgIDsToName[msgID] = msgName
205
206         log.Println("VPP GetMessageId ", msgID, " name:", msgName, " crc:", msgCrc)
207
208         return msgID, nil
209 }
210
211 // initMaps initializes internal maps (if not already initialized).
212 func (a *VppAdapter) initMaps() {
213         if a.msgIDsToName == nil {
214                 a.msgIDsToName = map[uint16]string{}
215                 a.msgNameToIds = map[string]uint16{}
216                 a.msgIDSeq = 1000
217         }
218
219         if a.binAPITypes == nil {
220                 a.binAPITypes = map[string]reflect.Type{}
221         }
222 }
223
224 // SendMsg emulates sending a binary-encoded message to VPP.
225 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
226         switch a.mode {
227         case useReplyHandlers:
228                 a.initMaps()
229                 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
230                         replyHandler := a.replyHandlers[i]
231
232                         buf := bytes.NewReader(data)
233                         reqHeader := core.VppRequestHeader{}
234                         struc.Unpack(buf, &reqHeader)
235
236                         a.access.Lock()
237                         reqMsgName := a.msgIDsToName[reqHeader.VlMsgID]
238                         a.access.Unlock()
239
240                         reply, msgID, finished := replyHandler(MessageDTO{
241                                 MsgID:    reqHeader.VlMsgID,
242                                 MsgName:  reqMsgName,
243                                 ClientID: clientID,
244                                 Data:     data,
245                         })
246                         if finished {
247                                 a.callback(clientID, msgID, reply)
248                                 return nil
249                         }
250                 }
251                 fallthrough
252         case useRepliesQueue:
253                 a.repliesLock.Lock()
254                 defer a.repliesLock.Unlock()
255
256                 // pop all replies from queue
257                 withSeqNums := false
258                 for i, reply := range a.replies {
259                         if i > 0 && reply.msg.GetMessageName() == "control_ping_reply" && !withSeqNums {
260                                 // hack - do not send control_ping_reply immediately, leave it for the the next callback
261                                 a.replies = a.replies[i:]
262                                 return nil
263                         }
264                         msgID, _ := a.GetMsgID(reply.msg.GetMessageName(), reply.msg.GetCrcString())
265                         buf := new(bytes.Buffer)
266                         context := clientID
267                         context = setMultipart(context, reply.multipart)
268                         if reply.hasSeqNum {
269                                 withSeqNums = true
270                                 context = setSeqNum(context, reply.seqNum)
271                         }
272                         if reply.msg.GetMessageType() == api.ReplyMessage {
273                                 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: context})
274                         } else if reply.msg.GetMessageType() == api.EventMessage {
275                                 struc.Pack(buf, &core.VppEventHeader{VlMsgID: msgID, Context: context})
276                         } else if reply.msg.GetMessageType() == api.RequestMessage {
277                                 struc.Pack(buf, &core.VppRequestHeader{VlMsgID: msgID, Context: context})
278                         } else {
279                                 struc.Pack(buf, &core.VppOtherHeader{VlMsgID: msgID})
280                         }
281                         struc.Pack(buf, reply.msg)
282                         a.callback(context, msgID, buf.Bytes())
283                 }
284                 if len(a.replies) > 0 {
285                         a.replies = []reply{}
286                         if len(a.replyHandlers) > 0 {
287                                 // Switch back to handlers once the queue is empty to revert back
288                                 // the fallthrough effect.
289                                 a.mode = useReplyHandlers
290                         }
291                         return nil
292                 }
293
294                 //fallthrough
295         default:
296                 // return default reply
297                 buf := new(bytes.Buffer)
298                 msgID := uint16(defaultReplyMsgID)
299                 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: clientID})
300                 struc.Pack(buf, &defaultReply{})
301                 a.callback(clientID, msgID, buf.Bytes())
302         }
303         return nil
304 }
305
306 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
307 func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
308         a.callback = cb
309 }
310
311 // WaitReady mocks waiting for VPP
312 func (a *VppAdapter) WaitReady() error {
313         return nil
314 }
315
316 // MockReply stores a message to be returned when the next request comes. It is a FIFO queue - multiple replies
317 // can be pushed into it, the first one will be popped when some request comes.
318 // Using of this method automatically switches the mock into th useRepliesQueue mode.
319 func (a *VppAdapter) MockReply(replyMsg api.Message, isMultipart bool) {
320         a.repliesLock.Lock()
321         defer a.repliesLock.Unlock()
322
323         a.replies = append(a.replies, reply{msg: replyMsg, multipart: isMultipart, hasSeqNum: false})
324         a.mode = useRepliesQueue
325 }
326
327 // MockReplyWithSeqNum queues next reply like MockReply() does, except that the
328 // sequence number can be customized and not necessarily match with the request.
329 func (a *VppAdapter) MockReplyWithSeqNum(replyMsg api.Message, isMultipart bool, sequenceNum uint16) {
330         a.repliesLock.Lock()
331         defer a.repliesLock.Unlock()
332
333         a.replies = append(a.replies, reply{msg: replyMsg, multipart: isMultipart, hasSeqNum: true, seqNum: sequenceNum})
334         a.mode = useRepliesQueue
335 }
336
337 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
338 // Using of this method automatically switches the mock into th useReplyHandlers mode.
339 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
340         a.repliesLock.Lock()
341         defer a.repliesLock.Unlock()
342
343         a.replyHandlers = append(a.replyHandlers, replyHandler)
344         a.mode = useReplyHandlers
345 }
346
347 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
348         context &= 0xffff0000
349         context |= uint32(seqNum)
350         return context
351 }
352
353 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
354         context &= 0xfffeffff
355         if isMultipart {
356                 context |= 1 << 16
357         }
358         return context
359 }
360