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