summaryrefslogtreecommitdiffstats
path: root/src/vpp-api/python/vpp_papi/vpp_transport_shmem.py
blob: 4719f23b40a034d54307dbe0fd2617c256aa6c78 (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
#
# A transport class. With two implementations.
# One for socket and one for shared memory.
#
import logging

from cffi import FFI
import cffi

logger = logging.getLogger('vpp_papi.transport')
logger.addHandler(logging.NullHandler())

ffi = FFI()
ffi.cdef("""
typedef void (*vac_callback_t)(unsigned char * data, int len);
typedef void (*vac_error_callback_t)(void *, unsigned char *, int);
int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb,
    int rx_qlen);
int vac_disconnect(void);
int vac_read(char **data, int *l, unsigned short timeout);
int vac_write(char *data, int len);
void vac_free(void * msg);

int vac_get_msg_index(unsigned char * name);
int vac_msg_table_size(void);
int vac_msg_table_max_index(void);

void vac_rx_suspend (void);
void vac_rx_resume (void);
void vac_set_error_handler(vac_error_callback_t);
void vac_mem_init (size_t size);
""")

vpp_object = None

# allow file to be imported so it can be mocked in tests.
# If the shared library fails, VppTransport cannot be initialized.
try:
    vpp_api = ffi.dlopen('libvppapiclient.so')
except OSError:
    vpp_api = None


@ffi.callback("void(unsigned char *, int)")
def vac_callback_sync(data, len):
    vpp_object.msg_handler_sync(ffi.buffer(data, len))


@ffi.callback("void(unsigned char *, int)")
def vac_callback_async(data, len):
    vpp_object.msg_handler_async(ffi.buffer(data, len))


@ffi.callback("void(void *, unsigned char *, int)")
def vac_error_handler(arg, msg, msg_len):
    vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len))


class VppTransportShmemIOError(IOError):
    """ exception communicating with vpp over shared memory """

    def __init__(self, rv, descr):
        self.rv = rv
        self.desc = descr

        super(VppTransportShmemIOError, self).__init__(rv, descr)


class VppTransport:
    VppTransportShmemIOError = VppTransportShmemIOError

    def __init__(self, parent, read_timeout, server_address):
        self.connected = False
        self.read_timeout = read_timeout
        self.parent = parent
        global vpp_object
        vpp_object = parent

        vpp_api.vac_mem_init(0)

        # Register error handler
        vpp_api.vac_set_error_handler(vac_error_handler)

        # Support legacy CFFI
        # from_buffer supported from 1.8.0
        (major, minor, patch) = [int(s) for s in
                                 cffi.__version__.split('.', 3)]
        if major >= 1 and minor >= 8:
            self.write = self._write_new_cffi
        else:
            self.write = self._write_legacy_cffi

    def connect(self, name, pfx, msg_handler, rx_qlen):
        self.connected = True
        if not pfx:
            pfx = ffi.NULL
        return vpp_api.vac_connect(name.encode('ascii'), pfx, msg_handler, rx_qlen)

    def disconnect(self):
        self.connected = False
        return vpp_api.vac_disconnect()

    def suspend(self):
        vpp_api.vac_rx_suspend()

    def resume(self):
        vpp_api.vac_rx_resume()

    def get_callback(self, do_async):
        return vac_callback_sync if not do_async else vac_callback_async

    def get_msg_index(self, name):
        return vpp_api.vac_get_msg_index(name.encode('ascii'))

    def msg_table_max_index(self):
        return vpp_api.vac_msg_table_max_index()

    def _write_new_cffi(self, buf):
        """Send a binary-packed message to VPP."""
        if not self.connected:
            raise VppTransportShmemIOError(1, 'Not connected')
        return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))

    def _write_legacy_cffi(self, buf):
        """Send a binary-packed message to VPP."""
        if not self.connected:
            raise VppTransportShmemIOError(1, 'Not connected')
        return vpp_api.vac_write(bytes(buf), len(buf))

    def read(self, timeout=None):
        if not self.connected:
            raise VppTransportShmemIOError(1, 'Not connected')
        if timeout is None:
            timeout = self.read_timeout
        mem = ffi.new("char **")
        size = ffi.new("int *")
        rv = vpp_api.vac_read(mem, size, timeout)
        if rv:
            strerror = 'vac_read failed.  It is likely that VPP died.'
            raise VppTransportShmemIOError(rv, strerror)
        msg = bytes(ffi.buffer(mem[0], size[0]))
        vpp_api.vac_free(mem[0])
        return msg