move the library to ONF

Change-Id: I383437e2934ce04cc1a7dc332134f7308991776f
diff --git a/grpc_robot/tools/protobuf_to_dict.py b/grpc_robot/tools/protobuf_to_dict.py
new file mode 100644
index 0000000..19ebf1a
--- /dev/null
+++ b/grpc_robot/tools/protobuf_to_dict.py
@@ -0,0 +1,281 @@
+# Copyright 2020-present Open Networking Foundation
+# Original copyright 2020-present ADTRAN, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+
+# This is free and unencumbered software released into the public domain
+# by its author, Ben Hodgson <ben@benhodgson.com>.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognise copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# For more information, please refer to <http://unlicense.org/>
+
+
+# -*- coding:utf-8 -*-
+
+# copied from https://github.com/kaporzhu/protobuf-to-dict
+# all credits to this script go to Kapor Zhu (kapor.zhu@gmail.com)
+#
+# Comments:
+# - need a fix for bug: "Use enum_label when setting the default value if use_enum_labels is true" (line 95)
+# - try to convert timestaps to a human readable format
+
+import base64
+
+import six
+from datetime import datetime
+
+from google.protobuf.message import Message
+from google.protobuf.descriptor import FieldDescriptor
+
+
+__all__ = ["protobuf_to_dict", "TYPE_CALLABLE_MAP", "dict_to_protobuf",
+           "REVERSE_TYPE_CALLABLE_MAP"]
+
+
+EXTENSION_CONTAINER = '___X'
+
+
+TYPE_CALLABLE_MAP = {
+    FieldDescriptor.TYPE_DOUBLE: float,
+    FieldDescriptor.TYPE_FLOAT: float,
+    FieldDescriptor.TYPE_INT32: int,
+    FieldDescriptor.TYPE_INT64: int if six.PY3 else six.integer_types[1],
+    FieldDescriptor.TYPE_UINT32: int,
+    FieldDescriptor.TYPE_UINT64: int if six.PY3 else six.integer_types[1],
+    FieldDescriptor.TYPE_SINT32: int,
+    FieldDescriptor.TYPE_SINT64: int if six.PY3 else six.integer_types[1],
+    FieldDescriptor.TYPE_FIXED32: int,
+    FieldDescriptor.TYPE_FIXED64: int if six.PY3 else six.integer_types[1],
+    FieldDescriptor.TYPE_SFIXED32: int,
+    FieldDescriptor.TYPE_SFIXED64: int if six.PY3 else six.integer_types[1],
+    FieldDescriptor.TYPE_BOOL: bool,
+    FieldDescriptor.TYPE_STRING: six.text_type,
+    FieldDescriptor.TYPE_BYTES: six.binary_type,
+    FieldDescriptor.TYPE_ENUM: int,
+}
+
+
+def repeated(type_callable):
+    return lambda value_list: [type_callable(value) for value in value_list]
+
+
+def enum_label_name(field, value):
+    return field.enum_type.values_by_number[int(value)].name
+
+
+def _is_map_entry(field):
+    return (field.type == FieldDescriptor.TYPE_MESSAGE and
+            field.message_type.has_options and
+            field.message_type.GetOptions().map_entry)
+
+
+def protobuf_to_dict(pb, type_callable_map=TYPE_CALLABLE_MAP,
+                     use_enum_labels=False,
+                     including_default_value_fields=False,
+                     human_readable_timestamps=False):
+    result_dict = {}
+    extensions = {}
+    for field, value in pb.ListFields():
+        if field.message_type and field.message_type.has_options and field.message_type.GetOptions().map_entry:
+            result_dict[field.name] = dict()
+            value_field = field.message_type.fields_by_name['value']
+            type_callable = _get_field_value_adaptor(
+                pb, value_field, type_callable_map,
+                use_enum_labels, including_default_value_fields)
+            for k, v in value.items():
+                result_dict[field.name][k] = type_callable(v)
+            continue
+        type_callable = _get_field_value_adaptor(pb, field, type_callable_map,
+                                                 use_enum_labels,
+                                                 including_default_value_fields,
+                                                 human_readable_timestamps)
+        if field.label == FieldDescriptor.LABEL_REPEATED:
+            type_callable = repeated(type_callable)
+
+        if field.is_extension:
+            extensions[str(field.number)] = type_callable(value)
+            continue
+
+        if field.full_name in ['google.protobuf.Timestamp.seconds'] and human_readable_timestamps:
+            result_dict[field.name] = datetime.fromtimestamp(type_callable(value)).strftime('%Y-%m-%d %H:%M:%S.%f')
+        else:
+            result_dict[field.name] = type_callable(value)
+
+    # Serialize default value if including_default_value_fields is True.
+    if including_default_value_fields:
+        for field in pb.DESCRIPTOR.fields:
+            # Singular message fields and oneof fields will not be affected.
+            if ((
+                    field.label != FieldDescriptor.LABEL_REPEATED and
+                    field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE) or
+                    field.containing_oneof):
+                continue
+            if field.name in result_dict:
+                # Skip the field which has been serailized already.
+                continue
+            if _is_map_entry(field):
+                result_dict[field.name] = {}
+            else:
+                if use_enum_labels and field.type == FieldDescriptor.TYPE_ENUM:
+                    result_dict[field.name] = enum_label_name(field, field.default_value)
+                else:
+                    result_dict[field.name] = field.default_value
+
+    if extensions:
+        result_dict[EXTENSION_CONTAINER] = extensions
+    return result_dict
+
+
+def _get_field_value_adaptor(pb, field, type_callable_map=TYPE_CALLABLE_MAP,
+                             use_enum_labels=False,
+                             including_default_value_fields=False,
+                             human_readable_timestamps=False):
+    if field.type == FieldDescriptor.TYPE_MESSAGE:
+        # recursively encode protobuf sub-message
+        return lambda pb: protobuf_to_dict(
+            pb, type_callable_map=type_callable_map,
+            use_enum_labels=use_enum_labels,
+            including_default_value_fields=including_default_value_fields,
+            human_readable_timestamps=human_readable_timestamps
+        )
+
+    if use_enum_labels and field.type == FieldDescriptor.TYPE_ENUM:
+        return lambda value: enum_label_name(field, value)
+
+    if field.type in type_callable_map:
+        return type_callable_map[field.type]
+
+    raise TypeError("Field %s.%s has unrecognised type id %d" % (
+        pb.__class__.__name__, field.name, field.type))
+
+
+REVERSE_TYPE_CALLABLE_MAP = {
+}
+
+
+def dict_to_protobuf(pb_klass_or_instance, values, type_callable_map=REVERSE_TYPE_CALLABLE_MAP, strict=True, ignore_none=False):
+    """Populates a protobuf model from a dictionary.
+
+    :param pb_klass_or_instance: a protobuf message class, or an protobuf instance
+    :type pb_klass_or_instance: a type or instance of a subclass of google.protobuf.message.Message
+    :param dict values: a dictionary of values. Repeated and nested values are
+       fully supported.
+    :param dict type_callable_map: a mapping of protobuf types to callables for setting
+       values on the target instance.
+    :param bool strict: complain if keys in the map are not fields on the message.
+    :param bool strict: ignore None-values of fields, treat them as empty field
+    """
+    if isinstance(pb_klass_or_instance, Message):
+        instance = pb_klass_or_instance
+    else:
+        instance = pb_klass_or_instance()
+    return _dict_to_protobuf(instance, values, type_callable_map, strict, ignore_none)
+
+
+def _get_field_mapping(pb, dict_value, strict):
+    field_mapping = []
+    for key, value in dict_value.items():
+        if key == EXTENSION_CONTAINER:
+            continue
+        if key not in pb.DESCRIPTOR.fields_by_name:
+            if strict:
+                raise KeyError("%s does not have a field called %s" % (pb, key))
+            continue
+        field_mapping.append((pb.DESCRIPTOR.fields_by_name[key], value, getattr(pb, key, None)))
+
+    for ext_num, ext_val in dict_value.get(EXTENSION_CONTAINER, {}).items():
+        try:
+            ext_num = int(ext_num)
+        except ValueError:
+            raise ValueError("Extension keys must be integers.")
+        if ext_num not in pb._extensions_by_number:
+            if strict:
+                raise KeyError("%s does not have a extension with number %s. Perhaps you forgot to import it?" % (pb, key))
+            continue
+        ext_field = pb._extensions_by_number[ext_num]
+        pb_val = None
+        pb_val = pb.Extensions[ext_field]
+        field_mapping.append((ext_field, ext_val, pb_val))
+
+    return field_mapping
+
+
+def _dict_to_protobuf(pb, value, type_callable_map, strict, ignore_none):
+    fields = _get_field_mapping(pb, value, strict)
+
+    for field, input_value, pb_value in fields:
+        if ignore_none and input_value is None:
+            continue
+        if field.label == FieldDescriptor.LABEL_REPEATED:
+            if field.message_type and field.message_type.has_options and field.message_type.GetOptions().map_entry:
+                value_field = field.message_type.fields_by_name['value']
+                for key, value in input_value.items():
+                    if value_field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE:
+                        _dict_to_protobuf(getattr(pb, field.name)[key], value, type_callable_map, strict, ignore_none)
+                    else:
+                        getattr(pb, field.name)[key] = value
+                continue
+            for item in input_value:
+                if field.type == FieldDescriptor.TYPE_MESSAGE:
+                    m = pb_value.add()
+                    _dict_to_protobuf(m, item, type_callable_map, strict, ignore_none)
+                elif field.type == FieldDescriptor.TYPE_ENUM and isinstance(item, six.string_types):
+                    pb_value.append(_string_to_enum(field, item))
+                else:
+                    pb_value.append(item)
+            continue
+        if field.type == FieldDescriptor.TYPE_MESSAGE:
+            _dict_to_protobuf(pb_value, input_value, type_callable_map, strict, ignore_none)
+            continue
+
+        if field.type in type_callable_map:
+            input_value = type_callable_map[field.type](input_value)
+
+        if field.is_extension:
+            pb.Extensions[field] = input_value
+            continue
+
+        if field.type == FieldDescriptor.TYPE_ENUM and isinstance(input_value, six.string_types):
+            input_value = _string_to_enum(field, input_value)
+
+        setattr(pb, field.name, input_value)
+
+    return pb
+
+
+def _string_to_enum(field, input_value):
+    enum_dict = field.enum_type.values_by_name
+    try:
+        input_value = enum_dict[input_value].number
+    except KeyError:
+        raise KeyError("`%s` is not a valid value for field `%s`" % (input_value, field.name))
+    return input_value