Exposed input_queue_size argument to user
[govpp.git] / adapter / vppapiclient / vppapiclient.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 // +build !windows,!darwin
16
17 package vppapiclient
18
19 /*
20 #cgo CFLAGS: -DPNG_DEBUG=1
21 #cgo LDFLAGS: -lvppapiclient
22
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <arpa/inet.h>
27 #include <vpp-api/client/vppapiclient.h>
28
29 extern void go_msg_callback(uint16_t msg_id, void* data, size_t size);
30
31 typedef struct __attribute__((__packed__)) _req_header {
32     uint16_t msg_id;
33     uint32_t client_index;
34     uint32_t context;
35 } req_header_t;
36
37 typedef struct __attribute__((__packed__)) _reply_header {
38     uint16_t msg_id;
39 } reply_header_t;
40
41 static void
42 govpp_msg_callback(unsigned char *data, int size)
43 {
44     reply_header_t *header = ((reply_header_t *)data);
45     go_msg_callback(ntohs(header->msg_id), data, size);
46 }
47
48 static int
49 govpp_send(uint32_t context, void *data, size_t size)
50 {
51         req_header_t *header = ((req_header_t *)data);
52         header->context = htonl(context);
53     return vac_write(data, size);
54 }
55
56 static int
57 govpp_connect(char *shm, int rx_qlen)
58 {
59     return vac_connect("govpp", shm, govpp_msg_callback, rx_qlen);
60 }
61
62 static int
63 govpp_disconnect()
64 {
65     return vac_disconnect();
66 }
67
68 static uint32_t
69 govpp_get_msg_index(char *name_and_crc)
70 {
71     return vac_get_msg_index(name_and_crc);
72 }
73 */
74 import "C"
75
76 import (
77         "fmt"
78         "os"
79         "path/filepath"
80         "reflect"
81         "unsafe"
82
83         "git.fd.io/govpp.git/adapter"
84         "github.com/fsnotify/fsnotify"
85 )
86
87 const (
88         // shmDir is a directory where shared memory is supposed to be created.
89         shmDir = "/dev/shm/"
90         // vppShmFile is a default name of the file in the shmDir.
91         vppShmFile = "vpe-api"
92 )
93
94 // global VPP binary API client, library vppapiclient only supports
95 // single connection at a time
96 var globalVppClient *vppClient
97
98 // stubVppClient is the default implementation of the VppAPI.
99 type vppClient struct {
100         shmPrefix      string
101         msgCallback    adapter.MsgCallback
102         inputQueueSize uint16
103 }
104
105 // NewVppClient returns a new VPP binary API client.
106 func NewVppClient(shmPrefix string) adapter.VppAPI {
107         return NewVppClientWithInputQueueSize(shmPrefix, 32)
108 }
109
110 // NewVppClientWithInputQueueSize returns a new VPP binary API client with a custom input queue size.
111 func NewVppClientWithInputQueueSize(shmPrefix string, inputQueueSize uint16) adapter.VppAPI {
112         return &vppClient{
113                 shmPrefix:      shmPrefix,
114                 inputQueueSize: inputQueueSize,
115         }
116 }
117
118 // Connect connects the process to VPP.
119 func (a *vppClient) Connect() error {
120         if globalVppClient != nil {
121                 return fmt.Errorf("already connected to binary API, disconnect first")
122         }
123
124         rxQlen := C.int(a.inputQueueSize)
125         var rc C.int
126         if a.shmPrefix == "" {
127                 rc = C.govpp_connect(nil, rxQlen)
128         } else {
129                 shm := C.CString(a.shmPrefix)
130                 rc = C.govpp_connect(shm, rxQlen)
131         }
132         if rc != 0 {
133                 return fmt.Errorf("connecting to VPP binary API failed (rc=%v)", rc)
134         }
135
136         globalVppClient = a
137         return nil
138 }
139
140 // Disconnect disconnects the process from VPP.
141 func (a *vppClient) Disconnect() error {
142         globalVppClient = nil
143
144         rc := C.govpp_disconnect()
145         if rc != 0 {
146                 return fmt.Errorf("disconnecting from VPP binary API failed (rc=%v)", rc)
147         }
148
149         return nil
150 }
151
152 // GetMsgID returns a runtime message ID for the given message name and CRC.
153 func (a *vppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
154         nameAndCrc := C.CString(msgName + "_" + msgCrc)
155         defer C.free(unsafe.Pointer(nameAndCrc))
156
157         msgID := uint16(C.govpp_get_msg_index(nameAndCrc))
158         if msgID == ^uint16(0) {
159                 // VPP does not know this message
160                 return msgID, fmt.Errorf("unknown message: %v (crc: %v)", msgName, msgCrc)
161         }
162
163         return msgID, nil
164 }
165
166 // SendMsg sends a binary-encoded message to VPP.
167 func (a *vppClient) SendMsg(context uint32, data []byte) error {
168         rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data)))
169         if rc != 0 {
170                 return fmt.Errorf("unable to send the message (rc=%v)", rc)
171         }
172         return nil
173 }
174
175 // SetMsgCallback sets a callback function that will be called by the adapter
176 // whenever a message comes from VPP.
177 func (a *vppClient) SetMsgCallback(cb adapter.MsgCallback) {
178         a.msgCallback = cb
179 }
180
181 // WaitReady blocks until shared memory for sending
182 // binary api calls is present on the file system.
183 func (a *vppClient) WaitReady() error {
184         var path string
185
186         // join the path to the shared memory segment
187         if a.shmPrefix == "" {
188                 path = filepath.Join(shmDir, vppShmFile)
189         } else {
190                 path = filepath.Join(shmDir, a.shmPrefix+"-"+vppShmFile)
191         }
192
193         // check if file at the path exists
194         if _, err := os.Stat(path); err == nil {
195                 // file exists, we are ready
196                 return nil
197         } else if !os.IsNotExist(err) {
198                 return err
199         }
200
201         // file does not exist, start watching folder
202         watcher, err := fsnotify.NewWatcher()
203         if err != nil {
204                 return err
205         }
206         defer watcher.Close()
207
208         if err := watcher.Add(shmDir); err != nil {
209                 return err
210         }
211
212         for {
213                 ev := <-watcher.Events
214                 if ev.Name == path {
215                         if (ev.Op & fsnotify.Create) == fsnotify.Create {
216                                 // file was created, we are ready
217                                 break
218                         }
219                 }
220         }
221
222         return nil
223 }
224
225 //export go_msg_callback
226 func go_msg_callback(msgID C.uint16_t, data unsafe.Pointer, size C.size_t) {
227         // convert unsafe.Pointer to byte slice
228         sliceHeader := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
229         byteSlice := *(*[]byte)(unsafe.Pointer(sliceHeader))
230
231         globalVppClient.msgCallback(uint16(msgID), byteSlice)
232 }