blob: 103a078cd5ba8a2b41d9931d3ee9d197715d199b [file] [log] [blame]
Ole Troana7564e82018-06-12 21:06:44 +02001#
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
16import struct
17import collections
18from enum import IntEnum
19import 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#
26logger = logging.getLogger(__name__)
27
28
29class 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):
Ole Troana7564e82018-06-12 21:06:44 +020048 return self.packer.pack(data)
49
50 def unpack(self, data, offset, result=None):
Ole Troanc84cbad2018-09-06 22:58:05 +020051 return self.packer.unpack_from(data, offset)[0], self.packer.size
Ole Troana7564e82018-06-12 21:06:44 +020052
53
54types = {}
55types['u8'] = BaseTypes('u8')
56types['u16'] = BaseTypes('u16')
57types['u32'] = BaseTypes('u32')
58types['i32'] = BaseTypes('i32')
59types['u64'] = BaseTypes('u64')
60types['f64'] = BaseTypes('f64')
61
62
63class 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."""
Ole Troana7564e82018-06-12 21:06:44 +020073 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 {}'
Ole Troan94495f22018-08-02 11:58:12 +020083 .format(self.name, len(data[offset:]), self.num))
Ole Troana7564e82018-06-12 21:06:44 +020084 return self.packer.unpack(data, offset)
85
86
87class 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):
Ole Troana7564e82018-06-12 21:06:44 +020094 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 = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200105 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200106 for e in range(self.num):
Ole Troanc84cbad2018-09-06 22:58:05 +0200107 x, size = self.packer.unpack(data, offset)
Ole Troana7564e82018-06-12 21:06:44 +0200108 result.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200109 offset += size
110 total += size
111 return result, total
Ole Troana7564e82018-06-12 21:06:44 +0200112
113
114class VLAList():
115 def __init__(self, name, field_type, len_field_name, index):
Ole Troanc84cbad2018-09-06 22:58:05 +0200116 self.name = name
Ole Troana7564e82018-06-12 21:06:44 +0200117 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):
Ole Troana7564e82018-06-12 21:06:44 +0200123 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):
Ole Troana7564e82018-06-12 21:06:44 +0200137 # Return a list of arguments
Ole Troanc84cbad2018-09-06 22:58:05 +0200138 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200139
140 # u8 array
141 if self.packer.size == 1:
142 if result[self.index] == 0:
Ole Troanc84cbad2018-09-06 22:58:05 +0200143 return b'', 0
Ole Troana7564e82018-06-12 21:06:44 +0200144 p = BaseTypes('u8', result[self.index])
Ole Troanc84cbad2018-09-06 22:58:05 +0200145 return p.unpack(data, offset)
Ole Troana7564e82018-06-12 21:06:44 +0200146
147 r = []
148 for e in range(result[self.index]):
Ole Troanc84cbad2018-09-06 22:58:05 +0200149 x, size = self.packer.unpack(data, offset)
Ole Troana7564e82018-06-12 21:06:44 +0200150 r.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200151 offset += size
152 total += size
153 return r, total
Ole Troana7564e82018-06-12 21:06:44 +0200154
155
156class 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):
Ole Troana7564e82018-06-12 21:06:44 +0200162 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):
Ole Troanc84cbad2018-09-06 22:58:05 +0200171 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200172 # 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 = []
Ole Troana7564e82018-06-12 21:06:44 +0200177 for e in range(elements):
Ole Troanc84cbad2018-09-06 22:58:05 +0200178 x, size = self.packer.unpack(data, offset)
Ole Troana7564e82018-06-12 21:06:44 +0200179 r.append(x)
180 offset += self.packer.size
Ole Troanc84cbad2018-09-06 22:58:05 +0200181 total += size
182 return r, total
Ole Troana7564e82018-06-12 21:06:44 +0200183
184
185class 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):
Ole Troana7564e82018-06-12 21:06:44 +0200204 return types['u32'].pack(data, kwargs)
205
206 def unpack(self, data, offset=0, result=None):
Ole Troanc84cbad2018-09-06 22:58:05 +0200207 x, size = types['u32'].unpack(data, offset)
208 return self.enum(x), size
Ole Troana7564e82018-06-12 21:06:44 +0200209
210
211class 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):
Ole Troana7564e82018-06-12 21:06:44 +0200238 for k, v in data.items():
239 logger.debug("Key: {} Value: {}".format(k, v))
240 b = self.packers[k].pack(v, kwargs)
Ole Troana7564e82018-06-12 21:06:44 +0200241 break
242 r = bytearray(self.size)
Ole Troanb199e982018-08-02 19:19:21 +0200243 r[:len(b)] = b
Ole Troana7564e82018-06-12 21:06:44 +0200244 return r
245
246 def unpack(self, data, offset=0, result=None):
247 r = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200248 maxsize = 0
Ole Troana7564e82018-06-12 21:06:44 +0200249 for k, p in self.packers.items():
Ole Troanc84cbad2018-09-06 22:58:05 +0200250 x, size = p.unpack(data, offset)
251 if size > maxsize:
252 maxsize = size
253 r.append(x)
254 return self.tuple._make(r), maxsize
Ole Troana7564e82018-06-12 21:06:44 +0200255
256
257class 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
Ole Troana7564e82018-06-12 21:06:44 +0200308 b = bytes()
309 for i, a in enumerate(self.fields):
310 if a not in data:
Ole Troana7564e82018-06-12 21:06:44 +0200311 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 = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200323 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200324 for p in self.packers:
Ole Troanc84cbad2018-09-06 22:58:05 +0200325 x, size = p.unpack(data, offset, result)
Ole Troana7564e82018-06-12 21:06:44 +0200326 if type(x) is tuple and len(x) == 1:
327 x = x[0]
328 result.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200329 offset += size
330 total += size
331 t = self.tuple._make(result)
332 return t, total
Ole Troana7564e82018-06-12 21:06:44 +0200333
334
335class VPPMessage(VPPType):
336 pass