def decode_gpb_kv(message, args):
    """
    Decode and print a GPB key-value message
    """
    header = telemetry_kv_pb2.Telemetry()
    try:
        header.ParseFromString(message)
    except Exception as e:
        print("ERROR decoding header. Not a valid 'Telemetry' message. Full " "message dump below:")
        print(bytes_to_string(message))
        return

    if args.json_dump:
        print(json.dumps(proto_to_dict(header)))
    else:
        # Print the message header
        print_gpb_kv_hdr(header)

        # Loop over the tables within the message, printing either just the first
        # row or all rows depending on the args specified
        if args.print_all:
            for entry in header.fields:
                print_gpb_kv_field(entry, 2)
        elif len(header.fields) > 0 and not args.brief:
            print("  Displaying first entry only")
            print_gpb_kv_field(header.fields[0], 1)
def decode_gpb_kv(message, args):
    """
    Decode and print a GPB key-value message
    """
    header = telemetry_kv_pb2.Telemetry()
    try:
        header.ParseFromString(message)
    except Exception as e:
        print("ERROR decoding header. Not a valid 'Telemetry' message. Full "
              "message dump below:")
        print(bytes_to_string(message))
        return

    if args.json_dump:
        print(json.dumps(proto_to_dict(header)))
    else:
        # Print the message header
        print_gpb_kv_hdr(header)

        # Loop over the tables within the message, printing either just the first
        # row or all rows depending on the args specified
        if args.print_all:
            for entry in header.fields:
                print_gpb_kv_field(entry, 2)
        elif len(header.fields) > 0 and not args.brief:
            print("  Displaying first entry only")
            print_gpb_kv_field(header.fields[0], 1)
def print_gpb_compact_msg(field, indent, args):
    """
    Recursively iterate over a compactGPB obejct, displaying all fields at an 
    appropriate indent.
    Argument: field
      The object to print.
    Argument: indent
      The indent level to start printing at.
    """
    for descriptor in field.DESCRIPTOR.fields:
        value = getattr(field, descriptor.name)
        if descriptor.type == descriptor.TYPE_MESSAGE:
            #
            # If the value is a sub-message then recursively call this function
            # to decode it. If the message is repeated then iterate over each
            # item.
            #
            if descriptor.label == descriptor.LABEL_REPEATED:
                print_at_indent(
                    "{} ({} items) [".format(descriptor.name, len(value)),
                    indent)
                for i, item in enumerate(value):
                    print_at_indent("{} {} {{".format(descriptor.name, i),
                                    indent)
                    print_gpb_compact_msg(item, indent + 1, args)
                    print_at_indent("}", indent)
                    if not args.print_all:
                        # Stop after the first item unless all have been
                        # requested
                        break
                print_at_indent("]", indent)
            else:
                print_at_indent("{} {{".format(descriptor.name), indent)
                print_gpb_compact_msg(value, indent + 1, args)
                print_at_indent("}", indent)
        elif descriptor.type == descriptor.TYPE_ENUM:
            #
            # For enum types print the enum name
            #
            enum_name = descriptor.enum_type.values[value].name
            print_at_indent("{}: {}".format(descriptor.name, enum_name),
                            indent)
        elif descriptor.type == descriptor.TYPE_BYTES:
            print_at_indent(
                "{}: {}".format(descriptor.name, bytes_to_string(value)),
                indent)
        else:
            #
            # For everything else just print the value
            #
            print_at_indent("{}: {}".format(descriptor.name, value), indent)
def print_gpb_compact_msg(field, indent, args):
    """
    Recursively iterate over a compactGPB obejct, displaying all fields at an 
    appropriate indent.
    Argument: field
      The object to print.
    Argument: indent
      The indent level to start printing at.
    """
    for descriptor in field.DESCRIPTOR.fields:
        value = getattr(field, descriptor.name)
        if descriptor.type == descriptor.TYPE_MESSAGE:
            #
            # If the value is a sub-message then recursively call this function
            # to decode it. If the message is repeated then iterate over each
            # item.
            #
            if descriptor.label == descriptor.LABEL_REPEATED:
                print_at_indent("{} ({} items) [".format(descriptor.name, len(value)), indent)
                for i, item in enumerate(value):
                    print_at_indent("{} {} {{".format(descriptor.name, i), indent)
                    print_gpb_compact_msg(item, indent + 1, args)
                    print_at_indent("}", indent)
                    if not args.print_all:
                        # Stop after the first item unless all have been
                        # requested
                        break
                print_at_indent("]", indent)
            else:
                print_at_indent("{} {{".format(descriptor.name), indent)
                print_gpb_compact_msg(value, indent + 1, args)
                print_at_indent("}", indent)
        elif descriptor.type == descriptor.TYPE_ENUM:
            #
            # For enum types print the enum name
            #
            enum_name = descriptor.enum_type.values[value].name
            print_at_indent("{}: {}".format(descriptor.name, enum_name), indent)
        elif descriptor.type == descriptor.TYPE_BYTES:
            print_at_indent("{}: {}".format(descriptor.name, bytes_to_string(value)), indent)
        else:
            #
            # For everything else just print the value
            #
            print_at_indent("{}: {}".format(descriptor.name, value), indent)
