ipsec: make 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, list, kwargs=None):
254         if not list:
255             return b""
256         if len(list) != kwargs[self.length_field]:
257             raise VPPSerializerValueError(
258                 'Variable length error, got: {} expected: {}'
259                 .format(len(list), kwargs[self.length_field]))
260         b = bytes()
261
262         # u8 array
263
264         if self.packer.size == 1:
265             return bytearray(list)
266
267         for e in list:
268             b += self.packer.pack(e)
269         return b
270
271     def unpack(self, data, offset=0, result=None, ntc=False):
272         # Return a list of arguments
273         total = 0
274
275         # u8 array
276         if self.packer.size == 1:
277             if result[self.index] == 0:
278                 return b'', 0
279             p = BaseTypes('u8', result[self.index])
280             return p.unpack(data, offset, ntc=ntc)
281
282         r = []
283         for e in range(result[self.index]):
284             x, size = self.packer.unpack(data, offset, ntc=ntc)
285             r.append(x)
286             offset += size
287             total += size
288         return r, total
289
290
291 class VLAList_legacy():
292     def __init__(self, name, field_type):
293         self.packer = types[field_type]
294         self.size = self.packer.size
295
296     def __call__(self, args):
297         self.options = args
298         return self
299
300     def pack(self, list, kwargs=None):
301         if self.packer.size == 1:
302             return bytes(list)
303
304         b = bytes()
305         for e in list:
306             b += self.packer.pack(e)
307         return b
308
309     def unpack(self, data, offset=0, result=None, ntc=False):
310         total = 0
311         # Return a list of arguments
312         if (len(data) - offset) % self.packer.size:
313             raise VPPSerializerValueError(
314                 'Legacy Variable Length Array length mismatch.')
315         elements = int((len(data) - offset) / self.packer.size)
316         r = []
317         for e in range(elements):
318             x, size = self.packer.unpack(data, offset, ntc=ntc)
319             r.append(x)
320             offset += self.packer.size
321             total += size
322         return r, total
323
324
325 class VPPEnumType(object):
326     def __init__(self, name, msgdef):
327         self.size = types['u32'].size
328         self.enumtype = 'u32'
329         e_hash = {}
330         for f in msgdef:
331             if type(f) is dict and 'enumtype' in f:
332                 if f['enumtype'] != 'u32':
333                     self.size = types[f['enumtype']].size
334                     self.enumtype = f['enumtype']
335                 continue
336             ename, evalue = f
337             e_hash[ename] = evalue
338         self.enum = IntFlag(name, e_hash)
339         types[name] = self
340
341     def __call__(self, args):
342         self.options = args
343         return self
344
345     def __getattr__(self, name):
346         return self.enum[name]
347
348     def __bool__(self):
349         return True
350
351     if sys.version[0] == '2':
352         __nonzero__ = __bool__
353
354     def pack(self, data, kwargs=None):
355         return types[self.enumtype].pack(data)
356
357     def unpack(self, data, offset=0, result=None, ntc=False):
358         x, size = types[self.enumtype].unpack(data, offset)
359         return self.enum(x), size
360
361
362 class VPPUnionType(object):
363     def __init__(self, name, msgdef):
364         self.name = name
365         self.size = 0
366         self.maxindex = 0
367         fields = []
368         self.packers = collections.OrderedDict()
369         for i, f in enumerate(msgdef):
370             if type(f) is dict and 'crc' in f:
371                 self.crc = f['crc']
372                 continue
373             f_type, f_name = f
374             if f_type not in types:
375                 logger.debug('Unknown union type {}'.format(f_type))
376                 raise VPPSerializerValueError(
377                     'Unknown message type {}'.format(f_type))
378             fields.append(f_name)
379             size = types[f_type].size
380             self.packers[f_name] = types[f_type]
381             if size > self.size:
382                 self.size = size
383                 self.maxindex = i
384
385         types[name] = self
386         self.tuple = collections.namedtuple(name, fields, rename=True)
387
388     def __call__(self, args):
389         self.options = args
390         return self
391
392     # Union of variable length?
393     def pack(self, data, kwargs=None):
394         if not data:
395             return b'\x00' * self.size
396
397         for k, v in data.items():
398             logger.debug("Key: {} Value: {}".format(k, v))
399             b = self.packers[k].pack(v, kwargs)
400             break
401         r = bytearray(self.size)
402         r[:len(b)] = b
403         return r
404
405     def unpack(self, data, offset=0, result=None, ntc=False):
406         r = []
407         maxsize = 0
408         for k, p in self.packers.items():
409             x, size = p.unpack(data, offset, ntc=ntc)
410             if size > maxsize:
411                 maxsize = size
412             r.append(x)
413         return self.tuple._make(r), maxsize
414
415
416 class VPPTypeAlias(object):
417     def __init__(self, name, msgdef):
418         self.name = name
419         t = vpp_get_type(msgdef['type'])
420         if not t:
421             raise ValueError('No such type: {}'.format(msgdef['type']))
422         if 'length' in msgdef:
423             if msgdef['length'] == 0:
424                 raise ValueError()
425             if msgdef['type'] == 'u8':
426                 self.packer = FixedList_u8(name, msgdef['type'],
427                                            msgdef['length'])
428                 self.size = self.packer.size
429             else:
430                 self.packer = FixedList(name, msgdef['type'], msgdef['length'])
431         else:
432             self.packer = t
433             self.size = t.size
434
435         types[name] = self
436         self.toplevelconversion = False
437
438     def __call__(self, args):
439         self.options = args
440         return self
441
442     def pack(self, data, kwargs=None):
443         if data and conversion_required(data, self.name):
444             try:
445                 return conversion_packer(data, self.name)
446             # Python 2 and 3 raises different exceptions from inet_pton
447             except(OSError, socket.error, TypeError):
448                 pass
449
450         return self.packer.pack(data, kwargs)
451
452     def unpack(self, data, offset=0, result=None, ntc=False):
453         if ntc == False and self.name in vpp_format.conversion_unpacker_table:
454             # Disable type conversion for dependent types
455             ntc = True
456             self.toplevelconversion = True
457         t, size = self.packer.unpack(data, offset, result, ntc=ntc)
458         if self.toplevelconversion:
459             self.toplevelconversion = False
460             return conversion_unpacker(t, self.name), size
461         return t, size
462
463
464 class VPPType(object):
465     # Set everything up to be able to pack / unpack
466     def __init__(self, name, msgdef):
467         self.name = name
468         self.msgdef = msgdef
469         self.packers = []
470         self.fields = []
471         self.fieldtypes = []
472         self.field_by_name = {}
473         size = 0
474         for i, f in enumerate(msgdef):
475             if type(f) is dict and 'crc' in f:
476                 self.crc = f['crc']
477                 continue
478             f_type, f_name = f[:2]
479             self.fields.append(f_name)
480             self.field_by_name[f_name] = None
481             self.fieldtypes.append(f_type)
482             if f_type not in types:
483                 logger.debug('Unknown type {}'.format(f_type))
484                 raise VPPSerializerValueError(
485                     'Unknown message type {}'.format(f_type))
486
487             fieldlen = len(f)
488             options = [x for x in f if type(x) is dict]
489             if len(options):
490                 self.options = options[0]
491                 fieldlen -= 1
492             else:
493                 self.options = {}
494             if fieldlen == 3:  # list
495                 list_elements = f[2]
496                 if list_elements == 0:
497                     if f_type == 'string':
498                         p = String(f_name, 0, self.options)
499                     else:
500                         p = VLAList_legacy(f_name, f_type)
501                     self.packers.append(p)
502                 elif f_type == 'u8':
503                     p = FixedList_u8(f_name, f_type, list_elements)
504                     self.packers.append(p)
505                     size += p.size
506                 elif f_type == 'string':
507                     p = String(f_name, list_elements, self.options)
508                     self.packers.append(p)
509                     size += p.size
510                 else:
511                     p = FixedList(f_name, f_type, list_elements)
512                     self.packers.append(p)
513                     size += p.size
514             elif fieldlen == 4:  # Variable length list
515                 length_index = self.fields.index(f[3])
516                 p = VLAList(f_name, f_type, f[3], length_index)
517                 self.packers.append(p)
518             else:
519                 p = types[f_type](self.options)
520                 self.packers.append(p)
521                 size += p.size
522
523         self.size = size
524         self.tuple = collections.namedtuple(name, self.fields, rename=True)
525         types[name] = self
526         self.toplevelconversion = False
527
528     def __call__(self, args):
529         self.options = args
530         return self
531
532     def pack(self, data, kwargs=None):
533         if not kwargs:
534             kwargs = data
535         b = bytes()
536
537         # Try one of the format functions
538         if data and conversion_required(data, self.name):
539             return conversion_packer(data, self.name)
540
541         for i, a in enumerate(self.fields):
542             if data and type(data) is not dict and a not in data:
543                 raise VPPSerializerValueError(
544                     "Invalid argument: {} expected {}.{}".
545                     format(data, self.name, a))
546
547             # Defaulting to zero.
548             if not data or a not in data:  # Default to 0
549                 arg = None
550                 kwarg = None  # No default for VLA
551             else:
552                 arg = data[a]
553                 kwarg = kwargs[a] if a in kwargs else None
554             if isinstance(self.packers[i], VPPType):
555                 b += self.packers[i].pack(arg, kwarg)
556             else:
557                 b += self.packers[i].pack(arg, kwargs)
558
559         return b
560
561     def unpack(self, data, offset=0, result=None, ntc=False):
562         # Return a list of arguments
563         result = []
564         total = 0
565         if ntc == False and self.name in vpp_format.conversion_unpacker_table:
566             # Disable type conversion for dependent types
567             ntc = True
568             self.toplevelconversion = True
569
570         for p in self.packers:
571             x, size = p.unpack(data, offset, result, ntc)
572             if type(x) is tuple and len(x) == 1:
573                 x = x[0]
574             result.append(x)
575             offset += size
576             total += size
577         t = self.tuple._make(result)
578
579         if self.toplevelconversion:
580             self.toplevelconversion = False
581             t = conversion_unpacker(t, self.name)
582         return t, total
583
584
585 class VPPMessage(VPPType):
586     pass