• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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