Change module name to go.fd.io/govpp
[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,!novpp
16
17 package vppapiclient
18
19 /*
20 #cgo CFLAGS: -DPNG_DEBUG=1
21 #cgo LDFLAGS: -lvppapiclient
22
23 #include "vppapiclient_wrapper.h"
24 */
25 import "C"
26
27 import (
28         "fmt"
29         "os"
30         "path/filepath"
31         "reflect"
32         "sync/atomic"
33         "time"
34         "unsafe"
35
36         "github.com/fsnotify/fsnotify"
37
38         "go.fd.io/govpp/adapter"
39 )
40
41 var (
42         // MaxWaitReady defines maximum duration before waiting for shared memory
43         // segment times out
44         MaxWaitReady = time.Second * 10
45 )
46
47 const (
48         // shmDir is a directory where shared memory is supposed to be created.
49         shmDir = "/dev/shm/"
50         // vppShmFile is a default name of the file in the shmDir.
51         vppShmFile = "vpe-api"
52 )
53
54 // global VPP binary API client, library vppapiclient only supports
55 // single connection at a time
56 var globalVppClient unsafe.Pointer
57
58 // stubVppClient is the default implementation of the VppAPI.
59 type vppClient struct {
60         shmPrefix      string
61         msgCallback    adapter.MsgCallback
62         inputQueueSize uint16
63 }
64
65 // NewVppClient returns a new VPP binary API client.
66 func NewVppClient(shmPrefix string) adapter.VppAPI {
67         return NewVppClientWithInputQueueSize(shmPrefix, 32)
68 }
69
70 // NewVppClientWithInputQueueSize returns a new VPP binary API client with a custom input queue size.
71 func NewVppClientWithInputQueueSize(shmPrefix string, inputQueueSize uint16) adapter.VppAPI {
72         return &vppClient{
73                 shmPrefix:      shmPrefix,
74                 inputQueueSize: inputQueueSize,
75         }
76 }
77
78 // Connect connects the process to VPP.
79 func (a *vppClient) Connect() error {
80         h := (*vppClient)(atomic.LoadPointer(&globalVppClient))
81         if h != nil {
82                 return fmt.Errorf("already connected to binary API, disconnect first")
83         }
84
85         rxQlen := C.int(a.inputQueueSize)
86         var rc C.int
87         if a.shmPrefix == "" {
88                 rc = C.govpp_connect(nil, rxQlen)
89         } else {
90                 shm := C.CString(a.shmPrefix)
91                 rc = C.govpp_connect(shm, rxQlen)
92         }
93         if rc != 0 {
94                 return fmt.Errorf("connecting to VPP binary API failed (rc=%v)", rc)
95         }
96
97         atomic.StorePointer(&globalVppClient, unsafe.Pointer(a))
98         return nil
99 }
100
101 // Disconnect disconnects the process from VPP.
102 func (a *vppClient) Disconnect() error {
103         atomic.StorePointer(&globalVppClient, nil)
104         rc := C.govpp_disconnect()
105         if rc != 0 {
106                 return fmt.Errorf("disconnecting from VPP binary API failed (rc=%v)", rc)
107         }
108         return nil
109 }
110
111 // GetMsgID returns a runtime message ID for the given message name and CRC.
112 func (a *vppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
113         nameAndCrc := C.CString(msgName + "_" + msgCrc)
114         defer C.free(unsafe.Pointer(nameAndCrc))
115
116         msgID := uint16(C.govpp_get_msg_index(nameAndCrc))
117         if msgID == ^uint16(0) {
118                 // VPP does not know this message
119                 return msgID, &adapter.UnknownMsgError{msgName, msgCrc}
120         }
121
122         return msgID, nil
123 }
124
125 // SendMsg sends a binary-encoded message to VPP.
126 func (a *vppClient) SendMsg(context uint32, data []byte) error {
127         rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data)))
128         if rc != 0 {
129                 return fmt.Errorf("unable to send the message (rc=%v)", rc)
130         }
131         return nil
132 }
133
134 // SetMsgCallback sets a callback function that will be called by the adapter
135 // whenever a message comes from VPP.
136 func (a *vppClient) SetMsgCallback(cb adapter.MsgCallback) {
137         a.msgCallback = cb
138 }
139
140 // WaitReady blocks until shared memory for sending
141 // binary api calls is present on the file system.
142 func (a *vppClient) WaitReady() error {
143         // join the path to the shared memory segment
144         var path string
145         if a.shmPrefix == "" {
146                 path = filepath.Join(shmDir, vppShmFile)
147         } else {
148                 path = filepath.Join(shmDir, a.shmPrefix+"-"+vppShmFile)
149         }
150
151         // check if file already exists
152         if _, err := os.Stat(path); err == nil {
153                 return nil // file exists, we are ready
154         } else if !os.IsNotExist(err) {
155                 return err // some other error occurred
156         }
157
158         // file does not exist, start watching folder
159         watcher, err := fsnotify.NewWatcher()
160         if err != nil {
161                 return err
162         }
163         defer watcher.Close()
164
165         // start watching directory
166         if err := watcher.Add(shmDir); err != nil {
167                 return err
168         }
169
170         timeout := time.NewTimer(MaxWaitReady)
171         for {
172                 select {
173                 case <-timeout.C:
174                         return fmt.Errorf("timeout waiting (%s) for shm file: %s", MaxWaitReady, path)
175
176                 case e := <-watcher.Errors:
177                         return e
178
179                 case ev := <-watcher.Events:
180                         if ev.Name == path && (ev.Op&fsnotify.Create) == fsnotify.Create {
181                                 // file created, we are ready
182                                 return nil
183                         }
184                 }
185         }
186 }
187
188 //export go_msg_callback
189 func go_msg_callback(msgID C.uint16_t, data unsafe.Pointer, size C.size_t) {
190         h := (*vppClient)(atomic.LoadPointer(&globalVppClient))
191         if h == nil {
192                 return
193         }
194         // convert unsafe.Pointer to byte slice
195         sliceHeader := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
196         byteSlice := *(*[]byte)(unsafe.Pointer(sliceHeader))
197         h.msgCallback(uint16(msgID), byteSlice)
198 }