def print_json_data(obj, indent, args):
    """
    Print the body of a JSON message
    """
    if type(obj) == dict:
        for key in list(obj.keys()):
            child = obj[key]
            # If the child object is a list or dictionary then indent using
            # braces. If the child is a leaf then just print on a single line.
            if type(child) == dict:
                print_at_indent("{} {{".format(key), indent)
                print_json_data(obj[key], indent + 1, args)
                print_at_indent("}", indent)
            elif type(child) == list:
                if not args.print_all:
                    warning = " - displaying first entry only"
                else:
                    warning = ""
                print_at_indent(
                    "{} ({} items{}) [".format(key, len(child), warning),
                    indent)
                print_json_data(obj[key], indent + 1, args)
                print_at_indent("]", indent)
            elif key == "CollectionTime":
                # Pretty-print collection timestamp
                print_at_indent(
                    "{}: {}".format(key, timestamp_to_string(child)), indent)
            else:
                # Try printing values as a string and if that fails print
                # it as bytes
                try:
                    print_at_indent("{}: {}".format(key, str(child)), indent)
                except Exception:
                    print_at_indent(
                        "{}: {}".format(key, bytes_to_string(child)), indent)
    elif type(obj) == list:
        for i, item in enumerate(obj):
            print_at_indent("[{}]".format(i), indent)
            print_json_data(item, indent + 1, args)
            if not args.print_all:
                # Stop after first item
                break
    else:
        print_at_indent("{}".format(str(obj)), indent)
def print_json_data(obj, indent, args):
    """
    Print the body of a JSON message
    """
    if type(obj) == dict:
        for key in list(obj.keys()):
            child = obj[key]
            # If the child object is a list or dictionary then indent using
            # braces. If the child is a leaf then just print on a single line.
            if type(child) == dict:
                print_at_indent("{} {{".format(key), indent)
                print_json_data(obj[key], indent + 1, args)
                print_at_indent("}", indent)
            elif type(child) == list:
                if not args.print_all:
                    warning = " - displaying first entry only"
                else:
                    warning = ""
                print_at_indent("{} ({} items{}) [".format(key, len(child), warning), indent)
                print_json_data(obj[key], indent + 1, args)
                print_at_indent("]", indent)
            elif key == "CollectionTime":
                # Pretty-print collection timestamp
                print_at_indent("{}: {}".format(key, timestamp_to_string(child)), indent)
            else:
                # Try printing values as a string and if that fails print
                # it as bytes
                try:
                    print_at_indent("{}: {}".format(key, str(child)), indent)
                except Exception:
                    print_at_indent("{}: {}".format(key, bytes_to_string(child)), indent)
    elif type(obj) == list:
        for i, item in enumerate(obj):
            print_at_indent("[{}]".format(i), indent)
            print_json_data(item, indent + 1, args)
            if not args.print_all:
                # Stop after first item
                break
    else:
        print_at_indent("{}".format(str(obj)), indent)
def print_gpb_kv_field(field, indent):
    """
    Pretty-print a TelemtryField message
    """
    # Decode the timestamp if there is one
    if field.timestamp != 0:
        time = timestamp_to_string(field.timestamp)
    else:
        time = ""

    # Find the datatype and print it
    datatypes = [
        "bytes_value",
        "string_value",
        "bool_value",
        "uint32_value",
        "uint64_value",
        "sint32_value",
        "sint64_value",
        "double_value",
        "float_value",
    ]
    for d in datatypes:
        datatype = d[:-6]
        if field.HasField(d):
            if datatype == "bytes":
                print_gpb_kv_field_data(field.name, bytes_to_string(field.bytes_value), datatype, time, indent)
            else:
                print_gpb_kv_field_data(field.name, getattr(field, d), datatype, time, indent)

    # If 'fields' is used then recursively call this function to decode
    if len(field.fields) > 0:
        print_gpb_kv_field_data(
            field.name, "fields", "items {}".format(len(field.fields)), "{} {{".format(time), indent
        )

        for f in field.fields:
            print_gpb_kv_field(f, indent + 1)
        print_at_indent("}", indent)
