2015-01-15 18:49:00 +04:00
|
|
|
from google.protobuf.descriptor import FieldDescriptor as FD
|
2015-01-19 16:10:54 +02:00
|
|
|
import opts_pb2
|
2014-12-31 14:06:48 +02:00
|
|
|
|
2015-01-15 18:49:00 +04:00
|
|
|
# pb2dict and dict2pb are methods to convert pb to/from dict.
|
|
|
|
# Inspired by:
|
|
|
|
# protobuf-to-dict - https://github.com/benhodgson/protobuf-to-dict
|
|
|
|
# protobuf-json - https://code.google.com/p/protobuf-json/
|
|
|
|
# protobuf source - https://code.google.com/p/protobuf/
|
|
|
|
# Both protobuf-to-dict/json do not fit here because of several reasons,
|
|
|
|
# here are some of them:
|
|
|
|
# - both have a common bug in treating optional field with empty
|
|
|
|
# repeated inside.
|
|
|
|
# - protobuf-to-json is not avalible in pip or in any other python
|
|
|
|
# repo, so it is hard to distribute and we can't rely on it.
|
|
|
|
# - both do not treat enums in a way we would like to. They convert
|
|
|
|
# protobuf enum to int, but we need a string here, because it is
|
|
|
|
# much more informative. BTW, protobuf text_format converts pb
|
|
|
|
# enums to string value too. (i.e. "march : x86_64" is better then
|
|
|
|
# "march : 1").
|
|
|
|
|
|
|
|
|
|
|
|
_basic_cast = {
|
|
|
|
FD.TYPE_DOUBLE : float,
|
|
|
|
FD.TYPE_FLOAT : float,
|
|
|
|
FD.TYPE_FIXED64 : float,
|
|
|
|
FD.TYPE_FIXED32 : float,
|
|
|
|
FD.TYPE_SFIXED64 : float,
|
|
|
|
FD.TYPE_SFIXED32 : float,
|
|
|
|
|
|
|
|
FD.TYPE_INT64 : long,
|
|
|
|
FD.TYPE_UINT64 : long,
|
|
|
|
FD.TYPE_SINT64 : long,
|
|
|
|
|
|
|
|
FD.TYPE_INT32 : int,
|
|
|
|
FD.TYPE_UINT32 : int,
|
|
|
|
FD.TYPE_SINT32 : int,
|
|
|
|
|
|
|
|
FD.TYPE_BOOL : bool,
|
|
|
|
|
|
|
|
FD.TYPE_STRING : unicode
|
|
|
|
}
|
|
|
|
|
2015-01-19 16:10:54 +02:00
|
|
|
def _marked_as_hex(field):
|
|
|
|
return field.GetOptions().Extensions[opts_pb2.criu].hex
|
|
|
|
|
2015-01-28 17:15:00 +03:00
|
|
|
def _pb2dict_cast(field, value, pretty = False, is_hex = False):
|
2015-01-19 16:10:54 +02:00
|
|
|
if not is_hex:
|
|
|
|
is_hex = _marked_as_hex(field)
|
|
|
|
|
2015-01-15 18:49:00 +04:00
|
|
|
if field.type == FD.TYPE_MESSAGE:
|
2015-01-28 17:15:00 +03:00
|
|
|
return pb2dict(value, pretty, is_hex)
|
2015-01-15 18:49:00 +04:00
|
|
|
elif field.type == FD.TYPE_BYTES:
|
|
|
|
return value.encode('base64')
|
|
|
|
elif field.type == FD.TYPE_ENUM:
|
|
|
|
return field.enum_type.values_by_number.get(value, None).name
|
|
|
|
elif field.type in _basic_cast:
|
2015-01-19 16:10:54 +02:00
|
|
|
cast = _basic_cast[field.type]
|
2015-01-28 17:15:00 +03:00
|
|
|
if (cast == int or cast == long) and is_hex and pretty:
|
2015-01-19 16:10:54 +02:00
|
|
|
# Fields that have (criu).hex = true option set
|
|
|
|
# should be stored in hex string format.
|
|
|
|
return "0x%x" % value
|
|
|
|
else:
|
|
|
|
return cast(value)
|
2015-01-15 18:49:00 +04:00
|
|
|
else:
|
|
|
|
raise Exception("Field(%s) has unsupported type %d" % (field.name, field.type))
|
2014-12-31 14:06:48 +02:00
|
|
|
|
2015-01-28 17:15:00 +03:00
|
|
|
def pb2dict(pb, pretty = False, is_hex = False):
|
2014-12-31 14:06:48 +02:00
|
|
|
"""
|
|
|
|
Convert protobuf msg to dictionary.
|
|
|
|
Takes a protobuf message and returns a dict.
|
|
|
|
"""
|
|
|
|
d = {}
|
2015-01-15 18:49:00 +04:00
|
|
|
for field, value in pb.ListFields():
|
|
|
|
if field.label == FD.LABEL_REPEATED:
|
|
|
|
d_val = []
|
|
|
|
for v in value:
|
2015-01-28 17:15:00 +03:00
|
|
|
d_val.append(_pb2dict_cast(field, v, pretty, is_hex))
|
2014-12-31 14:06:48 +02:00
|
|
|
else:
|
2015-01-28 17:15:00 +03:00
|
|
|
d_val = _pb2dict_cast(field, value, pretty, is_hex)
|
2014-12-31 14:06:48 +02:00
|
|
|
|
2015-01-15 18:49:00 +04:00
|
|
|
d[field.name] = d_val
|
2014-12-31 14:06:48 +02:00
|
|
|
return d
|
|
|
|
|
2015-01-15 18:49:00 +04:00
|
|
|
def _dict2pb_cast(field, value):
|
|
|
|
# Not considering TYPE_MESSAGE here, as repeated
|
|
|
|
# and non-repeated messages need special treatment
|
|
|
|
# in this case, and are hadled separately.
|
|
|
|
if field.type == FD.TYPE_BYTES:
|
|
|
|
return value.decode('base64')
|
|
|
|
elif field.type == FD.TYPE_ENUM:
|
|
|
|
return field.enum_type.values_by_name.get(value, None).number
|
2015-01-22 18:58:06 +03:00
|
|
|
elif field.type in _basic_cast:
|
2015-01-19 16:10:54 +02:00
|
|
|
cast = _basic_cast[field.type]
|
|
|
|
if (cast == int or cast == long) and isinstance(value, unicode):
|
|
|
|
# Some int or long fields might be stored as hex
|
|
|
|
# strings. See _pb2dict_cast.
|
|
|
|
return cast(value, 0)
|
|
|
|
else:
|
|
|
|
return cast(value)
|
2015-01-22 18:58:06 +03:00
|
|
|
else:
|
|
|
|
raise Exception("Field(%s) has unsupported type %d" % (field.name, field.type))
|
2015-01-15 18:49:00 +04:00
|
|
|
|
2014-12-31 14:06:48 +02:00
|
|
|
def dict2pb(d, pb):
|
|
|
|
"""
|
|
|
|
Convert dictionary to protobuf msg.
|
|
|
|
Takes dict and protobuf message to be merged into.
|
|
|
|
"""
|
2015-01-15 18:49:00 +04:00
|
|
|
for field in pb.DESCRIPTOR.fields:
|
|
|
|
if field.name not in d:
|
|
|
|
continue
|
|
|
|
value = d[field.name]
|
|
|
|
if field.label == FD.LABEL_REPEATED:
|
|
|
|
pb_val = getattr(pb, field.name, None)
|
|
|
|
for v in value:
|
|
|
|
if field.type == FD.TYPE_MESSAGE:
|
|
|
|
dict2pb(v, pb_val.add())
|
2014-12-31 14:06:48 +02:00
|
|
|
else:
|
2015-01-15 18:49:00 +04:00
|
|
|
pb_val.append(_dict2pb_cast(field, v))
|
2014-12-31 14:06:48 +02:00
|
|
|
else:
|
2015-01-15 18:49:00 +04:00
|
|
|
if field.type == FD.TYPE_MESSAGE:
|
|
|
|
# SetInParent method acts just like has_* = true in C,
|
|
|
|
# and helps to properly treat cases when we have optional
|
|
|
|
# field with empty repeated inside.
|
|
|
|
getattr(pb, field.name).SetInParent()
|
|
|
|
|
|
|
|
dict2pb(value, getattr(pb, field.name, None))
|
|
|
|
else:
|
|
|
|
setattr(pb, field.name, _dict2pb_cast(field, value))
|
|
|
|
return pb
|