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