def print_gpb_kv_field(field, indent):
    """
    Pretty-print a TelemtryField message
    """
    # Decode the timestamp if there is one
    if field.timestamp != 0:
        time = timestamp_to_string(field.timestamp)
    else:
        time = ""

    # Find the datatype and print it
    datatypes = [
        "bytes_value", "string_value", "bool_value", "uint32_value",
        "uint64_value", "sint32_value", "sint64_value", "double_value",
        "float_value"
    ]
    for d in datatypes:
        datatype = d[:-6]
        if field.HasField(d):
            if datatype == "bytes":
                print_gpb_kv_field_data(field.name,
                                        bytes_to_string(field.bytes_value),
                                        datatype, time, indent)
            else:
                print_gpb_kv_field_data(field.name, getattr(field, d),
                                        datatype, time, indent)

    # If 'fields' is used then recursively call this function to decode
    if len(field.fields) > 0:
        print_gpb_kv_field_data(field.name, "fields",
                                "items {}".format(len(field.fields)),
                                "{} {{".format(time), indent)

        for f in field.fields:
            print_gpb_kv_field(f, indent + 1)
        print_at_indent("}", indent)
def decode_gpb_compact(message, args):
    """
    Decode and print a GPB compact message
    """
    header = telemetry_pb2.TelemetryHeader()
    try:
        header.ParseFromString(message)
    except Exception as e:
        print("ERROR decoding header. Not a valid 'TelemetryHeader' message. " "Full message dump below:")
        print(bytes_to_string(message))
        return

    # Check the encoding value is correct
    ENCODING = 0x87654321
    if header.encoding != ENCODING:
        print("Invalid 'encoding' value {:#x} (expected {:#x})".format(header.encoding, ENCODING))
        return

    # Print the message header
    json_dict = {}
    if args.json_dump:
        # Convert the protobuf into a dictionary in preparation for dumping
        # it as JSON.
        json_dict = proto_to_dict(header)
    else:
        print_gpb_compact_hdr(header)

    # Loop over the tables within the message, printing either just the first
    # row or all rows depending on the args specified

    for t, entry in enumerate(header.tables):
        schema_path = entry.policy_path
        if not args.json_dump:
            print(INDENT + "Schema Path:{}".format(schema_path))
            warning = ""
            if not args.print_all:
                warning = "(Only first row displayed)"
            print(INDENT + "# Rows:{} {}".format(len(entry.row), warning))

        if not schema_path in decoder_dict.keys():
            print(INDENT + "No decoder available")
            if args.json_dump:
                json_dict["tables"][t]["row"][0] = "<No decoder available>"
        else:
            for i, row in enumerate(entry.row):
                row_msg = decoder_dict[schema_path]()
                try:
                    row_msg.ParseFromString(row)
                    if args.json_dump:
                        # Replace the bytes in the 'row' field with a decoded
                        # dict
                        table = json_dict["tables"][t]
                        table["row"][i] = proto_to_dict(row_msg)
                    else:
                        print(INDENT * 2 + "Row {}:".format(i))
                        print_gpb_compact_msg(row_msg, 2, args)
                        print("")
                except Exception as e:
                    print("ERROR decoding row. Not a valid GPB message. Full " "message dump below: {}".format(e))
                    print(bytes_to_string(row))

                if not args.print_all and not args.json_dump:
                    break

    if args.json_dump:
        print(json.dumps(json_dict))
DECODE_FN_MAP = {
    FieldDescriptor.TYPE_DOUBLE: float,
    FieldDescriptor.TYPE_FLOAT: float,
    FieldDescriptor.TYPE_INT32: int,
    FieldDescriptor.TYPE_INT64: long,
    FieldDescriptor.TYPE_UINT32: int,
    FieldDescriptor.TYPE_UINT64: long,
    FieldDescriptor.TYPE_SINT32: int,
    FieldDescriptor.TYPE_SINT64: long,
    FieldDescriptor.TYPE_FIXED32: int,
    FieldDescriptor.TYPE_FIXED64: long,
    FieldDescriptor.TYPE_SFIXED32: int,
    FieldDescriptor.TYPE_SFIXED64: long,
    FieldDescriptor.TYPE_BOOL: bool,
    FieldDescriptor.TYPE_STRING: unicode,
    FieldDescriptor.TYPE_BYTES: lambda b: bytes_to_string(b),
    FieldDescriptor.TYPE_ENUM: int,
}


