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