blob: c359e446086b5a75d58f2bbec12df9f15dbcc820 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
The descriptors used to define generated elements of the mojo python bindings.
"""
import array
import itertools
import struct
import mojo_bindings.reflection as reflection
import mojo_bindings.serialization as serialization
# pylint: disable=E0611,F0401
import mojo_system
class Type(object):
"""Describes the type of a struct field or a method parameter,"""
def Convert(self, value): # pylint: disable=R0201
"""
Convert the given value into its canonical representation, raising an
exception if the value cannot be converted.
"""
return value
def GetDefaultValue(self, value):
"""
Returns the default value for this type associated with the given value.
This method must be able to correcly handle value being None.
"""
return self.Convert(value)
def IsUnion(self):
"""
Returns true if the type is a union. This is necessary to be able to
identify a union when descriptor.py cannot be imported.
"""
return False
class SerializableType(Type):
"""Describe a type that can be serialized by itself."""
def __init__(self, typecode):
Type.__init__(self)
self.typecode = typecode
self.byte_size = struct.calcsize('<%s' % self.GetTypeCode())
def GetTypeCode(self):
"""
Returns the type code (as defined by the struct module) used to encode
this type.
"""
return self.typecode
def GetByteSize(self):
"""
Returns the size of the encoding of this type.
"""
return self.byte_size
def GetAlignment(self):
"""
Returns the alignment required by the encoding of this type. By default it
is set to the byte size of the biggest packed value.
"""
return max([struct.calcsize('<%s' % c) for c in self.GetTypeCode()])
def Serialize(self, value, data_offset, data, handle_offset):
"""
Serialize a value of this type.
Args:
value: the value to serialize.
data_offset: the offset to the end of the data bytearray. Used to encode
pointers.
data: the bytearray to append additional data to.
handle_offset: the offset to use to encode handles.
Returns a a tuple where the first element is the value to encode, and the
second is the array of handles to add to the message.
"""
raise NotImplementedError()
def Deserialize(self, value, context):
"""
Deserialize a value of this type.
Args:
value: the base value for this type. This is always a numeric type, and
corresponds to the first element in the tuple returned by
Serialize.
data: the bytearray to retrieve additional data from.
handles: the array of handles contained in the message to deserialize.
Returns the deserialized value.
"""
raise NotImplementedError()
class BooleanType(Type):
"""Type object for booleans"""
def Convert(self, value):
return bool(value)
class NumericType(SerializableType):
"""Base Type object for all numeric types"""
def GetDefaultValue(self, value):
if value is None:
return self.Convert(0)
return self.Convert(value)
def Serialize(self, value, data_offset, data, handle_offset):
return (value, [])
def Deserialize(self, value, context):
return value
class IntegerType(NumericType):
"""Type object for integer types."""
def __init__(self, typecode):
NumericType.__init__(self, typecode)
size = 8 * self.byte_size
signed = typecode.islower()
if signed:
self._min_value = -(1 << (size - 1))
self._max_value = (1 << (size - 1)) - 1
else:
self._min_value = 0
self._max_value = (1 << size) - 1
def Convert(self, value):
if value is None:
raise TypeError('None is not an integer.')
if not isinstance(value, (int, long)):
raise TypeError('%r is not an integer type' % value)
if value < self._min_value or value > self._max_value:
raise OverflowError('%r is not in the range [%d, %d]' %
(value, self._min_value, self._max_value))
return value
class FloatType(NumericType):
"""Type object for floating point number types."""
def Convert(self, value):
if value is None:
raise TypeError('None is not an floating point number.')
if not isinstance(value, (int, long, float)):
raise TypeError('%r is not a numeric type' % value)
return float(value)
class UnionType(SerializableType):
"""Base Type object for union."""
def __init__(self, union_type_getter, nullable=False):
SerializableType.__init__(self, 'IIQ')
self.nullable = nullable
self._union_type_getter = union_type_getter
self._union_type = None
def IsUnion(self):
return True
@property
def union_type(self):
if not self._union_type:
self._union_type = self._union_type_getter()
return self._union_type
def Serialize(self, value, data_offset, data, handle_offset):
if not value:
if not self.nullable:
raise serialization.SerializationException(
'Trying to serialize null for non nullable type.')
return ((0, 0, 0), [])
((size, tag, entry, new_data), new_handles) = (
value.SerializeInline(handle_offset))
if len(new_data) > 0:
data.extend(new_data)
entry = data_offset - 8
return ((size, tag, entry), new_handles)
def Deserialize(self, value, context):
result = self.union_type.Deserialize(context)
if not result and not self.nullable:
raise serialization.DeserializationException(
'Trying to deserialize null for non nullable type.')
return result
class PointerType(SerializableType):
"""Base Type object for pointers."""
def __init__(self, nullable=False):
SerializableType.__init__(self, 'Q')
self.nullable = nullable
def Serialize(self, value, data_offset, data, handle_offset):
if value is None and not self.nullable:
raise serialization.SerializationException(
'Trying to serialize null for non nullable type.')
if value is None:
return (0, [])
return self.SerializePointer(value, data_offset, data, handle_offset)
def Deserialize(self, value, context):
if value == 0:
if not self.nullable:
raise serialization.DeserializationException(
'Trying to deserialize null for non nullable type.')
return None
if value % 8 != 0:
raise serialization.DeserializationException(
'Pointer alignment is incorrect.')
sub_context = context.GetSubContext(value)
if len(sub_context.data) < serialization.HEADER_STRUCT.size:
raise serialization.DeserializationException(
'Available data too short to contain header.')
(size, nb_elements) = serialization.HEADER_STRUCT.unpack_from(
sub_context.data)
if len(sub_context.data) < size or size < serialization.HEADER_STRUCT.size:
raise serialization.DeserializationException('Header size is incorrect.')
sub_context.ClaimMemory(0, size)
return self.DeserializePointer(size, nb_elements, sub_context)
def SerializePointer(self, value, data_offset, data, handle_offset):
"""Serialize the not null value."""
raise NotImplementedError()
def DeserializePointer(self, size, nb_elements, context):
raise NotImplementedError()
class StringType(PointerType):
"""
Type object for strings.
Strings are represented as unicode, and the conversion is done using the
default encoding if a string instance is used.
"""
def __init__(self, nullable=False):
PointerType.__init__(self, nullable)
self._array_type = NativeArrayType('B', nullable)
def Convert(self, value):
if value is None or isinstance(value, unicode):
return value
if isinstance(value, str):
return unicode(value)
raise TypeError('%r is not a string' % value)
def SerializePointer(self, value, data_offset, data, handle_offset):
string_array = array.array('b')
string_array.fromstring(value.encode('utf8'))
return self._array_type.SerializeArray(
string_array, data_offset, data, handle_offset)
def DeserializePointer(self, size, nb_elements, context):
string_array = self._array_type.DeserializeArray(size, nb_elements, context)
return unicode(string_array.tostring(), 'utf8')
class BaseHandleType(SerializableType):
"""Type object for handles."""
def __init__(self, nullable=False, type_code='i'):
SerializableType.__init__(self, type_code)
self.nullable = nullable
def Serialize(self, value, data_offset, data, handle_offset):
handle = self.ToHandle(value)
if not handle.IsValid() and not self.nullable:
raise serialization.SerializationException(
'Trying to serialize null for non nullable type.')
if not handle.IsValid():
return (-1, [])
return (handle_offset, [handle])
def Deserialize(self, value, context):
if value == -1:
if not self.nullable:
raise serialization.DeserializationException(
'Trying to deserialize null for non nullable type.')
return self.FromHandle(mojo_system.Handle())
return self.FromHandle(context.ClaimHandle(value))
def FromHandle(self, handle):
raise NotImplementedError()
def ToHandle(self, value):
raise NotImplementedError()
class HandleType(BaseHandleType):
"""Type object for handles."""
def Convert(self, value):
if value is None:
return mojo_system.Handle()
if not isinstance(value, mojo_system.Handle):
raise TypeError('%r is not a handle' % value)
return value
def FromHandle(self, handle):
return handle
def ToHandle(self, value):
return value
class InterfaceRequestType(BaseHandleType):
"""Type object for interface requests."""
def Convert(self, value):
if value is None:
return reflection.InterfaceRequest(mojo_system.Handle())
if not isinstance(value, reflection.InterfaceRequest):
raise TypeError('%r is not an interface request' % value)
return value
def FromHandle(self, handle):
return reflection.InterfaceRequest(handle)
def ToHandle(self, value):
return value.PassMessagePipe()
class InterfaceType(BaseHandleType):
"""Type object for interfaces."""
def __init__(self, interface_getter, nullable=False):
# handle (4 bytes) + version (4 bytes)
BaseHandleType.__init__(self, nullable, 'iI')
self._interface_getter = interface_getter
self._interface = None
def Convert(self, value):
if value is None or isinstance(value, self.interface):
return value
raise TypeError('%r is not an instance of ' % self.interface)
@property
def interface(self):
if not self._interface:
self._interface = self._interface_getter()
return self._interface
def Serialize(self, value, data_offset, data, handle_offset):
(encoded_handle, handles) = super(InterfaceType, self).Serialize(
value, data_offset, data, handle_offset)
if encoded_handle == -1:
version = 0
else:
version = self.interface.manager.version
if value and isinstance(value, reflection.InterfaceProxy):
version = value.manager.version
return ((encoded_handle, version), handles)
def Deserialize(self, value, context):
proxy = super(InterfaceType, self).Deserialize(value[0], context)
if proxy:
proxy.manager.version = value[1]
return proxy
def FromHandle(self, handle):
if handle.IsValid():
return self.interface.manager.Proxy(handle)
return None
def ToHandle(self, value):
if not value:
return mojo_system.Handle()
if isinstance(value, reflection.InterfaceProxy):
return value.manager.PassMessagePipe()
pipe = mojo_system.MessagePipe()
self.interface.manager.Bind(value, pipe.handle0)
return pipe.handle1
class BaseArrayType(PointerType):
"""Abstract Type object for arrays."""
def __init__(self, nullable=False, length=0):
PointerType.__init__(self, nullable)
self.length = length
def SerializePointer(self, value, data_offset, data, handle_offset):
if self.length != 0 and len(value) != self.length:
raise serialization.SerializationException('Incorrect array size')
return self.SerializeArray(value, data_offset, data, handle_offset)
def SerializeArray(self, value, data_offset, data, handle_offset):
"""Serialize the not null array."""
raise NotImplementedError()
def DeserializePointer(self, size, nb_elements, context):
if self.length != 0 and nb_elements != self.length:
raise serialization.DeserializationException('Incorrect array size')
if (size <
serialization.HEADER_STRUCT.size + self.SizeForLength(nb_elements)):
raise serialization.DeserializationException('Incorrect array size')
return self.DeserializeArray(size, nb_elements, context)
def DeserializeArray(self, size, nb_elements, context):
raise NotImplementedError()
def SizeForLength(self, nb_elements):
raise NotImplementedError()
class BooleanArrayType(BaseArrayType):
def __init__(self, nullable=False, length=0):
BaseArrayType.__init__(self, nullable, length)
self._array_type = NativeArrayType('B', nullable)
def Convert(self, value):
if value is None:
return value
return [TYPE_BOOL.Convert(x) for x in value]
def SerializeArray(self, value, data_offset, data, handle_offset):
groups = [value[i:i+8] for i in range(0, len(value), 8)]
converted = array.array('B', [_ConvertBooleansToByte(x) for x in groups])
return _SerializeNativeArray(converted, data_offset, data, len(value))
def DeserializeArray(self, size, nb_elements, context):
converted = self._array_type.DeserializeArray(size, nb_elements, context)
elements = list(itertools.islice(
itertools.chain.from_iterable(
[_ConvertByteToBooleans(x, 8) for x in converted]),
0,
nb_elements))
return elements
def SizeForLength(self, nb_elements):
return (nb_elements + 7) // 8
class GenericArrayType(BaseArrayType):
"""Type object for arrays of pointers."""
def __init__(self, sub_type, nullable=False, length=0):
BaseArrayType.__init__(self, nullable, length)
assert isinstance(sub_type, SerializableType)
self.sub_type = sub_type
def Convert(self, value):
if value is None:
return value
return [self.sub_type.Convert(x) for x in value]
def SerializeArray(self, value, data_offset, data, handle_offset):
size = (serialization.HEADER_STRUCT.size +
self.sub_type.GetByteSize() * len(value))
data_end = len(data)
position = len(data) + serialization.HEADER_STRUCT.size
data.extend(bytearray(size +
serialization.NeededPaddingForAlignment(size)))
returned_handles = []
to_pack = []
for item in value:
(new_data, new_handles) = self.sub_type.Serialize(
item,
len(data) - position,
data,
handle_offset + len(returned_handles))
to_pack.extend(serialization.Flatten(new_data))
returned_handles.extend(new_handles)
position = position + self.sub_type.GetByteSize()
serialization.HEADER_STRUCT.pack_into(data, data_end, size, len(value))
# TODO(azani): Refactor so we don't have to create big formatting strings.
struct.pack_into(('%s' % self.sub_type.GetTypeCode()) * len(value),
data,
data_end + serialization.HEADER_STRUCT.size,
*to_pack)
return (data_offset, returned_handles)
def DeserializeArray(self, size, nb_elements, context):
# TODO(azani): Refactor so the format string isn't so big.
values = struct.unpack_from(
nb_elements * self.sub_type.GetTypeCode(),
buffer(context.data, serialization.HEADER_STRUCT.size))
values_per_element = len(self.sub_type.GetTypeCode())
assert nb_elements * values_per_element == len(values)
result = []
sub_context = context.GetSubContext(serialization.HEADER_STRUCT.size)
for index in xrange(nb_elements):
if values_per_element == 1:
value = values[index]
else:
value = tuple(values[index * values_per_element :
(index + 1) * values_per_element])
result.append(self.sub_type.Deserialize(
value,
sub_context))
sub_context = sub_context.GetSubContext(self.sub_type.GetByteSize())
return result
def SizeForLength(self, nb_elements):
return nb_elements * self.sub_type.GetByteSize();
class NativeArrayType(BaseArrayType):
"""Type object for arrays of native types."""
def __init__(self, typecode, nullable=False, length=0):
BaseArrayType.__init__(self, nullable, length)
self.array_typecode = typecode
self.element_size = struct.calcsize('<%s' % self.array_typecode)
def Convert(self, value):
if value is None:
return value
if (isinstance(value, array.array) and
value.array_typecode == self.array_typecode):
return value
return array.array(self.array_typecode, value)
def SerializeArray(self, value, data_offset, data, handle_offset):
return _SerializeNativeArray(value, data_offset, data, len(value))
def DeserializeArray(self, size, nb_elements, context):
result = array.array(self.array_typecode)
result.fromstring(buffer(context.data,
serialization.HEADER_STRUCT.size,
size - serialization.HEADER_STRUCT.size))
return result
def SizeForLength(self, nb_elements):
return nb_elements * self.element_size
class StructType(PointerType):
"""Type object for structs."""
def __init__(self, struct_type_getter, nullable=False):
PointerType.__init__(self)
self._struct_type_getter = struct_type_getter
self._struct_type = None
self.nullable = nullable
@property
def struct_type(self):
if not self._struct_type:
self._struct_type = self._struct_type_getter()
return self._struct_type
def Convert(self, value):
if value is None or isinstance(value, self.struct_type):
return value
raise TypeError('%r is not an instance of %r' % (value, self.struct_type))
def GetDefaultValue(self, value):
if value:
return self.struct_type()
return None
def SerializePointer(self, value, data_offset, data, handle_offset):
(new_data, new_handles) = value.Serialize(handle_offset)
data.extend(new_data)
return (data_offset, new_handles)
def DeserializePointer(self, size, nb_elements, context):
return self.struct_type.Deserialize(context)
class MapType(SerializableType):
"""Type objects for maps."""
def __init__(self, key_type, value_type, nullable=False):
self._key_type = key_type
self._value_type = value_type
dictionary = {
'__metaclass__': reflection.MojoStructType,
'__module__': __name__,
'DESCRIPTOR': {
'fields': [
SingleFieldGroup('keys', MapType._GetArrayType(key_type), 0, 0),
SingleFieldGroup('values', MapType._GetArrayType(value_type), 1, 0),
],
}
}
self.struct = reflection.MojoStructType('MapStruct', (object,), dictionary)
self.struct_type = StructType(lambda: self.struct, nullable)
SerializableType.__init__(self, self.struct_type.typecode)
def Convert(self, value):
if value is None:
return value
if isinstance(value, dict):
return dict([(self._key_type.Convert(x), self._value_type.Convert(y)) for
x, y in value.iteritems()])
raise TypeError('%r is not a dictionary.')
def Serialize(self, value, data_offset, data, handle_offset):
s = None
if value:
keys, values = [], []
for key, value in value.iteritems():
keys.append(key)
values.append(value)
s = self.struct(keys=keys, values=values)
return self.struct_type.Serialize(s, data_offset, data, handle_offset)
def Deserialize(self, value, context):
s = self.struct_type.Deserialize(value, context)
if s:
if len(s.keys) != len(s.values):
raise serialization.DeserializationException(
'keys and values do not have the same length.')
return dict(zip(s.keys, s.values))
return None
@staticmethod
def _GetArrayType(t):
if t == TYPE_BOOL:
return BooleanArrayType()
else:
return GenericArrayType(t)
TYPE_BOOL = BooleanType()
TYPE_INT8 = IntegerType('b')
TYPE_INT16 = IntegerType('h')
TYPE_INT32 = IntegerType('i')
TYPE_INT64 = IntegerType('q')
TYPE_UINT8 = IntegerType('B')
TYPE_UINT16 = IntegerType('H')
TYPE_UINT32 = IntegerType('I')
TYPE_UINT64 = IntegerType('Q')
TYPE_FLOAT = FloatType('f')
TYPE_DOUBLE = FloatType('d')
TYPE_STRING = StringType()
TYPE_NULLABLE_STRING = StringType(True)
TYPE_HANDLE = HandleType()
TYPE_NULLABLE_HANDLE = HandleType(True)
TYPE_INTERFACE_REQUEST = InterfaceRequestType()
TYPE_NULLABLE_INTERFACE_REQUEST = InterfaceRequestType(True)
class FieldDescriptor(object):
"""Describes a field in a generated struct."""
def __init__(self, name, field_type, index, version, default_value=None):
self.name = name
self.field_type = field_type
self.version = version
self.index = index
self._default_value = default_value
def GetDefaultValue(self):
return self.field_type.GetDefaultValue(self._default_value)
class FieldGroup(object):
"""
Describe a list of field in the generated struct that must be
serialized/deserialized together.
"""
def __init__(self, descriptors):
self.descriptors = descriptors
def GetDescriptors(self):
return self.descriptors
def GetTypeCode(self):
raise NotImplementedError()
def GetByteSize(self):
raise NotImplementedError()
def GetAlignment(self):
raise NotImplementedError()
def GetMinVersion(self):
raise NotImplementedError()
def GetMaxVersion(self):
raise NotImplementedError()
def Serialize(self, obj, data_offset, data, handle_offset):
raise NotImplementedError()
def Deserialize(self, value, context):
raise NotImplementedError()
def Filter(self, version):
raise NotImplementedError()
class SingleFieldGroup(FieldGroup, FieldDescriptor):
"""A FieldGroup that contains a single FieldDescriptor."""
def __init__(self, name, field_type, index, version, default_value=None):
FieldDescriptor.__init__(
self, name, field_type, index, version, default_value)
FieldGroup.__init__(self, [self])
def GetTypeCode(self):
return self.field_type.GetTypeCode()
def GetByteSize(self):
return self.field_type.GetByteSize()
def GetAlignment(self):
return self.field_type.GetAlignment()
def GetMinVersion(self):
return self.version
def GetMaxVersion(self):
return self.version
def Serialize(self, obj, data_offset, data, handle_offset):
value = getattr(obj, self.name)
return self.field_type.Serialize(value, data_offset, data, handle_offset)
def Deserialize(self, value, context):
entity = self.field_type.Deserialize(value, context)
return { self.name: entity }
def Filter(self, version):
return self
class BooleanGroup(FieldGroup):
"""A FieldGroup to pack booleans."""
def __init__(self, descriptors):
FieldGroup.__init__(self, descriptors)
self.min_version = min([descriptor.version for descriptor in descriptors])
self.max_version = max([descriptor.version for descriptor in descriptors])
def GetTypeCode(self):
return 'B'
def GetByteSize(self):
return 1
def GetAlignment(self):
return 1
def GetMinVersion(self):
return self.min_version
def GetMaxVersion(self):
return self.max_version
def Serialize(self, obj, data_offset, data, handle_offset):
value = _ConvertBooleansToByte(
[getattr(obj, field.name) for field in self.GetDescriptors()])
return (value, [])
def Deserialize(self, value, context):
values = itertools.izip_longest([x.name for x in self.descriptors],
_ConvertByteToBooleans(value),
fillvalue=False)
return dict(values)
def Filter(self, version):
return BooleanGroup(
filter(lambda d: d.version <= version, self.descriptors))
def _SerializeNativeArray(value, data_offset, data, length):
data_size = len(data)
data.extend(bytearray(serialization.HEADER_STRUCT.size))
data.extend(buffer(value))
data_length = len(data) - data_size
data.extend(bytearray(serialization.NeededPaddingForAlignment(data_length)))
serialization.HEADER_STRUCT.pack_into(data, data_size, data_length, length)
return (data_offset, [])
def _ConvertBooleansToByte(booleans):
"""Pack a list of booleans into an integer."""
return reduce(lambda x, y: x * 2 + y, reversed(booleans), 0)
def _ConvertByteToBooleans(value, min_size=0):
"""Unpack an integer into a list of booleans."""
res = []
while value:
res.append(bool(value&1))
value = value / 2
res.extend([False] * (min_size - len(res)))
return res