1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Utility classes for serialization""" 6 7import struct 8 9 10# Format of a header for a struct or an array. 11HEADER_STRUCT = struct.Struct("=II") 12 13 14class SerializationException(Exception): 15 """Error when strying to serialize a struct.""" 16 pass 17 18 19class DeserializationException(Exception): 20 """Error when strying to deserialize a struct.""" 21 pass 22 23 24class Serialization(object): 25 """ 26 Helper class to serialize/deserialize a struct. 27 """ 28 def __init__(self, groups): 29 self.version = _GetVersion(groups) 30 self._groups = groups 31 main_struct = _GetStruct(groups) 32 self.size = HEADER_STRUCT.size + main_struct.size 33 self._struct_per_version = { 34 self.version: main_struct, 35 } 36 self._groups_per_version = { 37 self.version: groups, 38 } 39 40 def _GetMainStruct(self): 41 return self._GetStruct(self.version) 42 43 def _GetGroups(self, version): 44 # If asking for a version greater than the last known. 45 version = min(version, self.version) 46 if version not in self._groups_per_version: 47 self._groups_per_version[version] = _FilterGroups(self._groups, version) 48 return self._groups_per_version[version] 49 50 def _GetStruct(self, version): 51 # If asking for a version greater than the last known. 52 version = min(version, self.version) 53 if version not in self._struct_per_version: 54 self._struct_per_version[version] = _GetStruct(self._GetGroups(version)) 55 return self._struct_per_version[version] 56 57 def Serialize(self, obj, handle_offset): 58 """ 59 Serialize the given obj. handle_offset is the the first value to use when 60 encoding handles. 61 """ 62 handles = [] 63 data = bytearray(self.size) 64 HEADER_STRUCT.pack_into(data, 0, self.size, self.version) 65 position = HEADER_STRUCT.size 66 to_pack = [] 67 for group in self._groups: 68 position = position + NeededPaddingForAlignment(position, 69 group.GetByteSize()) 70 (entry, new_handles) = group.Serialize( 71 obj, 72 len(data) - position, 73 data, 74 handle_offset + len(handles)) 75 to_pack.append(entry) 76 handles.extend(new_handles) 77 position = position + group.GetByteSize() 78 self._GetMainStruct().pack_into(data, HEADER_STRUCT.size, *to_pack) 79 return (data, handles) 80 81 def Deserialize(self, fields, data, handles): 82 if not isinstance(data, buffer): 83 data = buffer(data) 84 (_, version) = HEADER_STRUCT.unpack_from(data) 85 version_struct = self._GetStruct(version) 86 entitities = version_struct.unpack_from(data, HEADER_STRUCT.size) 87 filtered_groups = self._GetGroups(version) 88 position = HEADER_STRUCT.size 89 for (group, value) in zip(filtered_groups, entitities): 90 position = position + NeededPaddingForAlignment(position, 91 group.GetByteSize()) 92 fields.update(group.Deserialize(value, buffer(data, position), handles)) 93 position += group.GetByteSize() 94 95 96def NeededPaddingForAlignment(value, alignment=8): 97 """Returns the padding necessary to align value with the given alignment.""" 98 if value % alignment: 99 return alignment - (value % alignment) 100 return 0 101 102 103def _GetVersion(groups): 104 return sum([len(x.descriptors) for x in groups]) 105 106 107def _FilterGroups(groups, version): 108 return [group for group in groups if group.GetVersion() < version] 109 110 111def _GetStruct(groups): 112 index = 0 113 codes = [ '=' ] 114 for group in groups: 115 code = group.GetTypeCode() 116 size = group.GetByteSize() 117 needed_padding = NeededPaddingForAlignment(index, size) 118 if needed_padding: 119 codes.append('x' * needed_padding) 120 index = index + needed_padding 121 codes.append(code) 122 index = index + size 123 alignment_needed = NeededPaddingForAlignment(index) 124 if alignment_needed: 125 codes.append('x' * alignment_needed) 126 return struct.Struct(''.join(codes)) 127