def field_type_to_fn(msg, field):
    if field.type == FieldDescriptor.TYPE_MESSAGE:
        # For embedded messages recursively call this function. If it is
        # a repeated field return a list
        result = lambda msg: proto_to_dict(msg)
    elif field.type in DECODE_FN_MAP:
        result = DECODE_FN_MAP[field.type]
    else:
        raise TypeError("Field %s.%s has unrecognised type id %d" % (msg.__class__.__name__, field.name, field.type))
    return result
def decode_gpb_compact(message, args):
    """
    Decode and print a GPB compact message
    """
    header = telemetry_pb2.TelemetryHeader()
    try:
        header.ParseFromString(message)
    except Exception as e:
        print("ERROR decoding header. Not a valid 'TelemetryHeader' message. "
              "Full message dump below:")
        print(bytes_to_string(message))
        return

    # Check the encoding value is correct
    ENCODING = 0x87654321
    if header.encoding != ENCODING:
        print("Invalid 'encoding' value {:#x} (expected {:#x})".format(
            header.encoding, ENCODING))
        return

    # Print the message header
    json_dict = {}
    if args.json_dump:
        # Convert the protobuf into a dictionary in preparation for dumping
        # it as JSON.
        json_dict = proto_to_dict(header)
    else:
        print_gpb_compact_hdr(header)

    # Loop over the tables within the message, printing either just the first
    # row or all rows depending on the args specified

    for t, entry in enumerate(header.tables):
        schema_path = entry.policy_path
        if not args.json_dump:
            print(INDENT + "Schema Path:{}".format(schema_path))
            warning = ""
            if not args.print_all:
                warning = "(Only first row displayed)"
            print(INDENT + "# Rows:{} {}".format(len(entry.row), warning))

        if not schema_path in decoder_dict.keys():
            print(INDENT + "No decoder available")
            if args.json_dump:
                json_dict["tables"][t]["row"][0] = "<No decoder available>"
        else:
            for i, row in enumerate(entry.row):
                row_msg = decoder_dict[schema_path]()
                try:
                    row_msg.ParseFromString(row)
                    if args.json_dump:
                        # Replace the bytes in the 'row' field with a decoded
                        # dict
                        table = json_dict["tables"][t]
                        table["row"][i] = proto_to_dict(row_msg)
                    else:
                        print(INDENT * 2 + "Row {}:".format(i))
                        print_gpb_compact_msg(row_msg, 2, args)
                        print("")
                except Exception as e:
                    print("ERROR decoding row. Not a valid GPB message. Full "
                          "message dump below: {}".format(e))
                    print(bytes_to_string(row))

                if not args.print_all and not args.json_dump:
                    break

    if args.json_dump:
        print(json.dumps(json_dict))
DECODE_FN_MAP = {
    FieldDescriptor.TYPE_DOUBLE: float,
    FieldDescriptor.TYPE_FLOAT: float,
    FieldDescriptor.TYPE_INT32: int,
    FieldDescriptor.TYPE_INT64: long,
    FieldDescriptor.TYPE_UINT32: int,
    FieldDescriptor.TYPE_UINT64: long,
    FieldDescriptor.TYPE_SINT32: int,
    FieldDescriptor.TYPE_SINT64: long,
    FieldDescriptor.TYPE_FIXED32: int,
    FieldDescriptor.TYPE_FIXED64: long,
    FieldDescriptor.TYPE_SFIXED32: int,
    FieldDescriptor.TYPE_SFIXED64: long,
    FieldDescriptor.TYPE_BOOL: bool,
    FieldDescriptor.TYPE_STRING: unicode,
    FieldDescriptor.TYPE_BYTES: lambda b: bytes_to_string(b),
    FieldDescriptor.TYPE_ENUM: int,
}


def field_type_to_fn(msg, field):
    if field.type == FieldDescriptor.TYPE_MESSAGE:
        # For embedded messages recursively call this function. If it is
        # a repeated field return a list
        result = lambda msg: proto_to_dict(msg)
    elif field.type in DECODE_FN_MAP:
        result = DECODE_FN_MAP[field.type]
    else:
        raise TypeError("Field %s.%s has unrecognised type id %d" %
                        (msg.__class__.__name__, field.name, field.type))
    return result