support for shm prefixes
[govpp.git] / adapter / vppapiclient / vppapiclient_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 // +build !windows,!darwin
16
17 // Package vppapiclient is the default VPP adapter being used for the connection with VPP via shared memory.
18 // It is based on the communication with the vppapiclient VPP library written in C via CGO.
19 package vppapiclient
20
21 /*
22 #cgo CFLAGS: -DPNG_DEBUG=1
23 #cgo LDFLAGS: -lvppapiclient
24
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <arpa/inet.h>
29 #include <vpp-api/client/vppapiclient.h>
30
31 extern void go_msg_callback(uint16_t, uint32_t, void*, size_t);
32
33 typedef struct __attribute__((__packed__)) _req_header {
34     uint16_t msg_id;
35     uint32_t client_index;
36     uint32_t context;
37 } req_header_t;
38
39 typedef struct __attribute__((__packed__)) _reply_header {
40     uint16_t msg_id;
41     uint32_t context;
42 } reply_header_t;
43
44 static void
45 govpp_msg_callback (unsigned char *data, int size)
46 {
47     reply_header_t *header = ((reply_header_t *)data);
48     go_msg_callback(ntohs(header->msg_id), ntohl(header->context), data, size);
49 }
50
51 static int
52 govpp_connect (char *shm)
53 {
54     return vac_connect("govpp", shm, govpp_msg_callback, 32);
55 }
56
57 static int
58 govvp_disconnect()
59 {
60     return vac_disconnect();
61 }
62
63 static int
64 govpp_send(uint32_t context, void *data, size_t size)
65 {
66         req_header_t *header = ((req_header_t *)data);
67         header->context = htonl(context);
68     return vac_write(data, size);
69 }
70
71 static uint32_t
72 govpp_get_msg_index(char *name_and_crc)
73 {
74     return vac_get_msg_index(name_and_crc);
75 }
76 */
77 import "C"
78
79 import (
80         "fmt"
81         "os"
82         "reflect"
83         "unsafe"
84
85         "git.fd.io/govpp.git/adapter"
86         "github.com/fsnotify/fsnotify"
87 )
88
89 const (
90         // watchedFolder is a folder where vpp's shared memory is supposed to be created.
91         // File system events are monitored in this folder.
92         watchedFolder = "/dev/shm/"
93         // watchedFile is a default name of the file in the watchedFolder. Once the file is present,
94         // the vpp is ready to accept a new connection.
95         watchedFile = "vpe-api"
96 )
97
98 // vppAPIClientAdapter is the opaque context of the adapter.
99 type vppAPIClientAdapter struct {
100         shmPrefix string
101         callback  func(context uint32, msgId uint16, data []byte)
102 }
103
104 var vppClient *vppAPIClientAdapter // global vpp API client adapter context
105
106 // NewVppAdapter returns a new vpp API client adapter.
107 func NewVppAdapter(shmPrefix string) adapter.VppAdapter {
108         return &vppAPIClientAdapter{
109                 shmPrefix: shmPrefix,
110         }
111 }
112
113 // Connect connects the process to VPP.
114 func (a *vppAPIClientAdapter) Connect() error {
115         vppClient = a
116         var rc _Ctype_int
117         if a.shmPrefix == "" {
118                 rc = C.govpp_connect(nil)
119         } else {
120                 shm := C.CString(a.shmPrefix)
121                 rc = C.govpp_connect(shm)
122         }
123         if rc != 0 {
124                 return fmt.Errorf("unable to connect to VPP (error=%d)", rc)
125         }
126         return nil
127 }
128
129 // Disconnect disconnects the process from VPP.
130 func (a *vppAPIClientAdapter) Disconnect() {
131         C.govvp_disconnect()
132 }
133
134 // GetMsgID returns a runtime message ID for the given message name and CRC.
135 func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
136         nameAndCrc := C.CString(msgName + "_" + msgCrc)
137         defer C.free(unsafe.Pointer(nameAndCrc))
138
139         msgID := uint16(C.govpp_get_msg_index(nameAndCrc))
140         if msgID == ^uint16(0) {
141                 return msgID, fmt.Errorf("unknown message: %v (crc: %v)", msgName, msgCrc)
142         }
143
144         return msgID, nil
145 }
146
147 // SendMsg sends a binary-encoded message to VPP.
148 func (a *vppAPIClientAdapter) SendMsg(clientID uint32, data []byte) error {
149         rc := C.govpp_send(C.uint32_t(clientID), unsafe.Pointer(&data[0]), C.size_t(len(data)))
150         if rc != 0 {
151                 return fmt.Errorf("unable to send the message (error=%d)", rc)
152         }
153         return nil
154 }
155
156 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP.
157 func (a *vppAPIClientAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
158         a.callback = cb
159 }
160
161 // WaitReady blocks until shared memory for sending
162 // binary api calls is present on the file system.
163 func (a *vppAPIClientAdapter) WaitReady() error {
164         watcher, err := fsnotify.NewWatcher()
165         if err != nil {
166                 return err
167         }
168         defer watcher.Close()
169
170         err = watcher.Add(watchedFolder)
171         if err != nil {
172                 return err
173         }
174         // Path to the shared memory segment with prefix, if set
175         var path string
176         if a.shmPrefix == "" {
177                 path = watchedFolder + watchedFile
178         } else {
179                 path = watchedFolder + a.shmPrefix + "-" + watchedFile
180         }
181         if fileExists(path) {
182                 return nil
183         }
184
185         for {
186                 ev := <-watcher.Events
187                 if ev.Name == path && (ev.Op&fsnotify.Create) == fsnotify.Create {
188                         break
189                 }
190         }
191         return nil
192 }
193
194 func fileExists(name string) bool {
195         if _, err := os.Stat(name); err != nil {
196                 if os.IsNotExist(err) {
197                         return false
198                 }
199         }
200         return true
201 }
202
203 //export go_msg_callback
204 func go_msg_callback(msgID C.uint16_t, context C.uint32_t, data unsafe.Pointer, size C.size_t) {
205         // convert unsafe.Pointer to byte slice
206         slice := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
207         byteArr := *(*[]byte)(unsafe.Pointer(slice))
208
209         vppClient.callback(uint32(context), uint16(msgID), byteArr)
210 }