aboutsummaryrefslogtreecommitdiffstats
path: root/adapter/vppapiclient/vppapiclient_adapter.go
blob: 7aafa5527bc9bc4a3c3e2495787b2e4d95a3ba04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !windows,!darwin

// Package vppapiclient is the default VPP adapter being used for the connection with VPP via shared memory.
// It is based on the communication with the vppapiclient VPP library written in C via CGO.
package vppapiclient

/*
#cgo CFLAGS: -DPNG_DEBUG=1
#cgo LDFLAGS: -lvppapiclient

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <vpp-api/client/vppapiclient.h>

extern void go_msg_callback(uint16_t msg_id, uint32_t context, void* data, size_t size);

typedef struct __attribute__((__packed__)) _req_header {
    uint16_t msg_id;
    uint32_t client_index;
    uint32_t context;
} req_header_t;

typedef struct __attribute__((__packed__)) _reply_header {
    uint16_t msg_id;
    uint32_t context; // currently not all reply messages contain context field
} reply_header_t;

static void
govpp_msg_callback (unsigned char *data, int size)
{
    reply_header_t *header = ((reply_header_t *)data);
    go_msg_callback(ntohs(header->msg_id), ntohl(header->context), data, size);
}

static int
govpp_send(uint32_t context, void *data, size_t size)
{
	req_header_t *header = ((req_header_t *)data);
	header->context = htonl(context);
    return vac_write(data, size);
}

static int
govpp_connect (char *shm)
{
    return vac_connect("govpp", shm, govpp_msg_callback, 32);
}

static int
govvp_disconnect()
{
    return vac_disconnect();
}

static uint32_t
govpp_get_msg_index(char *name_and_crc)
{
    return vac_get_msg_index(name_and_crc);
}
*/
import "C"

import (
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"unsafe"

	"git.fd.io/govpp.git/adapter"
	"github.com/fsnotify/fsnotify"
)

const (
	// watchedFolder is a folder where vpp's shared memory is supposed to be created.
	// File system events are monitored in this folder.
	watchedFolder = "/dev/shm/"
	// watchedFile is a default name of the file in the watchedFolder. Once the file is present,
	// the vpp is ready to accept a new connection.
	watchedFile = "vpe-api"
)

// vppAPIClientAdapter is the opaque context of the adapter.
type vppAPIClientAdapter struct {
	shmPrefix string
	callback  adapter.MsgCallback
}

var vppClient *vppAPIClientAdapter // global vpp API client adapter context

// NewVppAdapter returns a new vpp API client adapter.
func NewVppAdapter(shmPrefix string) adapter.VppAdapter {
	return &vppAPIClientAdapter{
		shmPrefix: shmPrefix,
	}
}

// Connect connects the process to VPP.
func (a *vppAPIClientAdapter) Connect() error {
	vppClient = a
	var rc _Ctype_int
	if a.shmPrefix == "" {
		rc = C.govpp_connect(nil)
	} else {
		shm := C.CString(a.shmPrefix)
		rc = C.govpp_connect(shm)
	}
	if rc != 0 {
		return fmt.Errorf("unable to connect to VPP (error=%d)", rc)
	}
	return nil
}

// Disconnect disconnects the process from VPP.
func (a *vppAPIClientAdapter) Disconnect() {
	C.govvp_disconnect()
}

// GetMsgID returns a runtime message ID for the given message name and CRC.
func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
	nameAndCrc := C.CString(msgName + "_" + msgCrc)
	defer C.free(unsafe.Pointer(nameAndCrc))

	msgID := uint16(C.govpp_get_msg_index(nameAndCrc))
	if msgID == ^uint16(0) {
		// VPP does not know this message
		return msgID, fmt.Errorf("unknown message: %v (crc: %v)", msgName, msgCrc)
	}

	return msgID, nil
}

// SendMsg sends a binary-encoded message to VPP.
func (a *vppAPIClientAdapter) SendMsg(context uint32, data []byte) error {
	rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data)))
	if rc != 0 {
		return fmt.Errorf("unable to send the message (error=%d)", rc)
	}
	return nil
}

// SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP.
func (a *vppAPIClientAdapter) SetMsgCallback(cb adapter.MsgCallback) {
	a.callback = cb
}

// WaitReady blocks until shared memory for sending
// binary api calls is present on the file system.
func (a *vppAPIClientAdapter) WaitReady() error {
	// Path to the shared memory segment
	var path string
	if a.shmPrefix == "" {
		path = filepath.Join(watchedFolder, watchedFile)
	} else {
		path = filepath.Join(watchedFolder, a.shmPrefix+"-"+watchedFile)
	}

	// Watch folder if file does not exist yet
	if !fileExists(path) {
		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			return err
		}
		defer watcher.Close()

		if err := watcher.Add(watchedFolder); err != nil {
			return err
		}

		for {
			ev := <-watcher.Events
			if ev.Name == path && (ev.Op&fsnotify.Create) == fsnotify.Create {
				break
			}
		}
	}

	return nil
}

func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

//export go_msg_callback
func go_msg_callback(msgID C.uint16_t, context C.uint32_t, data unsafe.Pointer, size C.size_t) {
	// convert unsafe.Pointer to byte slice
	slice := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
	byteArr := *(*[]byte)(unsafe.Pointer(slice))

	vppClient.callback(uint16(msgID), uint32(context), byteArr)
}