PAPI: Add MACAddress object wrapper for vl_api_mac_address_t
[vpp.git] / src / vpp-api / python / vpp_papi / vpp_serializer.py
1 #
2 # Copyright (c) 2018 Cisco and/or its affiliates.
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
16 import struct
17 import collections
18 from enum import IntEnum
19 import logging
20 from . import vpp_format
21 import ipaddress
22 import sys
23 import socket
24
25 #
26 # Set log-level in application by doing e.g.:
27 # logger = logging.getLogger('vpp_serializer')
28 # logger.setLevel(logging.DEBUG)
29 #
30 logger = logging.getLogger(__name__)
31
32 if sys.version[0] == '2':
33     def check(d): type(d) is dict
34 else:
35     def check(d): type(d) is dict or type(d) is bytes
36
37
38 def conversion_required(data, field_type):
39     if check(data):
40         return False
41     try:
42         if type(data).__name__ in vpp_format.conversion_table[field_type]:
43             return True
44     except KeyError:
45         return False
46
47
48 def conversion_packer(data, field_type):
49     t = type(data).__name__
50     return types[field_type].pack(vpp_format.
51                                   conversion_table[field_type][t](data))
52
53
54 def conversion_unpacker(data, field_type):
55     if field_type not in vpp_format.conversion_unpacker_table:
56         return data
57     return vpp_format.conversion_unpacker_table[field_type](data)
58
59
60 class BaseTypes(object):
61     def __init__(self, type, elements=0):
62         base_types = {'u8': '>B',
63                       'string': '>s',
64                       'u16': '>H',
65                       'u32': '>I',
66                       'i32': '>i',
67                       'u64': '>Q',
68                       'f64': '>d',
69                       'bool': '>?',
70                       'header': '>HI'}
71
72         if elements > 0 and (type == 'u8' or type == 'string'):
73             self.packer = struct.Struct('>%ss' % elements)
74         else:
75             self.packer = struct.Struct(base_types[type])
76         self.size = self.packer.size
77
78     def pack(self, data, kwargs=None):
79         if not data:  # Default to zero if not specified
80             data = 0
81         return self.packer.pack(data)
82
83     def unpack(self, data, offset, result=None, ntc=False):
84         return self.packer.unpack_from(data, offset)[0], self.packer.size
85
86
87 class String(object):
88     def __init__(self):
89         self.name = 'string'
90         self.size = 1
91         self.length_field_packer = BaseTypes('u32')
92
93     def pack(self, list, kwargs=None):
94         if not list:
95             return self.length_field_packer.pack(0) + b""
96         return self.length_field_packer.pack(len(list)) + list.encode('utf8')
97
98     def unpack(self, data, offset=0, result=None, ntc=False):
99         length, length_field_size = self.length_field_packer.unpack(data,
100                                                                     offset)
101         if length == 0:
102             return b'', 0
103         p = BaseTypes('u8', length)
104         x, size = p.unpack(data, offset + length_field_size)
105         x2 = x.split(b'\0', 1)[0]
106         return (x2.decode('utf8'), size + length_field_size)
107
108
109 types = {'u8': BaseTypes('u8'), 'u16': BaseTypes('u16'),
110          'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'),
111          'u64': BaseTypes('u64'), 'f64': BaseTypes('f64'),
112          'bool': BaseTypes('bool'), 'string': String()}
113
114
115 def vpp_get_type(name):
116     try:
117         return types[name]
118     except KeyError:
119         return None
120
121
122 class VPPSerializerValueError(ValueError):
123     pass
124
125
126 class FixedList_u8(object):
127     def __init__(self, name, field_type, num):
128         self.name = name
129         self.num = num
130         self.packer = BaseTypes(field_type, num)
131         self.size = self.packer.size
132         self.field_type = field_type
133
134     def pack(self, data, kwargs=None):
135         """Packs a fixed length bytestring. Left-pads with zeros
136         if input data is too short."""
137         if not data:
138             return b'\x00' * self.size
139
140         if len(data) > self.num:
141             raise VPPSerializerValueError(
142                 'Fixed list length error for "{}", got: {}'
143                 ' expected: {}'
144                 .format(self.name, len(data), self.num))
145
146         return self.packer.pack(data)
147
148     def unpack(self, data, offset=0, result=None, ntc=False):
149         if len(data[offset:]) < self.num:
150             raise VPPSerializerValueError(
151                 'Invalid array length for "{}" got {}'
152                 ' expected {}'
153                 .format(self.name, len(data[offset:]), self.num))
154         if self.field_type == 'string':
155             s = self.packer.unpack(data, offset)
156             s2 = s[0].split(b'\0', 1)[0]
157             return (s2.decode('utf-8'), self.num)
158         return self.packer.unpack(data, offset)
159
160
161 class FixedList(object):
162     def __init__(self, name, field_type, num):
163         self.num = num
164         self.packer = types[field_type]
165         self.size = self.packer.size * num
166         self.name = name
167         self.field_type = field_type
168
169     def pack(self, list, kwargs):
170         if len(list) != self.num:
171             raise VPPSerializerValueError(
172                 'Fixed list length error, got: {} expected: {}'
173                 .format(len(list), self.num))
174         b = bytes()
175         for e in list:
176             b += self.packer.pack(e)
177         return b
178
179     def unpack(self, data, offset=0, result=None, ntc=False):
180         # Return a list of arguments
181         result = []
182         total = 0
183         for e in range(self.num):
184             x, size = self.packer.unpack(data, offset, ntc=ntc)
185             result.append(x)
186             offset += size
187             total += size
188         return result, total
189
190
191 class VLAList(object):
192     def __init__(self, name, field_type, len_field_name, index):
193         self.name = name
194         self.field_type = field_type
195         self.index = index
196         self.packer = types[field_type]
197         self.size = self.packer.size
198         self.length_field = len_field_name
199
200     def pack(self, list, kwargs=None):
201         if not list:
202             return b""
203         if len(list) != kwargs[self.length_field]:
204             raise VPPSerializerValueError(
205                 'Variable length error, got: {} expected: {}'
206                 .format(len(list), kwargs[self.length_field]))
207         b = bytes()
208
209         # u8 array
210
211         if self.packer.size == 1:
212             return bytearray(list)
213
214         for e in list:
215             b += self.packer.pack(e)
216         return b
217
218     def unpack(self, data, offset=0, result=None, ntc=False):
219         # Return a list of arguments
220         total = 0
221
222         # u8 array
223         if self.packer.size == 1:
224             if result[self.index] == 0:
225                 return b'', 0
226             p = BaseTypes('u8', result[self.index])
227             return p.unpack(data, offset, ntc=ntc)
228
229         r = []
230         for e in range(result[self.index]):
231             x, size = self.packer.unpack(data, offset, ntc=ntc)
232             r.append(x)
233             offset += size
234             total += size
235         return r, total
236
237
238 class VLAList_legacy():
239     def __init__(self, name, field_type):
240         self.packer = types[field_type]
241         self.size = self.packer.size
242
243     def pack(self, list, kwargs=None):
244         if self.packer.size == 1:
245             return bytes(list)
246
247         b = bytes()
248         for e in list:
249             b += self.packer.pack(e)
250         return b
251
252     def unpack(self, data, offset=0, result=None, ntc=False):
253         total = 0
254         # Return a list of arguments
255         if (len(data) - offset) % self.packer.size:
256             raise VPPSerializerValueError(
257                 'Legacy Variable Length Array length mismatch.')
258         elements = int((len(data) - offset) / self.packer.size)
259         r = []
260         for e in range(elements):
261             x, size = self.packer.unpack(data, offset, ntc=ntc)
262             r.append(x)
263             offset += self.packer.size
264             total += size
265         return r, total
266
267
268 class VPPEnumType(object):
269     def __init__(self, name, msgdef):
270         self.size = types['u32'].size
271         e_hash = {}
272         for f in msgdef:
273             if type(f) is dict and 'enumtype' in f:
274                 if f['enumtype'] != 'u32':
275                     raise NotImplementedError
276                 continue
277             ename, evalue = f
278             e_hash[ename] = evalue
279         self.enum = IntEnum(name, e_hash)
280         types[name] = self
281
282     def __getattr__(self, name):
283         return self.enum[name]
284
285     def __nonzero__(self):
286         return True
287
288     def pack(self, data, kwargs=None):
289         return types['u32'].pack(data)
290
291     def unpack(self, data, offset=0, result=None, ntc=False):
292         x, size = types['u32'].unpack(data, offset)
293         return self.enum(x), size
294
295
296 class VPPUnionType(object):
297     def __init__(self, name, msgdef):
298         self.name = name
299         self.size = 0
300         self.maxindex = 0
301         fields = []
302         self.packers = collections.OrderedDict()
303         for i, f in enumerate(msgdef):
304             if type(f) is dict and 'crc' in f:
305                 self.crc = f['crc']
306                 continue
307             f_type, f_name = f
308             if f_type not in types:
309                 logger.debug('Unknown union type {}'.format(f_type))
310                 raise VPPSerializerValueError(
311                     'Unknown message type {}'.format(f_type))
312             fields.append(f_name)
313             size = types[f_type].size
314             self.packers[f_name] = types[f_type]
315             if size > self.size:
316                 self.size = size
317                 self.maxindex = i
318
319         types[name] = self
320         self.tuple = collections.namedtuple(name, fields, rename=True)
321
322     # Union of variable length?
323     def pack(self, data, kwargs=None):
324         if not data:
325             return b'\x00' * self.size
326
327         for k, v in data.items():
328             logger.debug("Key: {} Value: {}".format(k, v))
329             b = self.packers[k].pack(v, kwargs)
330             break
331         r = bytearray(self.size)
332         r[:len(b)] = b
333         return r
334
335     def unpack(self, data, offset=0, result=None, ntc=False):
336         r = []
337         maxsize = 0
338         for k, p in self.packers.items():
339             x, size = p.unpack(data, offset, ntc=ntc)
340             if size > maxsize:
341                 maxsize = size
342             r.append(x)
343         return self.tuple._make(r), maxsize
344
345
346 class VPPTypeAlias(object):
347     def __init__(self, name, msgdef):
348         self.name = name
349         t = vpp_get_type(msgdef['type'])
350         if not t:
351             raise ValueError()
352         if 'length' in msgdef:
353             if msgdef['length'] == 0:
354                 raise ValueError()
355             if msgdef['type'] == 'u8':
356                 self.packer = FixedList_u8(name, msgdef['type'],
357                                            msgdef['length'])
358                 self.size = self.packer.size
359             else:
360                 self.packer = FixedList(name, msgdef['type'], msgdef['length'])
361         else:
362             self.packer = t
363             self.size = t.size
364
365         types[name] = self
366
367     def pack(self, data, kwargs=None):
368         if data and conversion_required(data, self.name):
369             try:
370                 return conversion_packer(data, self.name)
371             # Python 2 and 3 raises different exceptions from inet_pton
372             except(OSError, socket.error, TypeError):
373                 pass
374
375         return self.packer.pack(data, kwargs)
376
377     def unpack(self, data, offset=0, result=None, ntc=False):
378         t, size = self.packer.unpack(data, offset, result, ntc=ntc)
379         if not ntc:
380             return conversion_unpacker(t, self.name), size
381         return t, size
382
383
384 class VPPType(object):
385     # Set everything up to be able to pack / unpack
386     def __init__(self, name, msgdef):
387         self.name = name
388         self.msgdef = msgdef
389         self.packers = []
390         self.fields = []
391         self.fieldtypes = []
392         self.field_by_name = {}
393         size = 0
394         for i, f in enumerate(msgdef):
395             if type(f) is dict and 'crc' in f:
396                 self.crc = f['crc']
397                 continue
398             f_type, f_name = f[:2]
399             self.fields.append(f_name)
400             self.field_by_name[f_name] = None
401             self.fieldtypes.append(f_type)
402             if f_type not in types:
403                 logger.debug('Unknown type {}'.format(f_type))
404                 raise VPPSerializerValueError(
405                     'Unknown message type {}'.format(f_type))
406             if len(f) == 3:  # list
407                 list_elements = f[2]
408                 if list_elements == 0:
409                     p = VLAList_legacy(f_name, f_type)
410                     self.packers.append(p)
411                 elif f_type == 'u8' or f_type == 'string':
412                     p = FixedList_u8(f_name, f_type, list_elements)
413                     self.packers.append(p)
414                     size += p.size
415                 else:
416                     p = FixedList(f_name, f_type, list_elements)
417                     self.packers.append(p)
418                     size += p.size
419             elif len(f) == 4:  # Variable length list
420                 length_index = self.fields.index(f[3])
421                 p = VLAList(f_name, f_type, f[3], length_index)
422                 self.packers.append(p)
423             else:
424                 self.packers.append(types[f_type])
425                 size += types[f_type].size
426
427         self.size = size
428         self.tuple = collections.namedtuple(name, self.fields, rename=True)
429         types[name] = self
430
431     def pack(self, data, kwargs=None):
432         if not kwargs:
433             kwargs = data
434         b = bytes()
435
436         # Try one of the format functions
437         if data and conversion_required(data, self.name):
438             return conversion_packer(data, self.name)
439
440         for i, a in enumerate(self.fields):
441             if data and type(data) is not dict and a not in data:
442                 raise VPPSerializerValueError(
443                     "Invalid argument: {} expected {}.{}".
444                     format(data, self.name, a))
445
446             # Defaulting to zero.
447             if not data or a not in data:  # Default to 0
448                 arg = None
449                 kwarg = None  # No default for VLA
450             else:
451                 arg = data[a]
452                 kwarg = kwargs[a] if a in kwargs else None
453             if isinstance(self.packers[i], VPPType):
454                 b += self.packers[i].pack(arg, kwarg)
455             else:
456                 b += self.packers[i].pack(arg, kwargs)
457
458         return b
459
460     def unpack(self, data, offset=0, result=None, ntc=False):
461         # Return a list of arguments
462         result = []
463         total = 0
464         for p in self.packers:
465             x, size = p.unpack(data, offset, result, ntc)
466             if type(x) is tuple and len(x) == 1:
467                 x = x[0]
468             result.append(x)
469             offset += size
470             total += size
471         t = self.tuple._make(result)
472         if not ntc:
473             t = conversion_unpacker(t, self.name)
474         return t, total
475
476
477 class VPPMessage(VPPType):
478     pass