chromeos-dbus-bindings: Add D-Bus signature parser

Add a utility for converting a D-Bus signature into the C++
typename equivalent.

BUG=chromium:404505
TEST=New unit test

Change-Id: I05b51014741f001089dfcbbab6eb1a0b6f96d888
Reviewed-on: https://chromium-review.googlesource.com/216491
Reviewed-by: Paul Stewart <[email protected]>
Commit-Queue: Paul Stewart <[email protected]>
Tested-by: Paul Stewart <[email protected]>
diff --git a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
index 2e1ddc2..cdcf525 100644
--- a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
+++ b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
@@ -23,6 +23,7 @@
       'target_name': 'libchromeos-dbus-bindings',
       'type': 'static_library',
       'sources': [
+        'dbus_signature.cc',
         'method_name_generator.cc',
         'xml_interface_parser.cc',
       ],
@@ -30,7 +31,10 @@
         'exported_deps': [
           'expat',
         ],
-        'deps': ['<@(exported_deps)'],
+        'deps': [
+          'dbus-1',
+          '<@(exported_deps)',
+        ],
       },
       'all_dependent_settings': {
         'variables': {
@@ -66,6 +70,7 @@
           'includes': ['../../platform2/common-mk/common_test.gypi'],
           'sources': [
             'testrunner.cc',
+            'dbus_signature_unittest.cc',
             'method_name_generator_unittest.cc',
             'xml_interface_parser_unittest.cc',
           ],
diff --git a/chromeos-dbus-bindings/dbus_signature.cc b/chromeos-dbus-bindings/dbus_signature.cc
new file mode 100644
index 0000000..f3f62d8
--- /dev/null
+++ b/chromeos-dbus-bindings/dbus_signature.cc
@@ -0,0 +1,190 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos-dbus-bindings/dbus_signature.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <dbus/dbus-protocol.h>
+
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace chromeos_dbus_bindings {
+
+// static
+const char DbusSignature::kArrayTypename[] = "std::vector";
+const char DbusSignature::kBooleanTypename[] = "bool";
+const char DbusSignature::kByteTypename[] = "uint8_t";
+const char DbusSignature::kDefaultObjectPathTypename[] = "std::string";
+const char DbusSignature::kDictTypename[] = "std::map";
+const char DbusSignature::kDoubleTypename[] = "double";
+const char DbusSignature::kSigned16Typename[] = "int16_t";
+const char DbusSignature::kSigned32Typename[] = "int32_t";
+const char DbusSignature::kSigned64Typename[] = "int64_t";
+const char DbusSignature::kStringTypename[] = "std::string";
+const char DbusSignature::kUnixFdTypename[] = "int";
+const char DbusSignature::kUnsigned16Typename[] = "uint16_t";
+const char DbusSignature::kUnsigned32Typename[] = "uint32_t";
+const char DbusSignature::kUnsigned64Typename[] = "uint64_t";
+const char DbusSignature::kVariantTypename[] = "chromeos::Any";
+
+DbusSignature::DbusSignature()
+    : object_path_typename_(kDefaultObjectPathTypename) {}
+
+bool DbusSignature::Parse(const string& signature, string* output) {
+  string::const_iterator end;
+  if (!GetTypenameForSignature(
+          signature.begin(), signature.end(), &end, output)) {
+    LOG(ERROR) << "Parse failed for signature " << signature;
+    return false;
+  }
+  if (end != signature.end()) {
+    LOG(WARNING) << "A portion of signature " << signature
+                 << " is left unparsed: " << string(end, signature.end());
+  }
+  return true;
+}
+
+bool DbusSignature::GetTypenameForSignature(
+    string::const_iterator signature,
+    string::const_iterator end,
+    string::const_iterator* next,
+    string* output) {
+  if (signature == end) {
+    LOG(ERROR) << "Signature is empty";
+    return false;
+  }
+
+  string::const_iterator cur = signature;
+  int signature_value = *cur++;
+  switch (signature_value) {
+    case DBUS_TYPE_ARRAY:
+      if (!GetArrayTypenameForSignature(cur, end, &cur, output)) {
+        return false;
+      }
+      break;
+
+    case DBUS_TYPE_BOOLEAN:
+      *output = kBooleanTypename;
+      break;
+
+    case DBUS_TYPE_BYTE:
+      *output = kByteTypename;
+      break;
+
+    case DBUS_TYPE_DOUBLE:
+      *output = kDoubleTypename;
+      break;
+
+    case DBUS_TYPE_OBJECT_PATH:
+      *output = object_path_typename_;
+      break;
+
+    case DBUS_TYPE_INT16:
+      *output = kSigned16Typename;
+      break;
+
+    case DBUS_TYPE_INT32:
+      *output = kSigned32Typename;
+      break;
+
+    case DBUS_TYPE_INT64:
+      *output = kSigned64Typename;
+      break;
+
+    case DBUS_TYPE_STRING:
+      *output = kStringTypename;
+      break;
+
+    case DBUS_TYPE_UNIX_FD:
+      *output = kUnixFdTypename;
+      break;
+
+    case DBUS_TYPE_UINT16:
+      *output = kUnsigned16Typename;
+      break;
+
+    case DBUS_TYPE_UINT32:
+      *output = kUnsigned32Typename;
+      break;
+
+    case DBUS_TYPE_UINT64:
+      *output = kUnsigned64Typename;
+      break;
+
+    case DBUS_TYPE_VARIANT:
+      *output = kVariantTypename;
+      break;
+
+    default:
+      LOG(ERROR) << "Unexpected token " << *signature;
+      return false;
+  }
+
+  if (next) {
+    *next = cur;
+  }
+
+  return true;
+}
+
+bool DbusSignature::GetArrayTypenameForSignature(
+    string::const_iterator signature,
+    string::const_iterator end,
+    string::const_iterator* next,
+    string* output) {
+  string::const_iterator cur = signature;
+  if (cur == end) {
+    LOG(ERROR) << "At end of string while reading array parameter";
+    return false;
+  }
+
+  if (*cur == DBUS_DICT_ENTRY_BEGIN_CHAR) {
+    vector<string> children;
+    ++cur;
+    while (cur != end && *cur != DBUS_DICT_ENTRY_END_CHAR) {
+      children.emplace_back();
+      if (!GetTypenameForSignature(cur, end, &cur, &children.back())) {
+        LOG(ERROR) << "Unable to decode child elements starting at "
+                   << string(cur, end);
+        return false;
+      }
+    }
+    if (cur == end) {
+      LOG(ERROR) << "At end of string while processing dict "
+                 << "starting at " << string(signature, end);
+      return false;
+    }
+
+    DCHECK_EQ(DBUS_DICT_ENTRY_END_CHAR, *cur);
+    ++cur;
+
+    if (children.size() != 2) {
+      LOG(ERROR) << "Dict entry contains " << children.size()
+                 << " members starting at " << string(signature, end)
+                 << " where only 2 children is valid.";
+      return false;
+    }
+    *output = StringPrintf("%s<%s,%s>", kDictTypename,
+                           children[0].c_str(), children[1].c_str());
+  } else {
+    string child;
+    if (!GetTypenameForSignature(cur, end, &cur, &child)) {
+      LOG(ERROR) << "Unable to decode child element starting at "
+                 << string(cur, end);
+      return false;
+    }
+    *output = StringPrintf("%s<%s>", kArrayTypename, child.c_str());
+  }
+
+  if (next) {
+    *next = cur;
+  }
+
+  return true;
+}
+
+}  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/dbus_signature.h b/chromeos-dbus-bindings/dbus_signature.h
new file mode 100644
index 0000000..707d3a6
--- /dev/null
+++ b/chromeos-dbus-bindings/dbus_signature.h
@@ -0,0 +1,78 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_
+#define CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_dbus_bindings {
+
+class DbusSignature {
+ public:
+  DbusSignature();
+  virtual ~DbusSignature() = default;
+
+  // Returns a C++ typename in |output| for a D-Bus signature in |signature|
+  // and returns true on success.  Returns false otherwise.
+  bool Parse(const std::string& signature, std::string* output);
+
+  void set_object_path_typename(const std::string& object_path_typename) {
+    object_path_typename_ = object_path_typename;
+  }
+
+ private:
+  friend class DbusSignatureTest;
+  FRIEND_TEST(DbusSignatureTest, DefaultObjectPathTypename);
+  FRIEND_TEST(DbusSignatureTest, ParseSuccesses);
+
+  // Typenames are C++ syntax types.
+  static const char kArrayTypename[];
+  static const char kBooleanTypename[];
+  static const char kByteTypename[];
+  static const char kDefaultObjectPathTypename[];
+  static const char kDictTypename[];
+  static const char kDoubleTypename[];
+  static const char kSigned16Typename[];
+  static const char kSigned32Typename[];
+  static const char kSigned64Typename[];
+  static const char kStringTypename[];
+  static const char kUnixFdTypename[];
+  static const char kUnsigned16Typename[];
+  static const char kUnsigned32Typename[];
+  static const char kUnsigned64Typename[];
+  static const char kVariantTypename[];
+
+  // Returns the C++ type name for the next D-Bus signature in the string at
+  // |signature| in |output|, as well as the next position within the string
+  // that parsing should continue |next|.  It is not an error to pass a
+  // pointer to |signature| or nullptr as |next|.  Returns true on success.
+  bool GetTypenameForSignature(std::string::const_iterator signature,
+                               std::string::const_iterator end,
+                               std::string::const_iterator* next,
+                               std::string* output);
+
+  // Utility task for GetTypenameForSignature() which handles array objects
+  // and decodes them into a map or vector depending on the encoded sub-elements
+  // in the array.  The arguments and return values are the same
+  // as GetTypenameForSignature().
+  bool GetArrayTypenameForSignature(std::string::const_iterator signature,
+                                    std::string::const_iterator end,
+                                    std::string::const_iterator* next,
+                                    std::string* output);
+
+
+  // The C++ typename to be used for D-Bus object pathnames.
+  std::string object_path_typename_;
+
+  DISALLOW_COPY_AND_ASSIGN(DbusSignature);
+};
+
+}  // namespace chromeos_dbus_bindings
+
+#endif  // CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_
diff --git a/chromeos-dbus-bindings/dbus_signature_unittest.cc b/chromeos-dbus-bindings/dbus_signature_unittest.cc
new file mode 100644
index 0000000..f8a6d46
--- /dev/null
+++ b/chromeos-dbus-bindings/dbus_signature_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos-dbus-bindings/dbus_signature.h"
+
+#include <map>
+#include <string>
+
+#include <dbus/dbus-protocol.h>
+#include <gtest/gtest.h>
+
+using std::map;
+using std::string;
+using testing::Test;
+
+namespace chromeos_dbus_bindings {
+
+namespace {
+
+// Failing signatures.
+const char kEmptySignature[] = "";
+const char kEmptyDictSignature[] = "a{}";
+const char kMissingArraryParameterSignature[] = "a";
+const char kMissingArraryParameterInnerSignature[] = "a{sa}i";
+const char kOrphanDictSignature[] = "a{s{i}}";
+const char kTooFewDictMembersSignature[] = "a{s}";
+const char kTooManyDictMembersSignature[] = "a{sa{i}u}";
+const char kUnclosedDictOuterSignature[] = "a{s";
+const char kUnclosedDictInnerSignature[] = "a{a{u}";
+const char kUnexpectedCloseSignature[] = "a}i{";
+const char kUnknownSignature[] = "al";
+
+// Succeeding signatures.
+const char kBoolArraySignature[] = "ab";
+const char kByteArraySignature[] = "ay";
+const char kByteArrayArraySignature[] = "aay";
+const char kObjectArraySignature[] = "ao";
+const char kObjectDictBlobSignature[] = "a{oa{sa{sv}}}";
+const char kObjectNameDictSignature[] = "a{os}";
+const char kStringArraySignature[] = "as";
+const char kStringStringDictSignature[] = "a{ss}";
+const char kStringStringStringDictSignature[] = "a{sa{ss}}";
+const char kStringStringVariantDictSignature[] = "a{sa{sv}}";
+const char kStringVariantDictSignature[] = "a{sv}";
+const char kStringVariantDictWithTrailingSignature[] = "a{sv}NoneOfThisParses";
+const char kUnsigned64ArraySignature[] = "at";
+
+// Corresponding typenames for signatures above.
+const char kBoolArrayTypename[] = "std::vector<bool>";
+const char kByteArrayTypename[] = "std::vector<uint8_t>";
+const char kByteArrayArrayTypename[] = "std::vector<std::vector<uint8_t>>";
+const char kObjectArrayTypename[] = "std::vector<ObjectPathType>";
+const char kObjectDictBlobTypename[] =
+    "std::map<ObjectPathType,std::map<std::string,std::map"
+    "<std::string,chromeos::Any>>>";
+const char kObjectNameDictTypename[] = "std::map<ObjectPathType,std::string>";
+const char kStringArrayTypename[] = "std::vector<std::string>";
+const char kStringStringDictTypename[] = "std::map<std::string,std::string>";
+const char kStringStringStringDictTypename[] =
+    "std::map<std::string,std::map<std::string,std::string>>";
+const char kStringStringVariantDictTypename[] =
+    "std::map<std::string,std::map<std::string,chromeos::Any>>";
+const char kStringVariantDictTypename[] = "std::map<std::string,chromeos::Any>";
+const char kUnsigned64ArrayTypename[] = "std::vector<uint64_t>";
+
+// Define an object type name to disambiguate the typenames above.
+const char kObjectPathTypename[] = "ObjectPathType";
+
+}  // namespace
+
+class DbusSignatureTest : public Test {
+ protected:
+  DbusSignature signature_;
+};
+
+TEST_F(DbusSignatureTest, ParseFailures) {
+  for (const auto& failing_string : { kEmptySignature,
+                                      kEmptyDictSignature,
+                                      kMissingArraryParameterSignature,
+                                      kMissingArraryParameterInnerSignature,
+                                      kOrphanDictSignature,
+                                      kTooFewDictMembersSignature,
+                                      kTooManyDictMembersSignature,
+                                      kUnclosedDictOuterSignature,
+                                      kUnclosedDictInnerSignature,
+                                      kUnexpectedCloseSignature,
+                                      kUnknownSignature }) {
+    string unused_output;
+    EXPECT_FALSE(signature_.Parse(failing_string, &unused_output))
+        << "Expected signature " << failing_string
+        << " to fail but it succeeded";
+  }
+}
+
+TEST_F(DbusSignatureTest, DefaultObjectPathTypename) {
+  // The ParseSuccesses test below overrides the default object typename, so
+  // test the default behavior separately.
+  string output;
+  EXPECT_TRUE(signature_.Parse(DBUS_TYPE_OBJECT_PATH_AS_STRING, &output));
+  EXPECT_EQ(DbusSignature::kDefaultObjectPathTypename, output);
+}
+
+TEST_F(DbusSignatureTest, ParseSuccesses) {
+  const map<string, string> parse_values {
+    // Simple types.
+    { DBUS_TYPE_BOOLEAN_AS_STRING, DbusSignature::kBooleanTypename },
+    { DBUS_TYPE_BYTE_AS_STRING, DbusSignature::kByteTypename },
+    { DBUS_TYPE_DOUBLE_AS_STRING, DbusSignature::kDoubleTypename },
+    { DBUS_TYPE_OBJECT_PATH_AS_STRING, kObjectPathTypename },
+    { DBUS_TYPE_INT16_AS_STRING, DbusSignature::kSigned16Typename },
+    { DBUS_TYPE_INT32_AS_STRING, DbusSignature::kSigned32Typename },
+    { DBUS_TYPE_INT64_AS_STRING, DbusSignature::kSigned64Typename },
+    { DBUS_TYPE_STRING_AS_STRING, DbusSignature::kStringTypename },
+    { DBUS_TYPE_UNIX_FD_AS_STRING, DbusSignature::kUnixFdTypename },
+    { DBUS_TYPE_UINT16_AS_STRING, DbusSignature::kUnsigned16Typename },
+    { DBUS_TYPE_UINT32_AS_STRING, DbusSignature::kUnsigned32Typename },
+    { DBUS_TYPE_UINT64_AS_STRING, DbusSignature::kUnsigned64Typename },
+    { DBUS_TYPE_VARIANT_AS_STRING, DbusSignature::kVariantTypename },
+
+    // Complex types.
+    { kBoolArraySignature, kBoolArrayTypename },
+    { kByteArraySignature, kByteArrayTypename },
+    { kByteArrayArraySignature, kByteArrayArrayTypename },
+    { kObjectArraySignature, kObjectArrayTypename },
+    { kObjectDictBlobSignature, kObjectDictBlobTypename },
+    { kObjectNameDictSignature, kObjectNameDictTypename },
+    { kStringArraySignature, kStringArrayTypename },
+    { kStringStringDictSignature, kStringStringDictTypename },
+    { kStringStringStringDictSignature, kStringStringStringDictTypename },
+    { kStringStringVariantDictSignature, kStringStringVariantDictTypename },
+    { kStringVariantDictSignature, kStringVariantDictTypename },
+    { kStringVariantDictWithTrailingSignature, kStringVariantDictTypename },
+    { kUnsigned64ArraySignature, kUnsigned64ArrayTypename },
+  };
+  signature_.set_object_path_typename(kObjectPathTypename);
+  for (const auto& parse_test : parse_values) {
+    string output;
+    EXPECT_TRUE(signature_.Parse(parse_test.first, &output))
+        << "Expected signature " << parse_test.first
+        << " to succeed but it failed.";
+    EXPECT_EQ(parse_test.second, output)
+        << "Expected typename for " << parse_test.first
+        << " to be " << parse_test.second << " but instead it was " << output;
+  }
+}
+
+}  // namespace chromeos_dbus_bindings