blob: d62e3a4a57a56163a8918124312f90a10a3de335 [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
Ole Troan0bcad322018-12-11 13:04:01 +010020from . import vpp_format
21import ipaddress
22import sys
23import socket
Ole Troana7564e82018-06-12 21:06:44 +020024
25#
26# Set log-level in application by doing e.g.:
27# logger = logging.getLogger('vpp_serializer')
28# logger.setLevel(logging.DEBUG)
29#
30logger = logging.getLogger(__name__)
31
Ole Troan0bcad322018-12-11 13:04:01 +010032if sys.version[0] == '2':
Ole Troan8006c6a2018-12-17 12:02:26 +010033 def check(d): type(d) is dict
Ole Troan0bcad322018-12-11 13:04:01 +010034else:
Ole Troan8006c6a2018-12-17 12:02:26 +010035 def check(d): type(d) is dict or type(d) is bytes
36
Ole Troan0bcad322018-12-11 13:04:01 +010037
38def conversion_required(data, field_type):
39 if check(data):
40 return False
41 try:
42 if type(data).__name__ in vpp_format.conversion_table[field_type]:
43 return True
44 except KeyError:
45 return False
46
47
48def conversion_packer(data, field_type):
49 t = type(data).__name__
50 return types[field_type].pack(vpp_format.
51 conversion_table[field_type][t](data))
52
53
54def conversion_unpacker(data, field_type):
55 if field_type not in vpp_format.conversion_unpacker_table:
56 return data
57 return vpp_format.conversion_unpacker_table[field_type](data)
58
Ole Troana7564e82018-06-12 21:06:44 +020059
Paul Vinciguerra7e713f12018-11-26 12:04:48 -080060class BaseTypes(object):
Ole Troana7564e82018-06-12 21:06:44 +020061 def __init__(self, type, elements=0):
62 base_types = {'u8': '>B',
Ole Troan413f4a52018-11-28 11:36:05 +010063 'string': '>s',
Ole Troana7564e82018-06-12 21:06:44 +020064 'u16': '>H',
65 'u32': '>I',
66 'i32': '>i',
67 'u64': '>Q',
68 'f64': '>d',
Ole Troanf47e9b62018-10-16 15:14:03 +020069 'bool': '>?',
Ole Troana7564e82018-06-12 21:06:44 +020070 'header': '>HI'}
71
Ole Troan413f4a52018-11-28 11:36:05 +010072 if elements > 0 and (type == 'u8' or type == 'string'):
Ole Troana7564e82018-06-12 21:06:44 +020073 self.packer = struct.Struct('>%ss' % elements)
74 else:
75 self.packer = struct.Struct(base_types[type])
76 self.size = self.packer.size
Ole Troana7564e82018-06-12 21:06:44 +020077
78 def pack(self, data, kwargs=None):
Ole Troan31555a32018-10-22 09:30:26 +020079 if not data: # Default to zero if not specified
80 data = 0
Ole Troana7564e82018-06-12 21:06:44 +020081 return self.packer.pack(data)
82
Ole Troan0bcad322018-12-11 13:04:01 +010083 def unpack(self, data, offset, result=None, ntc=False):
Ole Troanc84cbad2018-09-06 22:58:05 +020084 return self.packer.unpack_from(data, offset)[0], self.packer.size
Ole Troana7564e82018-06-12 21:06:44 +020085
86
Ole Troan413f4a52018-11-28 11:36:05 +010087class String(object):
88 def __init__(self):
89 self.name = 'string'
90 self.size = 1
91 self.length_field_packer = BaseTypes('u32')
92
93 def pack(self, list, kwargs=None):
94 if not list:
95 return self.length_field_packer.pack(0) + b""
96 return self.length_field_packer.pack(len(list)) + list.encode('utf8')
97
98 def unpack(self, data, offset=0, result=None, ntc=False):
99 length, length_field_size = self.length_field_packer.unpack(data,
100 offset)
101 if length == 0:
102 return b'', 0
103 p = BaseTypes('u8', length)
104 x, size = p.unpack(data, offset + length_field_size)
Ole Troan8006c6a2018-12-17 12:02:26 +0100105 x2 = x.split(b'\0', 1)[0]
Ole Troan413f4a52018-11-28 11:36:05 +0100106 return (x2.decode('utf8'), size + length_field_size)
107
108
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800109types = {'u8': BaseTypes('u8'), 'u16': BaseTypes('u16'),
110 'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'),
111 'u64': BaseTypes('u64'), 'f64': BaseTypes('f64'),
Ole Troan413f4a52018-11-28 11:36:05 +0100112 'bool': BaseTypes('bool'), 'string': String()}
Ole Troana7564e82018-06-12 21:06:44 +0200113
114
Ole Troan0685da42018-10-16 14:42:50 +0200115def vpp_get_type(name):
116 try:
117 return types[name]
118 except KeyError:
119 return None
120
121
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800122class VPPSerializerValueError(ValueError):
123 pass
124
125
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800126class FixedList_u8(object):
Ole Troana7564e82018-06-12 21:06:44 +0200127 def __init__(self, name, field_type, num):
128 self.name = name
129 self.num = num
130 self.packer = BaseTypes(field_type, num)
131 self.size = self.packer.size
Ole Troan413f4a52018-11-28 11:36:05 +0100132 self.field_type = field_type
Ole Troana7564e82018-06-12 21:06:44 +0200133
Ole Troan0bcad322018-12-11 13:04:01 +0100134 def pack(self, data, kwargs=None):
Ole Troana7564e82018-06-12 21:06:44 +0200135 """Packs a fixed length bytestring. Left-pads with zeros
136 if input data is too short."""
Ole Troan0bcad322018-12-11 13:04:01 +0100137 if not data:
Ole Troan31555a32018-10-22 09:30:26 +0200138 return b'\x00' * self.size
Ole Troan0bcad322018-12-11 13:04:01 +0100139
140 if len(data) > self.num:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800141 raise VPPSerializerValueError(
142 'Fixed list length error for "{}", got: {}'
143 ' expected: {}'
Ole Troan0bcad322018-12-11 13:04:01 +0100144 .format(self.name, len(data), self.num))
Ole Troana7564e82018-06-12 21:06:44 +0200145
Ole Troan0bcad322018-12-11 13:04:01 +0100146 return self.packer.pack(data)
147
148 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troana7564e82018-06-12 21:06:44 +0200149 if len(data[offset:]) < self.num:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800150 raise VPPSerializerValueError(
151 'Invalid array length for "{}" got {}'
152 ' expected {}'
153 .format(self.name, len(data[offset:]), self.num))
Ole Troan413f4a52018-11-28 11:36:05 +0100154 if self.field_type == 'string':
155 s = self.packer.unpack(data, offset)
156 s2 = s[0].split(b'\0', 1)[0]
157 return (s2.decode('utf-8'), self.num)
Ole Troana7564e82018-06-12 21:06:44 +0200158 return self.packer.unpack(data, offset)
159
160
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800161class FixedList(object):
Ole Troana7564e82018-06-12 21:06:44 +0200162 def __init__(self, name, field_type, num):
163 self.num = num
164 self.packer = types[field_type]
165 self.size = self.packer.size * num
Ole Troan0bcad322018-12-11 13:04:01 +0100166 self.name = name
167 self.field_type = field_type
Ole Troana7564e82018-06-12 21:06:44 +0200168
169 def pack(self, list, kwargs):
Ole Troana7564e82018-06-12 21:06:44 +0200170 if len(list) != self.num:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800171 raise VPPSerializerValueError(
172 'Fixed list length error, got: {} expected: {}'
173 .format(len(list), self.num))
Ole Troana7564e82018-06-12 21:06:44 +0200174 b = bytes()
175 for e in list:
176 b += self.packer.pack(e)
177 return b
178
Ole Troan0bcad322018-12-11 13:04:01 +0100179 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troana7564e82018-06-12 21:06:44 +0200180 # Return a list of arguments
181 result = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200182 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200183 for e in range(self.num):
Ole Troan0bcad322018-12-11 13:04:01 +0100184 x, size = self.packer.unpack(data, offset, ntc=ntc)
Ole Troana7564e82018-06-12 21:06:44 +0200185 result.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200186 offset += size
187 total += size
188 return result, total
Ole Troana7564e82018-06-12 21:06:44 +0200189
190
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800191class VLAList(object):
Ole Troana7564e82018-06-12 21:06:44 +0200192 def __init__(self, name, field_type, len_field_name, index):
Ole Troanc84cbad2018-09-06 22:58:05 +0200193 self.name = name
Ole Troan413f4a52018-11-28 11:36:05 +0100194 self.field_type = field_type
Ole Troana7564e82018-06-12 21:06:44 +0200195 self.index = index
196 self.packer = types[field_type]
197 self.size = self.packer.size
198 self.length_field = len_field_name
199
200 def pack(self, list, kwargs=None):
Ole Troan31555a32018-10-22 09:30:26 +0200201 if not list:
202 return b""
Ole Troana7564e82018-06-12 21:06:44 +0200203 if len(list) != kwargs[self.length_field]:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800204 raise VPPSerializerValueError(
205 'Variable length error, got: {} expected: {}'
206 .format(len(list), kwargs[self.length_field]))
Ole Troana7564e82018-06-12 21:06:44 +0200207 b = bytes()
208
209 # u8 array
Ole Troan413f4a52018-11-28 11:36:05 +0100210
Ole Troana7564e82018-06-12 21:06:44 +0200211 if self.packer.size == 1:
212 return bytearray(list)
213
214 for e in list:
215 b += self.packer.pack(e)
216 return b
217
Ole Troan0bcad322018-12-11 13:04:01 +0100218 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troana7564e82018-06-12 21:06:44 +0200219 # Return a list of arguments
Ole Troanc84cbad2018-09-06 22:58:05 +0200220 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200221
222 # u8 array
223 if self.packer.size == 1:
224 if result[self.index] == 0:
Ole Troanc84cbad2018-09-06 22:58:05 +0200225 return b'', 0
Ole Troana7564e82018-06-12 21:06:44 +0200226 p = BaseTypes('u8', result[self.index])
Ole Troan0bcad322018-12-11 13:04:01 +0100227 return p.unpack(data, offset, ntc=ntc)
Ole Troana7564e82018-06-12 21:06:44 +0200228
229 r = []
230 for e in range(result[self.index]):
Ole Troan0bcad322018-12-11 13:04:01 +0100231 x, size = self.packer.unpack(data, offset, ntc=ntc)
Ole Troana7564e82018-06-12 21:06:44 +0200232 r.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200233 offset += size
234 total += size
235 return r, total
Ole Troana7564e82018-06-12 21:06:44 +0200236
237
238class VLAList_legacy():
239 def __init__(self, name, field_type):
240 self.packer = types[field_type]
241 self.size = self.packer.size
242
243 def pack(self, list, kwargs=None):
Ole Troana7564e82018-06-12 21:06:44 +0200244 if self.packer.size == 1:
245 return bytes(list)
246
247 b = bytes()
248 for e in list:
249 b += self.packer.pack(e)
250 return b
251
Ole Troan0bcad322018-12-11 13:04:01 +0100252 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troanc84cbad2018-09-06 22:58:05 +0200253 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200254 # Return a list of arguments
255 if (len(data) - offset) % self.packer.size:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800256 raise VPPSerializerValueError(
257 'Legacy Variable Length Array length mismatch.')
Ole Troana7564e82018-06-12 21:06:44 +0200258 elements = int((len(data) - offset) / self.packer.size)
259 r = []
Ole Troana7564e82018-06-12 21:06:44 +0200260 for e in range(elements):
Ole Troan0bcad322018-12-11 13:04:01 +0100261 x, size = self.packer.unpack(data, offset, ntc=ntc)
Ole Troana7564e82018-06-12 21:06:44 +0200262 r.append(x)
263 offset += self.packer.size
Ole Troanc84cbad2018-09-06 22:58:05 +0200264 total += size
265 return r, total
Ole Troana7564e82018-06-12 21:06:44 +0200266
267
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800268class VPPEnumType(object):
Ole Troana7564e82018-06-12 21:06:44 +0200269 def __init__(self, name, msgdef):
270 self.size = types['u32'].size
271 e_hash = {}
272 for f in msgdef:
273 if type(f) is dict and 'enumtype' in f:
274 if f['enumtype'] != 'u32':
275 raise NotImplementedError
276 continue
277 ename, evalue = f
278 e_hash[ename] = evalue
279 self.enum = IntEnum(name, e_hash)
280 types[name] = self
Ole Troana7564e82018-06-12 21:06:44 +0200281
282 def __getattr__(self, name):
283 return self.enum[name]
284
Ole Troan0685da42018-10-16 14:42:50 +0200285 def __nonzero__(self):
286 return True
287
Ole Troana7564e82018-06-12 21:06:44 +0200288 def pack(self, data, kwargs=None):
Ole Troan31555a32018-10-22 09:30:26 +0200289 return types['u32'].pack(data)
Ole Troana7564e82018-06-12 21:06:44 +0200290
Ole Troan0bcad322018-12-11 13:04:01 +0100291 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troanc84cbad2018-09-06 22:58:05 +0200292 x, size = types['u32'].unpack(data, offset)
293 return self.enum(x), size
Ole Troana7564e82018-06-12 21:06:44 +0200294
295
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800296class VPPUnionType(object):
Ole Troana7564e82018-06-12 21:06:44 +0200297 def __init__(self, name, msgdef):
298 self.name = name
299 self.size = 0
300 self.maxindex = 0
301 fields = []
302 self.packers = collections.OrderedDict()
303 for i, f in enumerate(msgdef):
304 if type(f) is dict and 'crc' in f:
305 self.crc = f['crc']
306 continue
307 f_type, f_name = f
308 if f_type not in types:
309 logger.debug('Unknown union type {}'.format(f_type))
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800310 raise VPPSerializerValueError(
311 'Unknown message type {}'.format(f_type))
Ole Troana7564e82018-06-12 21:06:44 +0200312 fields.append(f_name)
313 size = types[f_type].size
314 self.packers[f_name] = types[f_type]
315 if size > self.size:
316 self.size = size
317 self.maxindex = i
318
319 types[name] = self
320 self.tuple = collections.namedtuple(name, fields, rename=True)
Ole Troana7564e82018-06-12 21:06:44 +0200321
Ole Troan31555a32018-10-22 09:30:26 +0200322 # Union of variable length?
Ole Troana7564e82018-06-12 21:06:44 +0200323 def pack(self, data, kwargs=None):
Ole Troan31555a32018-10-22 09:30:26 +0200324 if not data:
325 return b'\x00' * self.size
326
Ole Troana7564e82018-06-12 21:06:44 +0200327 for k, v in data.items():
328 logger.debug("Key: {} Value: {}".format(k, v))
329 b = self.packers[k].pack(v, kwargs)
Ole Troana7564e82018-06-12 21:06:44 +0200330 break
331 r = bytearray(self.size)
Ole Troanb199e982018-08-02 19:19:21 +0200332 r[:len(b)] = b
Ole Troana7564e82018-06-12 21:06:44 +0200333 return r
334
Ole Troan0bcad322018-12-11 13:04:01 +0100335 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troana7564e82018-06-12 21:06:44 +0200336 r = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200337 maxsize = 0
Ole Troana7564e82018-06-12 21:06:44 +0200338 for k, p in self.packers.items():
Ole Troan0bcad322018-12-11 13:04:01 +0100339 x, size = p.unpack(data, offset, ntc=ntc)
Ole Troanc84cbad2018-09-06 22:58:05 +0200340 if size > maxsize:
341 maxsize = size
342 r.append(x)
343 return self.tuple._make(r), maxsize
Ole Troana7564e82018-06-12 21:06:44 +0200344
345
Ole Troan0bcad322018-12-11 13:04:01 +0100346class VPPTypeAlias(object):
347 def __init__(self, name, msgdef):
348 self.name = name
349 t = vpp_get_type(msgdef['type'])
350 if not t:
Ole Troan53fffa12018-11-13 12:36:56 +0100351 raise ValueError()
Ole Troan0bcad322018-12-11 13:04:01 +0100352 if 'length' in msgdef:
353 if msgdef['length'] == 0:
354 raise ValueError()
355 if msgdef['type'] == 'u8':
356 self.packer = FixedList_u8(name, msgdef['type'],
357 msgdef['length'])
358 self.size = self.packer.size
359 else:
360 self.packer = FixedList(name, msgdef['type'], msgdef['length'])
Ole Troan8c8acc02018-11-27 10:05:23 +0100361 else:
Ole Troan0bcad322018-12-11 13:04:01 +0100362 self.packer = t
363 self.size = t.size
364
365 types[name] = self
366
367 def pack(self, data, kwargs=None):
368 if data and conversion_required(data, self.name):
369 try:
370 return conversion_packer(data, self.name)
371 # Python 2 and 3 raises different exceptions from inet_pton
372 except(OSError, socket.error, TypeError):
373 pass
374
375 return self.packer.pack(data, kwargs)
376
377 def unpack(self, data, offset=0, result=None, ntc=False):
378 t, size = self.packer.unpack(data, offset, result, ntc=ntc)
379 if not ntc:
380 return conversion_unpacker(t, self.name), size
381 return t, size
Ole Troan53fffa12018-11-13 12:36:56 +0100382
383
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800384class VPPType(object):
Ole Troana7564e82018-06-12 21:06:44 +0200385 # Set everything up to be able to pack / unpack
386 def __init__(self, name, msgdef):
387 self.name = name
388 self.msgdef = msgdef
389 self.packers = []
390 self.fields = []
391 self.fieldtypes = []
392 self.field_by_name = {}
393 size = 0
394 for i, f in enumerate(msgdef):
395 if type(f) is dict and 'crc' in f:
396 self.crc = f['crc']
397 continue
398 f_type, f_name = f[:2]
399 self.fields.append(f_name)
400 self.field_by_name[f_name] = None
401 self.fieldtypes.append(f_type)
402 if f_type not in types:
403 logger.debug('Unknown type {}'.format(f_type))
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800404 raise VPPSerializerValueError(
405 'Unknown message type {}'.format(f_type))
Ole Troana7564e82018-06-12 21:06:44 +0200406 if len(f) == 3: # list
407 list_elements = f[2]
408 if list_elements == 0:
409 p = VLAList_legacy(f_name, f_type)
410 self.packers.append(p)
Ole Troan413f4a52018-11-28 11:36:05 +0100411 elif f_type == 'u8' or f_type == 'string':
Ole Troana7564e82018-06-12 21:06:44 +0200412 p = FixedList_u8(f_name, f_type, list_elements)
413 self.packers.append(p)
414 size += p.size
415 else:
416 p = FixedList(f_name, f_type, list_elements)
417 self.packers.append(p)
418 size += p.size
419 elif len(f) == 4: # Variable length list
Ole Troan413f4a52018-11-28 11:36:05 +0100420 length_index = self.fields.index(f[3])
421 p = VLAList(f_name, f_type, f[3], length_index)
422 self.packers.append(p)
Ole Troana7564e82018-06-12 21:06:44 +0200423 else:
424 self.packers.append(types[f_type])
425 size += types[f_type].size
426
427 self.size = size
428 self.tuple = collections.namedtuple(name, self.fields, rename=True)
429 types[name] = self
Ole Troana7564e82018-06-12 21:06:44 +0200430
431 def pack(self, data, kwargs=None):
432 if not kwargs:
433 kwargs = data
Ole Troana7564e82018-06-12 21:06:44 +0200434 b = bytes()
Ole Troan31555a32018-10-22 09:30:26 +0200435
Ole Troan0bcad322018-12-11 13:04:01 +0100436 # Try one of the format functions
437 if data and conversion_required(data, self.name):
438 return conversion_packer(data, self.name)
439
440 for i, a in enumerate(self.fields):
Ole Troan31555a32018-10-22 09:30:26 +0200441 if data and type(data) is not dict and a not in data:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800442 raise VPPSerializerValueError(
443 "Invalid argument: {} expected {}.{}".
444 format(data, self.name, a))
Ole Troan31555a32018-10-22 09:30:26 +0200445
446 # Defaulting to zero.
447 if not data or a not in data: # Default to 0
448 arg = None
449 kwarg = None # No default for VLA
450 else:
451 arg = data[a]
452 kwarg = kwargs[a] if a in kwargs else None
Ole Troana7564e82018-06-12 21:06:44 +0200453 if isinstance(self.packers[i], VPPType):
Ole Troan0bcad322018-12-11 13:04:01 +0100454 b += self.packers[i].pack(arg, kwarg)
Ole Troana7564e82018-06-12 21:06:44 +0200455 else:
Ole Troan31555a32018-10-22 09:30:26 +0200456 b += self.packers[i].pack(arg, kwargs)
457
Ole Troana7564e82018-06-12 21:06:44 +0200458 return b
459
Ole Troan0bcad322018-12-11 13:04:01 +0100460 def unpack(self, data, offset=0, result=None, ntc=False):
Ole Troana7564e82018-06-12 21:06:44 +0200461 # Return a list of arguments
462 result = []
Ole Troanc84cbad2018-09-06 22:58:05 +0200463 total = 0
Ole Troana7564e82018-06-12 21:06:44 +0200464 for p in self.packers:
Ole Troan0bcad322018-12-11 13:04:01 +0100465 x, size = p.unpack(data, offset, result, ntc)
Ole Troana7564e82018-06-12 21:06:44 +0200466 if type(x) is tuple and len(x) == 1:
467 x = x[0]
468 result.append(x)
Ole Troanc84cbad2018-09-06 22:58:05 +0200469 offset += size
470 total += size
471 t = self.tuple._make(result)
Ole Troan0bcad322018-12-11 13:04:01 +0100472 if not ntc:
473 t = conversion_unpacker(t, self.name)
Ole Troanc84cbad2018-09-06 22:58:05 +0200474 return t, total
Ole Troana7564e82018-06-12 21:06:44 +0200475
476
477class VPPMessage(VPPType):
478 pass