PAPI: Unpack embedded types with variable length arrays.
[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 from enum import IntEnum
19 import logging
20
21 #
22 # Set log-level in application by doing e.g.:
23 # logger = logging.getLogger('vpp_serializer')
24 # logger.setLevel(logging.DEBUG)
25 #
26 logger = logging.getLogger(__name__)
27
28
29 class BaseTypes():
30     def __init__(self, type, elements=0):
31         base_types = {'u8': '>B',
32                       'u16': '>H',
33                       'u32': '>I',
34                       'i32': '>i',
35                       'u64': '>Q',
36                       'f64': '>d',
37                       'header': '>HI'}
38
39         if elements > 0 and type == 'u8':
40             self.packer = struct.Struct('>%ss' % elements)
41         else:
42             self.packer = struct.Struct(base_types[type])
43         self.size = self.packer.size
44         logger.debug('Adding {} with format: {}'
45                      .format(type, base_types[type]))
46
47     def pack(self, data, kwargs=None):
48         return self.packer.pack(data)
49
50     def unpack(self, data, offset, result=None):
51         return self.packer.unpack_from(data, offset)[0], self.packer.size
52
53
54 types = {}
55 types['u8'] = BaseTypes('u8')
56 types['u16'] = BaseTypes('u16')
57 types['u32'] = BaseTypes('u32')
58 types['i32'] = BaseTypes('i32')
59 types['u64'] = BaseTypes('u64')
60 types['f64'] = BaseTypes('f64')
61
62
63 class FixedList_u8():
64     def __init__(self, name, field_type, num):
65         self.name = name
66         self.num = num
67         self.packer = BaseTypes(field_type, num)
68         self.size = self.packer.size
69
70     def pack(self, list, kwargs):
71         """Packs a fixed length bytestring. Left-pads with zeros
72         if input data is too short."""
73         if len(list) > self.num:
74             raise ValueError('Fixed list length error for "{}", got: {}'
75                              ' expected: {}'
76                              .format(self.name, len(list), self.num))
77         return self.packer.pack(list)
78
79     def unpack(self, data, offset=0, result=None):
80         if len(data[offset:]) < self.num:
81             raise ValueError('Invalid array length for "{}" got {}'
82                              ' expected {}'
83                              .format(self.name, len(data), self.num))
84         return self.packer.unpack(data, offset)
85
86
87 class FixedList():
88     def __init__(self, name, field_type, num):
89         self.num = num
90         self.packer = types[field_type]
91         self.size = self.packer.size * num
92
93     def pack(self, list, kwargs):
94         if len(list) != self.num:
95             raise ValueError('Fixed list length error, got: {} expected: {}'
96                              .format(len(list), self.num))
97         b = bytes()
98         for e in list:
99             b += self.packer.pack(e)
100         return b
101
102     def unpack(self, data, offset=0, result=None):
103         # Return a list of arguments
104         result = []
105         total = 0
106         for e in range(self.num):
107             x, size = self.packer.unpack(data, offset)
108             result.append(x)
109             offset += size
110             total += size
111         return result, total
112
113
114 class VLAList():
115     def __init__(self, name, field_type, len_field_name, index):
116         self.name = name
117         self.index = index
118         self.packer = types[field_type]
119         self.size = self.packer.size
120         self.length_field = len_field_name
121
122     def pack(self, list, kwargs=None):
123         if len(list) != kwargs[self.length_field]:
124             raise ValueError('Variable length error, got: {} expected: {}'
125                              .format(len(list), kwargs[self.length_field]))
126         b = bytes()
127
128         # u8 array
129         if self.packer.size == 1:
130             return bytearray(list)
131
132         for e in list:
133             b += self.packer.pack(e)
134         return b
135
136     def unpack(self, data, offset=0, result=None):
137         # Return a list of arguments
138         total = 0
139
140         # u8 array
141         if self.packer.size == 1:
142             if result[self.index] == 0:
143                 return b'', 0
144             p = BaseTypes('u8', result[self.index])
145             return p.unpack(data, offset)
146
147         r = []
148         for e in range(result[self.index]):
149             x, size = self.packer.unpack(data, offset)
150             r.append(x)
151             offset += size
152             total += size
153         return r, total
154
155
156 class VLAList_legacy():
157     def __init__(self, name, field_type):
158         self.packer = types[field_type]
159         self.size = self.packer.size
160
161     def pack(self, list, kwargs=None):
162         if self.packer.size == 1:
163             return bytes(list)
164
165         b = bytes()
166         for e in list:
167             b += self.packer.pack(e)
168         return b
169
170     def unpack(self, data, offset=0, result=None):
171         total = 0
172         # Return a list of arguments
173         if (len(data) - offset) % self.packer.size:
174             raise ValueError('Legacy Variable Length Array length mismatch.')
175         elements = int((len(data) - offset) / self.packer.size)
176         r = []
177         for e in range(elements):
178             x, size = self.packer.unpack(data, offset)
179             r.append(x)
180             offset += self.packer.size
181             total += size
182         return r, total
183
184
185 class VPPEnumType():
186     def __init__(self, name, msgdef):
187         self.size = types['u32'].size
188         e_hash = {}
189         for f in msgdef:
190             if type(f) is dict and 'enumtype' in f:
191                 if f['enumtype'] != 'u32':
192                     raise NotImplementedError
193                 continue
194             ename, evalue = f
195             e_hash[ename] = evalue
196         self.enum = IntEnum(name, e_hash)
197         types[name] = self
198         logger.debug('Adding enum {}'.format(name))
199
200     def __getattr__(self, name):
201         return self.enum[name]
202
203     def pack(self, data, kwargs=None):
204         return types['u32'].pack(data, kwargs)
205
206     def unpack(self, data, offset=0, result=None):
207         x, size = types['u32'].unpack(data, offset)
208         return self.enum(x), size
209
210
211 class VPPUnionType():
212     def __init__(self, name, msgdef):
213         self.name = name
214         self.size = 0
215         self.maxindex = 0
216         fields = []
217         self.packers = collections.OrderedDict()
218         for i, f in enumerate(msgdef):
219             if type(f) is dict and 'crc' in f:
220                 self.crc = f['crc']
221                 continue
222             f_type, f_name = f
223             if f_type not in types:
224                 logger.debug('Unknown union type {}'.format(f_type))
225                 raise ValueError('Unknown message type {}'.format(f_type))
226             fields.append(f_name)
227             size = types[f_type].size
228             self.packers[f_name] = types[f_type]
229             if size > self.size:
230                 self.size = size
231                 self.maxindex = i
232
233         types[name] = self
234         self.tuple = collections.namedtuple(name, fields, rename=True)
235         logger.debug('Adding union {}'.format(name))
236
237     def pack(self, data, kwargs=None):
238         for k, v in data.items():
239             logger.debug("Key: {} Value: {}".format(k, v))
240             b = self.packers[k].pack(v, kwargs)
241             break
242         r = bytearray(self.size)
243         r[:len(b)] = b
244         return r
245
246     def unpack(self, data, offset=0, result=None):
247         r = []
248         maxsize = 0
249         for k, p in self.packers.items():
250             x, size = p.unpack(data, offset)
251             if size > maxsize:
252                 maxsize = size
253             r.append(x)
254         return self.tuple._make(r), maxsize
255
256
257 class VPPType():
258     # Set everything up to be able to pack / unpack
259     def __init__(self, name, msgdef):
260         self.name = name
261         self.msgdef = msgdef
262         self.packers = []
263         self.fields = []
264         self.fieldtypes = []
265         self.field_by_name = {}
266         size = 0
267         for i, f in enumerate(msgdef):
268             if type(f) is dict and 'crc' in f:
269                 self.crc = f['crc']
270                 continue
271             f_type, f_name = f[:2]
272             self.fields.append(f_name)
273             self.field_by_name[f_name] = None
274             self.fieldtypes.append(f_type)
275             if f_type not in types:
276                 logger.debug('Unknown type {}'.format(f_type))
277                 raise ValueError('Unknown message type {}'.format(f_type))
278             if len(f) == 3:  # list
279                 list_elements = f[2]
280                 if list_elements == 0:
281                     p = VLAList_legacy(f_name, f_type)
282                     self.packers.append(p)
283                 elif f_type == 'u8':
284                     p = FixedList_u8(f_name, f_type, list_elements)
285                     self.packers.append(p)
286                     size += p.size
287                 else:
288                     p = FixedList(f_name, f_type, list_elements)
289                     self.packers.append(p)
290                     size += p.size
291             elif len(f) == 4:  # Variable length list
292                     # Find index of length field
293                     length_index = self.fields.index(f[3])
294                     p = VLAList(f_name, f_type, f[3], length_index)
295                     self.packers.append(p)
296             else:
297                 self.packers.append(types[f_type])
298                 size += types[f_type].size
299
300         self.size = size
301         self.tuple = collections.namedtuple(name, self.fields, rename=True)
302         types[name] = self
303         logger.debug('Adding type {}'.format(name))
304
305     def pack(self, data, kwargs=None):
306         if not kwargs:
307             kwargs = data
308         b = bytes()
309         for i, a in enumerate(self.fields):
310             if a not in data:
311                 b += b'\x00' * self.packers[i].size
312                 continue
313
314             if isinstance(self.packers[i], VPPType):
315                 b += self.packers[i].pack(data[a], kwargs[a])
316             else:
317                 b += self.packers[i].pack(data[a], kwargs)
318         return b
319
320     def unpack(self, data, offset=0, result=None):
321         # Return a list of arguments
322         result = []
323         total = 0
324         for p in self.packers:
325             x, size = p.unpack(data, offset, result)
326             if type(x) is tuple and len(x) == 1:
327                 x = x[0]
328             result.append(x)
329             offset += size
330             total += size
331         t = self.tuple._make(result)
332         return t, total
333
334
335 class VPPMessage(VPPType):
336     pass