bier: tests support python3
[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, name, num, options):
109         self.name = name
110         self.num = num
111         self.size = 1
112         self.length_field_packer = BaseTypes('u32')
113         self.limit = options['limit'] if 'limit' in options else num
114         self.fixed = True if num else False
115         if self.fixed and not self.limit:
116             raise VPPSerializerValueError(
117                 "Invalid argument length for: {}, {} maximum {}".
118                 format(list, len(list), self.limit))
119
120     def pack(self, list, kwargs=None):
121         if not list:
122             if self.fixed:
123                 return b"\x00" * self.limit
124             return self.length_field_packer.pack(0) + b""
125         if self.limit and len(list) > self.limit - 1:
126             raise VPPSerializerValueError(
127                 "Invalid argument length for: {}, {} maximum {}".
128                 format(list, len(list), self.limit - 1))
129         if self.fixed:
130             return list.encode('ascii').ljust(self.limit, b'\x00')
131         return self.length_field_packer.pack(len(list)) + list.encode('ascii')
132
133     def unpack(self, data, offset=0, result=None, ntc=False):
134         if self.fixed:
135             p = BaseTypes('u8', self.num)
136             s = p.unpack(data, offset)
137             s2 = s[0].split(b'\0', 1)[0]
138             return (s2.decode('ascii'), self.num)
139
140         length, length_field_size = self.length_field_packer.unpack(data,
141                                                                     offset)
142         if length == 0:
143             return '', 0
144         p = BaseTypes('u8', length)
145         x, size = p.unpack(data, offset + length_field_size)
146         #x2 = x.split(b'\0', 1)[0]
147         return (x.decode('ascii', errors='replace'), size + length_field_size)
148
149
150 types = {'u8': BaseTypes('u8'), 'u16': BaseTypes('u16'),
151          'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'),
152          'u64': BaseTypes('u64'), 'f64': BaseTypes('f64'),
153          'bool': BaseTypes('bool'), 'string': String}
154
155
156 def vpp_get_type(name):
157     try:
158         return types[name]
159     except KeyError:
160         return None
161
162
163 class VPPSerializerValueError(ValueError):
164     pass
165
166
167 class FixedList_u8(object):
168     def __init__(self, name, field_type, num):
169         self.name = name
170         self.num = num
171         self.packer = BaseTypes(field_type, num)
172         self.size = self.packer.size
173         self.field_type = field_type
174
175     def __call__(self, args):
176         self.options = args
177         return self
178
179     def pack(self, data, kwargs=None):
180         """Packs a fixed length bytestring. Left-pads with zeros
181         if input data is too short."""
182         if not data:
183             return b'\x00' * self.size
184
185         if len(data) > self.num:
186             raise VPPSerializerValueError(
187                 'Fixed list length error for "{}", got: {}'
188                 ' expected: {}'
189                 .format(self.name, len(data), self.num))
190
191         try:
192             return self.packer.pack(data)
193         except struct.error:
194             raise VPPSerializerValueError(
195                 'Packing failed for "{}" {}'
196                 .format(self.name, kwargs))
197     def unpack(self, data, offset=0, result=None, ntc=False):
198         if len(data[offset:]) < self.num:
199             raise VPPSerializerValueError(
200                 'Invalid array length for "{}" got {}'
201                 ' expected {}'
202                 .format(self.name, len(data[offset:]), self.num))
203         return self.packer.unpack(data, offset)
204
205
206 class FixedList(object):
207     def __init__(self, name, field_type, num):
208         self.num = num
209         self.packer = types[field_type]
210         self.size = self.packer.size * num
211         self.name = name
212         self.field_type = field_type
213
214     def __call__(self, args):
215         self.options = args
216         return self
217
218     def pack(self, list, kwargs):
219         if len(list) != self.num:
220             raise VPPSerializerValueError(
221                 'Fixed list length error, got: {} expected: {}'
222                 .format(len(list), self.num))
223         b = bytes()
224         for e in list:
225             b += self.packer.pack(e)
226         return b
227
228     def unpack(self, data, offset=0, result=None, ntc=False):
229         # Return a list of arguments
230         result = []
231         total = 0
232         for e in range(self.num):
233             x, size = self.packer.unpack(data, offset, ntc=ntc)
234             result.append(x)
235             offset += size
236             total += size
237         return result, total
238
239
240 class VLAList(object):
241     def __init__(self, name, field_type, len_field_name, index):
242         self.name = name
243         self.field_type = field_type
244         self.index = index
245         self.packer = types[field_type]
246         self.size = self.packer.size
247         self.length_field = len_field_name
248
249     def __call__(self, args):
250         self.options = args
251         return self
252
253     def pack(self, lst, kwargs=None):
254         if not lst:
255             return b""
256         if len(lst) != kwargs[self.length_field]:
257             raise VPPSerializerValueError(
258                 'Variable length error, got: {} expected: {}'
259                 .format(len(lst), kwargs[self.length_field]))
260
261         # u8 array
262         if self.packer.size == 1:
263             if isinstance(lst, list):
264                 return b''.join(lst)
265             return bytes(lst)
266
267         b = bytes()
268         for e in lst:
269             b += self.packer.pack(e)
270         return b
271
272     def unpack(self, data, offset=0, result=None, ntc=False):
273         # Return a list of arguments
274         total = 0
275
276         # u8 array
277         if self.packer.size == 1:
278             if result[self.index] == 0:
279                 return b'', 0
280             p = BaseTypes('u8', result[self.index])
281             return p.unpack(data, offset, ntc=ntc)
282
283         r = []
284         for e in range(result[self.index]):
285             x, size = self.packer.unpack(data, offset, ntc=ntc)
286             r.append(x)
287             offset += size
288             total += size
289         return r, total
290
291
292 class VLAList_legacy():
293     def __init__(self, name, field_type):
294         self.packer = types[field_type]
295         self.size = self.packer.size
296
297     def __call__(self, args):
298         self.options = args
299         return self
300
301     def pack(self, list, kwargs=None):
302         if self.packer.size == 1:
303             return bytes(list)
304
305         b = bytes()
306         for e in list:
307             b += self.packer.pack(e)
308         return b
309
310     def unpack(self, data, offset=0, result=None, ntc=False):
311         total = 0
312         # Return a list of arguments
313         if (len(data) - offset) % self.packer.size:
314             raise VPPSerializerValueError(
315                 'Legacy Variable Length Array length mismatch.')
316         elements = int((len(data) - offset) / self.packer.size)
317         r = []
318         for e in range(elements):
319             x, size = self.packer.unpack(data, offset, ntc=ntc)
320             r.append(x)
321             offset += self.packer.size
322             total += size
323         return r, total
324
325
326 class VPPEnumType(object):
327     def __init__(self, name, msgdef):
328         self.size = types['u32'].size
329         self.enumtype = 'u32'
330         e_hash = {}
331         for f in msgdef:
332             if type(f) is dict and 'enumtype' in f:
333                 if f['enumtype'] != 'u32':
334                     self.size = types[f['enumtype']].size
335                     self.enumtype = f['enumtype']
336                 continue
337             ename, evalue = f
338             e_hash[ename] = evalue
339         self.enum = IntFlag(name, e_hash)
340         types[name] = self
341
342     def __call__(self, args):
343         self.options = args
344         return self
345
346     def __getattr__(self, name):
347         return self.enum[name]
348
349     def __bool__(self):
350         return True
351
352     if sys.version[0] == '2':
353         __nonzero__ = __bool__
354
355     def pack(self, data, kwargs=None):
356         return types[self.enumtype].pack(data)
357
358     def unpack(self, data, offset=0, result=None, ntc=False):
359         x, size = types[self.enumtype].unpack(data, offset)
360         return self.enum(x), size
361
362
363 class VPPUnionType(object):
364     def __init__(self, name, msgdef):
365         self.name = name
366         self.size = 0
367         self.maxindex = 0
368         fields = []
369         self.packers = collections.OrderedDict()
370         for i, f in enumerate(msgdef):
371             if type(f) is dict and 'crc' in f:
372                 self.crc = f['crc']
373                 continue
374             f_type, f_name = f
375             if f_type not in types:
376                 logger.debug('Unknown union type {}'.format(f_type))
377                 raise VPPSerializerValueError(
378                     'Unknown message type {}'.format(f_type))
379             fields.append(f_name)
380             size = types[f_type].size
381             self.packers[f_name] = types[f_type]
382             if size > self.size:
383                 self.size = size
384                 self.maxindex = i
385
386         types[name] = self
387         self.tuple = collections.namedtuple(name, fields, rename=True)
388
389     def __call__(self, args):
390         self.options = args
391         return self
392
393     # Union of variable length?
394     def pack(self, data, kwargs=None):
395         if not data:
396             return b'\x00' * self.size
397
398         for k, v in data.items():
399             logger.debug("Key: {} Value: {}".format(k, v))
400             b = self.packers[k].pack(v, kwargs)
401             break
402         r = bytearray(self.size)
403         r[:len(b)] = b
404         return r
405
406     def unpack(self, data, offset=0, result=None, ntc=False):
407         r = []
408         maxsize = 0
409         for k, p in self.packers.items():
410             x, size = p.unpack(data, offset, ntc=ntc)
411             if size > maxsize:
412                 maxsize = size
413             r.append(x)
414         return self.tuple._make(r), maxsize
415
416
417 class VPPTypeAlias(object):
418     def __init__(self, name, msgdef):
419         self.name = name
420         t = vpp_get_type(msgdef['type'])
421         if not t:
422             raise ValueError('No such type: {}'.format(msgdef['type']))
423         if 'length' in msgdef:
424             if msgdef['length'] == 0:
425                 raise ValueError()
426             if msgdef['type'] == 'u8':
427                 self.packer = FixedList_u8(name, msgdef['type'],
428                                            msgdef['length'])
429                 self.size = self.packer.size
430             else:
431                 self.packer = FixedList(name, msgdef['type'], msgdef['length'])
432         else:
433             self.packer = t
434             self.size = t.size
435
436         types[name] = self
437         self.toplevelconversion = False
438
439     def __call__(self, args):
440         self.options = args
441         return self
442
443     def pack(self, data, kwargs=None):
444         if data and conversion_required(data, self.name):
445             try:
446                 return conversion_packer(data, self.name)
447             # Python 2 and 3 raises different exceptions from inet_pton
448             except(OSError, socket.error, TypeError):
449                 pass
450
451         return self.packer.pack(data, kwargs)
452
453     def unpack(self, data, offset=0, result=None, ntc=False):
454         if ntc == False and self.name in vpp_format.conversion_unpacker_table:
455             # Disable type conversion for dependent types
456             ntc = True
457             self.toplevelconversion = True
458         t, size = self.packer.unpack(data, offset, result, ntc=ntc)
459         if self.toplevelconversion:
460             self.toplevelconversion = False
461             return conversion_unpacker(t, self.name), size
462         return t, size
463
464
465 class VPPType(object):
466     # Set everything up to be able to pack / unpack
467     def __init__(self, name, msgdef):
468         self.name = name
469         self.msgdef = msgdef
470         self.packers = []
471         self.fields = []
472         self.fieldtypes = []
473         self.field_by_name = {}
474         size = 0
475         for i, f in enumerate(msgdef):
476             if type(f) is dict and 'crc' in f:
477                 self.crc = f['crc']
478                 continue
479             f_type, f_name = f[:2]
480             self.fields.append(f_name)
481             self.field_by_name[f_name] = None
482             self.fieldtypes.append(f_type)
483             if f_type not in types:
484                 logger.debug('Unknown type {}'.format(f_type))
485                 raise VPPSerializerValueError(
486                     'Unknown message type {}'.format(f_type))
487
488             fieldlen = len(f)
489             options = [x for x in f if type(x) is dict]
490             if len(options):
491                 self.options = options[0]
492                 fieldlen -= 1
493             else:
494                 self.options = {}
495             if fieldlen == 3:  # list
496                 list_elements = f[2]
497                 if list_elements == 0:
498                     if f_type == 'string':
499                         p = String(f_name, 0, self.options)
500                     else:
501                         p = VLAList_legacy(f_name, f_type)
502                     self.packers.append(p)
503                 elif f_type == 'u8':
504                     p = FixedList_u8(f_name, f_type, list_elements)
505                     self.packers.append(p)
506                     size += p.size
507                 elif f_type == 'string':
508                     p = String(f_name, list_elements, self.options)
509                     self.packers.append(p)
510                     size += p.size
511                 else:
512                     p = FixedList(f_name, f_type, list_elements)
513                     self.packers.append(p)
514                     size += p.size
515             elif fieldlen == 4:  # Variable length list
516                 length_index = self.fields.index(f[3])
517                 p = VLAList(f_name, f_type, f[3], length_index)
518                 self.packers.append(p)
519             else:
520                 p = types[f_type](self.options)
521                 self.packers.append(p)
522                 size += p.size
523
524         self.size = size
525         self.tuple = collections.namedtuple(name, self.fields, rename=True)
526         types[name] = self
527         self.toplevelconversion = False
528
529     def __call__(self, args):
530         self.options = args
531         return self
532
533     def pack(self, data, kwargs=None):
534         if not kwargs:
535             kwargs = data
536         b = bytes()
537
538         # Try one of the format functions
539         if data and conversion_required(data, self.name):
540             return conversion_packer(data, self.name)
541
542         for i, a in enumerate(self.fields):
543             if data and type(data) is not dict and a not in data:
544                 raise VPPSerializerValueError(
545                     "Invalid argument: {} expected {}.{}".
546                     format(data, self.name, a))
547
548             # Defaulting to zero.
549             if not data or a not in data:  # Default to 0
550                 arg = None
551                 kwarg = None  # No default for VLA
552             else:
553                 arg = data[a]
554                 kwarg = kwargs[a] if a in kwargs else None
555             if isinstance(self.packers[i], VPPType):
556                 b += self.packers[i].pack(arg, kwarg)
557             else:
558                 b += self.packers[i].pack(arg, kwargs)
559
560         return b
561
562     def unpack(self, data, offset=0, result=None, ntc=False):
563         # Return a list of arguments
564         result = []
565         total = 0
566         if ntc == False and self.name in vpp_format.conversion_unpacker_table:
567             # Disable type conversion for dependent types
568             ntc = True
569             self.toplevelconversion = True
570
571         for p in self.packers:
572             x, size = p.unpack(data, offset, result, ntc)
573             if type(x) is tuple and len(x) == 1:
574                 x = x[0]
575             result.append(x)
576             offset += size
577             total += size
578         t = self.tuple._make(result)
579
580         if self.toplevelconversion:
581             self.toplevelconversion = False
582             t = conversion_unpacker(t, self.name)
583         return t, total
584
585
586 class VPPMessage(VPPType):
587     pass