Add support for string types
[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/codec"
29         "github.com/lunixbochs/struc"
30 )
31
32 type replyMode int
33
34 const (
35         _                replyMode = iota
36         useRepliesQueue            // use replies in the queue
37         useReplyHandlers           // use reply handler
38 )
39
40 // VppAPI 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
43
44         msgIDSeq     uint16
45         access       sync.RWMutex
46         msgNameToIds map[string]uint16
47         msgIDsToName map[uint16]string
48         binAPITypes  map[string]reflect.Type
49
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
54 }
55
56 // defaultReply is a default reply message that mock adapter returns for a request.
57 type defaultReply struct {
58         Retval int32
59 }
60
61 // MessageDTO is a structure used for propagating information to ReplyHandlers.
62 type MessageDTO struct {
63         MsgID    uint16
64         MsgName  string
65         ClientID uint32
66         Data     []byte
67 }
68
69 // reply for one request (can be multipart, contain replies to previously timeouted requests, etc.)
70 type reply struct {
71         msgs []MsgWithContext
72 }
73
74 // MsgWithContext encapsulates reply message with possibly sequence number and is-multipart flag.
75 type MsgWithContext struct {
76         Msg       api.Message
77         SeqNum    uint16
78         Multipart bool
79
80         /* set by mock adapter */
81         hasCtx bool
82 }
83
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)
87
88 const (
89         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
90 )
91
92 // NewVppAdapter returns a new mock adapter.
93 func NewVppAdapter() *VppAdapter {
94         a := &VppAdapter{
95                 msgIDSeq:     1000,
96                 msgIDsToName: make(map[uint16]string),
97                 msgNameToIds: make(map[string]uint16),
98                 binAPITypes:  make(map[string]reflect.Type),
99         }
100         a.registerBinAPITypes()
101         return a
102 }
103
104 // Connect emulates connecting the process to VPP.
105 func (a *VppAdapter) Connect() error {
106         return nil
107 }
108
109 // Disconnect emulates disconnecting the process from VPP.
110 func (a *VppAdapter) Disconnect() error {
111         return nil
112 }
113
114 // GetMsgNameByID returns message name for specified message ID.
115 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
116         switch msgID {
117         case 100:
118                 return "control_ping", true
119         case 101:
120                 return "control_ping_reply", true
121         case 200:
122                 return "sw_interface_dump", true
123         case 201:
124                 return "sw_interface_details", true
125         }
126
127         a.access.Lock()
128         defer a.access.Unlock()
129         msgName, found := a.msgIDsToName[msgID]
130
131         return msgName, found
132 }
133
134 func (a *VppAdapter) registerBinAPITypes() {
135         a.access.Lock()
136         defer a.access.Unlock()
137         for _, msg := range api.GetAllMessages() {
138                 a.binAPITypes[msg.GetMessageName()] = reflect.TypeOf(msg).Elem()
139         }
140 }
141
142 // ReplyTypeFor returns reply message type for given request message name.
143 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
144         replyName, foundName := binapi.ReplyNameFor(requestMsgName)
145         if foundName {
146                 if reply, found := a.binAPITypes[replyName]; found {
147                         msgID, err := a.GetMsgID(replyName, "")
148                         if err == nil {
149                                 return reply, msgID, found
150                         }
151                 }
152         }
153
154         return nil, 0, false
155 }
156
157 // ReplyFor returns reply message for given request message name.
158 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
159         replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
160         if foundReplType {
161                 msgVal := reflect.New(replType)
162                 if msg, ok := msgVal.Interface().(api.Message); ok {
163                         log.Println("FFF ", replType, msgID, foundReplType)
164                         return msg, msgID, true
165                 }
166         }
167
168         return nil, 0, false
169 }
170
171 // ReplyBytes encodes the mocked reply into binary format.
172 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
173         replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
174         if err != nil {
175                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
176                         " ", err)
177                 return nil, err
178         }
179         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
180
181         buf := new(bytes.Buffer)
182         err = struc.Pack(buf, &codec.VppReplyHeader{
183                 VlMsgID: replyMsgID,
184                 Context: request.ClientID,
185         })
186         if err != nil {
187                 return nil, err
188         }
189         if err = struc.Pack(buf, reply); err != nil {
190                 return nil, err
191         }
192
193         return buf.Bytes(), nil
194 }
195
196 // GetMsgID returns mocked message ID for the given message name and CRC.
197 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
198         switch msgName {
199         case "control_ping":
200                 return 100, nil
201         case "control_ping_reply":
202                 return 101, nil
203         case "sw_interface_dump":
204                 return 200, nil
205         case "sw_interface_details":
206                 return 201, nil
207         }
208
209         a.access.Lock()
210         defer a.access.Unlock()
211
212         msgID, found := a.msgNameToIds[msgName]
213         if found {
214                 return msgID, nil
215         }
216
217         a.msgIDSeq++
218         msgID = a.msgIDSeq
219         a.msgNameToIds[msgName] = msgID
220         a.msgIDsToName[msgID] = msgName
221
222         return msgID, nil
223 }
224
225 // SendMsg emulates sending a binary-encoded message to VPP.
226 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
227         switch a.mode {
228         case useReplyHandlers:
229                 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
230                         replyHandler := a.replyHandlers[i]
231
232                         buf := bytes.NewReader(data)
233                         reqHeader := codec.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(msgID, reply)
248                                 return nil
249                         }
250                 }
251                 fallthrough
252
253         case useRepliesQueue:
254                 a.repliesLock.Lock()
255                 defer a.repliesLock.Unlock()
256
257                 // pop the first reply
258                 if len(a.replies) > 0 {
259                         reply := a.replies[0]
260                         for _, msg := range reply.msgs {
261                                 msgID, _ := a.GetMsgID(msg.Msg.GetMessageName(), msg.Msg.GetCrcString())
262                                 buf := new(bytes.Buffer)
263                                 context := clientID
264                                 if msg.hasCtx {
265                                         context = setMultipart(context, msg.Multipart)
266                                         context = setSeqNum(context, msg.SeqNum)
267                                 }
268                                 if msg.Msg.GetMessageType() == api.ReplyMessage {
269                                         struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: context})
270                                 } else if msg.Msg.GetMessageType() == api.RequestMessage {
271                                         struc.Pack(buf, &codec.VppRequestHeader{VlMsgID: msgID, Context: context})
272                                 } else if msg.Msg.GetMessageType() == api.EventMessage {
273                                         struc.Pack(buf, &codec.VppEventHeader{VlMsgID: msgID})
274                                 } else {
275                                         struc.Pack(buf, &codec.VppOtherHeader{VlMsgID: msgID})
276                                 }
277                                 struc.Pack(buf, msg.Msg)
278                                 a.callback(msgID, buf.Bytes())
279                         }
280
281                         a.replies = a.replies[1:]
282                         if len(a.replies) == 0 && len(a.replyHandlers) > 0 {
283                                 // Switch back to handlers once the queue is empty to revert back
284                                 // the fallthrough effect.
285                                 a.mode = useReplyHandlers
286                         }
287                         return nil
288                 }
289
290                 //fallthrough
291         default:
292                 // return default reply
293                 buf := new(bytes.Buffer)
294                 msgID := uint16(defaultReplyMsgID)
295                 struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: clientID})
296                 struc.Pack(buf, &defaultReply{})
297                 a.callback(msgID, buf.Bytes())
298         }
299         return nil
300 }
301
302 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
303 func (a *VppAdapter) SetMsgCallback(cb adapter.MsgCallback) {
304         a.callback = cb
305 }
306
307 // WaitReady mocks waiting for VPP
308 func (a *VppAdapter) WaitReady() error {
309         return nil
310 }
311
312 // MockReply stores a message or a list of multipart messages to be returned when
313 // the next request comes. It is a FIFO queue - multiple replies can be pushed into it,
314 // the first message or the first set of multi-part messages will be popped when
315 // some request comes.
316 // Using of this method automatically switches the mock into the useRepliesQueue mode.
317 //
318 // Note: multipart requests are implemented using two requests actually - the multipart
319 // request itself followed by control ping used to tell which multipart message
320 // is the last one. A mock reply to a multipart request has to thus consist of
321 // exactly two calls of this method.
322 // For example:
323 //
324 //    mockVpp.MockReply(  // push multipart messages all at once
325 //                      &interfaces.SwInterfaceDetails{SwIfIndex:1},
326 //                      &interfaces.SwInterfaceDetails{SwIfIndex:2},
327 //                      &interfaces.SwInterfaceDetails{SwIfIndex:3},
328 //    )
329 //    mockVpp.MockReply(&vpe.ControlPingReply{})
330 //
331 // Even if the multipart request has no replies, MockReply has to be called twice:
332 //
333 //    mockVpp.MockReply()  // zero multipart messages
334 //    mockVpp.MockReply(&vpe.ControlPingReply{})
335 func (a *VppAdapter) MockReply(msgs ...api.Message) {
336         a.repliesLock.Lock()
337         defer a.repliesLock.Unlock()
338
339         r := reply{}
340         for _, msg := range msgs {
341                 r.msgs = append(r.msgs, MsgWithContext{Msg: msg, hasCtx: false})
342         }
343         a.replies = append(a.replies, r)
344         a.mode = useRepliesQueue
345 }
346
347 // MockReplyWithContext queues next reply like MockReply() does, except that the
348 // sequence number and multipart flag (= context minus channel ID) can be customized
349 // and not necessarily match with the request.
350 // The purpose of this function is to test handling of sequence numbers and as such
351 // it is not really meant to be used outside the govpp UTs.
352 func (a *VppAdapter) MockReplyWithContext(msgs ...MsgWithContext) {
353         a.repliesLock.Lock()
354         defer a.repliesLock.Unlock()
355
356         r := reply{}
357         for _, msg := range msgs {
358                 r.msgs = append(r.msgs,
359                         MsgWithContext{Msg: msg.Msg, SeqNum: msg.SeqNum, Multipart: msg.Multipart, hasCtx: true})
360         }
361         a.replies = append(a.replies, r)
362         a.mode = useRepliesQueue
363 }
364
365 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
366 // Using of this method automatically switches the mock into th useReplyHandlers mode.
367 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
368         a.repliesLock.Lock()
369         defer a.repliesLock.Unlock()
370
371         a.replyHandlers = append(a.replyHandlers, replyHandler)
372         a.mode = useReplyHandlers
373 }
374
375 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
376         context &= 0xffff0000
377         context |= uint32(seqNum)
378         return context
379 }
380
381 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
382         context &= 0xfffeffff
383         if isMultipart {
384                 context |= 1 << 16
385         }
386         return context
387 }