Change module name to go.fd.io/govpp
[govpp.git] / adapter / mock / mock_vpp_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         "encoding/binary"
21         "log"
22         "reflect"
23         "sync"
24
25         "go.fd.io/govpp/adapter"
26         "go.fd.io/govpp/adapter/mock/binapi"
27         "go.fd.io/govpp/api"
28         "go.fd.io/govpp/codec"
29 )
30
31 type replyMode int
32
33 const (
34         _                replyMode = iota
35         useRepliesQueue            // use replies in the queue
36         useReplyHandlers           // use reply handler
37 )
38
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
42
43         msgIDSeq     uint16
44         access       sync.RWMutex
45         msgNameToIds map[string]uint16
46         msgIDsToName map[uint16]string
47         binAPITypes  map[string]map[string]reflect.Type
48
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
53 }
54
55 // defaultReply is a default reply message that mock adapter returns for a request.
56 type defaultReply struct {
57         Retval int32
58 }
59
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
64 }
65 func (m *defaultReply) Size() int {
66         if m == nil {
67                 return 0
68         }
69         var size int
70         // field[1] m.Retval
71         size += 4
72         return size
73 }
74 func (m *defaultReply) Marshal(b []byte) ([]byte, error) {
75         var buf *codec.Buffer
76         if b == nil {
77                 buf = codec.NewBuffer(make([]byte, m.Size()))
78         } else {
79                 buf = codec.NewBuffer(b)
80         }
81         // field[1] m.Retval
82         buf.EncodeUint32(uint32(m.Retval))
83         return buf.Bytes(), nil
84 }
85 func (m *defaultReply) Unmarshal(b []byte) error {
86         buf := codec.NewBuffer(b)
87         // field[1] m.Retval
88         m.Retval = int32(buf.DecodeUint32())
89         return nil
90 }
91
92 // MessageDTO is a structure used for propagating information to ReplyHandlers.
93 type MessageDTO struct {
94         MsgID    uint16
95         MsgName  string
96         ClientID uint32
97         Data     []byte
98 }
99
100 // reply for one request (can be multipart, contain replies to previously timeouted requests, etc.)
101 type reply struct {
102         msgs []MsgWithContext
103 }
104
105 // MsgWithContext encapsulates reply message with possibly sequence number and is-multipart flag.
106 type MsgWithContext struct {
107         Msg       api.Message
108         SeqNum    uint16
109         Multipart bool
110
111         /* set by mock adapter */
112         hasCtx bool
113 }
114
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)
118
119 const (
120         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
121 )
122
123 // NewVppAdapter returns a new mock adapter.
124 func NewVppAdapter() *VppAdapter {
125         a := &VppAdapter{
126                 msgIDSeq:     1000,
127                 msgIDsToName: make(map[uint16]string),
128                 msgNameToIds: make(map[string]uint16),
129                 binAPITypes:  make(map[string]map[string]reflect.Type),
130         }
131         a.registerBinAPITypes()
132         return a
133 }
134
135 // Connect emulates connecting the process to VPP.
136 func (a *VppAdapter) Connect() error {
137         return nil
138 }
139
140 // Disconnect emulates disconnecting the process from VPP.
141 func (a *VppAdapter) Disconnect() error {
142         return nil
143 }
144
145 // GetMsgNameByID returns message name for specified message ID.
146 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
147         switch msgID {
148         case 100:
149                 return "control_ping", true
150         case 101:
151                 return "control_ping_reply", true
152         case 200:
153                 return "sw_interface_dump", true
154         case 201:
155                 return "sw_interface_details", true
156         }
157
158         a.access.Lock()
159         defer a.access.Unlock()
160         msgName, found := a.msgIDsToName[msgID]
161
162         return msgName, found
163 }
164
165 func (a *VppAdapter) registerBinAPITypes() {
166         a.access.Lock()
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()
172                 }
173                 a.binAPITypes[pkg] = msgMap
174         }
175 }
176
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)
180         if foundName {
181                 if messages, found := a.binAPITypes[pkg]; found {
182                         if reply, found := messages[replyName]; found {
183                                 msgID, err := a.GetMsgID(replyName, "")
184                                 if err == nil {
185                                         return reply, msgID, found
186                                 }
187                         }
188                 }
189         }
190
191         return nil, 0, false
192 }
193
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)
197         if foundReplType {
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
202                 }
203         }
204
205         return nil, 0, false
206 }
207
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())
211         if err != nil {
212                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
213                         " ", err)
214                 return nil, err
215         }
216         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
217
218         data, err := codec.DefaultCodec.EncodeMsg(reply, replyMsgID)
219         if err != nil {
220                 return nil, err
221         }
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)
226         }
227         return data, nil
228 }
229
230 // GetMsgID returns mocked message ID for the given message name and CRC.
231 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
232         switch msgName {
233         case "control_ping":
234                 return 100, nil
235         case "control_ping_reply":
236                 return 101, nil
237         case "sw_interface_dump":
238                 return 200, nil
239         case "sw_interface_details":
240                 return 201, nil
241         }
242
243         a.access.Lock()
244         defer a.access.Unlock()
245
246         msgID, found := a.msgNameToIds[msgName]
247         if found {
248                 return msgID, nil
249         }
250
251         a.msgIDSeq++
252         msgID = a.msgIDSeq
253         a.msgNameToIds[msgName] = msgID
254         a.msgIDsToName[msgID] = msgName
255
256         return msgID, nil
257 }
258
259 // SendMsg emulates sending a binary-encoded message to VPP.
260 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
261         a.repliesLock.Lock()
262         mode := a.mode
263         a.repliesLock.Unlock()
264         switch mode {
265         case useReplyHandlers:
266                 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
267                         replyHandler := a.replyHandlers[i]
268
269                         msgID := binary.BigEndian.Uint16(data[0:2])
270
271                         a.access.Lock()
272                         reqMsgName := a.msgIDsToName[msgID]
273                         a.access.Unlock()
274
275                         reply, msgID, finished := replyHandler(MessageDTO{
276                                 MsgID:    msgID,
277                                 MsgName:  reqMsgName,
278                                 ClientID: clientID,
279                                 Data:     data,
280                         })
281                         if finished {
282                                 a.callback(msgID, reply)
283                                 return nil
284                         }
285                 }
286                 fallthrough
287
288         case useRepliesQueue:
289                 a.repliesLock.Lock()
290                 defer a.repliesLock.Unlock()
291
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())
297                                 context := clientID
298                                 if msg.hasCtx {
299                                         context = setMultipart(context, msg.Multipart)
300                                         context = setSeqNum(context, msg.SeqNum)
301                                 }
302                                 data, err := codec.DefaultCodec.EncodeMsg(msg.Msg, msgID)
303                                 if err != nil {
304                                         panic(err)
305                                 }
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)
310                                 }
311                                 a.callback(msgID, data)
312                         }
313
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
319                         }
320                         return nil
321                 }
322
323                 //fallthrough
324         default:
325                 // return default reply
326                 msgID := uint16(defaultReplyMsgID)
327                 data, err := codec.DefaultCodec.EncodeMsg(&defaultReply{}, msgID)
328                 if err != nil {
329                         panic(err)
330                 }
331                 binary.BigEndian.PutUint32(data[2:6], clientID)
332                 a.callback(msgID, data)
333         }
334         return nil
335 }
336
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) {
339         a.callback = cb
340 }
341
342 // WaitReady mocks waiting for VPP
343 func (a *VppAdapter) WaitReady() error {
344         return nil
345 }
346
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.
352 //
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.
357 // For example:
358 //
359 //    mockVpp.MockReply(  // push multipart messages all at once
360 //                      &interfaces.SwInterfaceDetails{SwIfIndex:1},
361 //                      &interfaces.SwInterfaceDetails{SwIfIndex:2},
362 //                      &interfaces.SwInterfaceDetails{SwIfIndex:3},
363 //    )
364 //    mockVpp.MockReply(&vpe.ControlPingReply{})
365 //
366 // Even if the multipart request has no replies, MockReply has to be called twice:
367 //
368 //    mockVpp.MockReply()  // zero multipart messages
369 //    mockVpp.MockReply(&vpe.ControlPingReply{})
370 func (a *VppAdapter) MockReply(msgs ...api.Message) {
371         a.repliesLock.Lock()
372         defer a.repliesLock.Unlock()
373
374         r := reply{}
375         for _, msg := range msgs {
376                 r.msgs = append(r.msgs, MsgWithContext{Msg: msg, hasCtx: false})
377         }
378         a.replies = append(a.replies, r)
379         a.mode = useRepliesQueue
380 }
381
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) {
388         a.repliesLock.Lock()
389         defer a.repliesLock.Unlock()
390
391         r := reply{}
392         for _, msg := range msgs {
393                 r.msgs = append(r.msgs,
394                         MsgWithContext{Msg: msg.Msg, SeqNum: msg.SeqNum, Multipart: msg.Multipart, hasCtx: true})
395         }
396         a.replies = append(a.replies, r)
397         a.mode = useRepliesQueue
398 }
399
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) {
403         a.repliesLock.Lock()
404         defer a.repliesLock.Unlock()
405
406         a.replyHandlers = append(a.replyHandlers, replyHandler)
407         a.mode = useReplyHandlers
408 }
409
410 // MockClearReplyHanders clears all reply handlers that were registered
411 // Will also set the mode to useReplyHandlers
412 func (a *VppAdapter) MockClearReplyHandlers() {
413         a.repliesLock.Lock()
414         defer a.repliesLock.Unlock()
415
416         a.replyHandlers = a.replyHandlers[:0]
417         a.mode = useReplyHandlers
418 }
419
420 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
421         context &= 0xffff0000
422         context |= uint32(seqNum)
423         return context
424 }
425
426 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
427         context &= 0xfffeffff
428         if isMultipart {
429                 context |= 1 << 16
430         }
431         return context
432 }