Add GDBus introspection class 02/151502/2
authorsangwan.kwon <sangwan.kwon@samsung.com>
Mon, 4 Sep 2017 09:18:50 +0000 (05:18 -0400)
committersangwan kwon <sangwan.kwon@samsung.com>
Fri, 29 Sep 2017 01:21:14 +0000 (01:21 +0000)
Change-Id: Icdf25b14552cbf37e414596e54863cde7d7d5d15
Signed-off-by: sangwan.kwon <sangwan.kwon@samsung.com>
include/klay/dbus/introspection.h [new file with mode: 0644]
src/CMakeLists.txt
src/dbus/introspection.cpp [new file with mode: 0644]
test/dbus.cpp

diff --git a/include/klay/dbus/introspection.h b/include/klay/dbus/introspection.h
new file mode 100644 (file)
index 0000000..5960876
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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
+ *  limitations under the License
+ */
+
+#ifndef __RUNTIME_DBUS_INTROSPECTION_H__
+#define __RUNTIME_DBUS_INTROSPECTION_H__
+
+#include <gio/gio.h>
+
+#include <string>
+
+namespace dbus {
+
+using BusNode = GDBusNodeInfo*;
+using Interface = GDBusInterfaceInfo*;
+using Method = GDBusMethodInfo*;
+using Signal = GDBusSignalInfo*;
+using Property = GDBusPropertyInfo*;
+
+class Introspection {
+public:
+       explicit Introspection(const std::string &xmlData);
+       virtual ~Introspection(void);
+
+       Introspection(const Introspection &) = delete;
+       Introspection(Introspection &&) = delete;
+       Introspection &operator=(const Introspection &) = delete;
+       Introspection &operator=(Introspection &&) = delete;
+
+       Interface getInterface(const std::string &name) const;
+       Method getMethod(const std::string &interfaceName,
+                                        const std::string &methodName) const;
+       Signal getSignal(const std::string &interfaceName,
+                                        const std::string &signalName) const;
+       Property getProperty(const std::string &interfaceName,
+                                                const std::string &propertyName) const;
+
+       std::string getXmlData(unsigned int indent = 0);
+
+       void addInterface(const std::string &name);
+       void addMethod(const std::string &interfaceName, const std::string &methodData);
+       void addSignal(const std::string &interfaceName, const std::string &signalData);
+       void addProperty(const std::string &interfaceName, const std::string &propertyData);
+
+private:
+       BusNode getBusNode(const std::string &xmlData);
+       void update(void);
+
+       std::string getInterfaceBeginTag(const std::string &name) const;
+       std::string getInterfaceEndTag(void) const;
+
+       void addInternalData(const std::string &interfaceName, const std::string &data);
+
+       void checkDataFormat(const std::string &data) const;
+
+       BusNode busNode;
+       std::string xmlData;
+};
+
+} // namespace dbus
+
+#endif //! __RUNTIME_DBUS_INTROSPECTION_H__
index f4d682868f5741e9721f9990bd10d26e1629a89a..6073882b5ff136af042e68e1695d5eadbaabd71b 100755 (executable)
@@ -46,6 +46,7 @@ SET (KLAY_SOURCES             ${KLAY_SRC}/error.cpp
                                                ${KLAY_SRC}/dbus/error.cpp
                                                ${KLAY_SRC}/dbus/variant.cpp
                                                ${KLAY_SRC}/dbus/connection.cpp
+                                               ${KLAY_SRC}/dbus/introspection.cpp
                                                ${KLAY_SRC}/audit/logger.cpp
                                                ${KLAY_SRC}/audit/logger-core.cpp
                                                ${KLAY_SRC}/audit/null-sink.cpp
diff --git a/src/dbus/introspection.cpp b/src/dbus/introspection.cpp
new file mode 100644 (file)
index 0000000..182fe13
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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
+ *  limitations under the License
+ */
+
+#include <klay/dbus/introspection.h>
+#include <klay/dbus/error.h>
+#include <klay/exception.h>
+
+#include <memory>
+#include <functional>
+
+namespace dbus {
+
+Introspection::Introspection(const std::string &xmlData) :
+       busNode(getBusNode(xmlData)), xmlData(getXmlData())
+{
+}
+
+Introspection::~Introspection(void)
+{
+       if (busNode)
+               ::g_dbus_node_info_unref(busNode);
+}
+
+BusNode Introspection::getBusNode(const std::string &xmlData)
+{
+       if (xmlData.empty())
+               throw runtime::Exception("Invalid argument.");
+
+       dbus::Error error;
+       auto busNode = ::g_dbus_node_info_new_for_xml(xmlData.c_str(), &error);
+       if (busNode == nullptr || error)
+               throw runtime::Exception("Failed to get BusNode.");
+
+       return busNode;
+}
+
+Interface Introspection::getInterface(const std::string &name) const
+{
+       if (name.empty())
+               throw runtime::Exception("Invalid argument.");
+
+       return ::g_dbus_node_info_lookup_interface(busNode, name.c_str());
+}
+
+Method Introspection::getMethod(const std::string &interfaceName,
+                                                               const std::string &methodName) const
+{
+       auto interface = getInterface(interfaceName);
+       if (interface == nullptr || methodName.empty())
+               throw runtime::Exception("Invalid argument.");
+
+       return ::g_dbus_interface_info_lookup_method(interface, methodName.c_str());
+}
+
+Signal Introspection::getSignal(const std::string &interfaceName,
+                                                               const std::string &signalName) const
+{
+       auto interface = getInterface(interfaceName);
+       if (interface == nullptr || signalName.empty())
+               throw runtime::Exception("Invalid argument.");
+
+       return ::g_dbus_interface_info_lookup_signal(interface, signalName.c_str());
+}
+
+Property Introspection::getProperty(const std::string &interfaceName,
+                                                                       const std::string &propertyName) const
+{
+       auto interface = getInterface(interfaceName);
+       if (interface == nullptr || propertyName.empty())
+               throw runtime::Exception("Invalid argument.");
+
+       return ::g_dbus_interface_info_lookup_property(interface, propertyName.c_str());
+}
+
+std::string Introspection::getXmlData(unsigned int indent)
+{
+       using ScopedGString = std::unique_ptr<GString, std::function<void(GString*)>>;
+       ScopedGString buf(g_string_new(""), [](GString *ptr)
+               {
+                       ::g_string_free(ptr, TRUE);
+               });
+       if (buf == nullptr)
+               throw runtime::Exception("Out of memory.");
+
+       ::g_dbus_node_info_generate_xml(busNode, indent, buf.get());
+       return std::string(buf->str);
+}
+
+void Introspection::addInterface(const std::string &name)
+{
+       if (getInterface(name) != nullptr)
+               throw runtime::Exception("Interface is already exist:" + name);
+
+       std::size_t offset = xmlData.find("</node>");
+       if (offset == std::string::npos)
+               throw runtime::Exception("Failed to find </node>.");
+
+       xmlData.insert(offset, getInterfaceBeginTag(name) + getInterfaceEndTag());
+       update();
+}
+
+std::string Introspection::getInterfaceBeginTag(const std::string &name) const
+{
+       const std::string token = "@NAME@";
+       std::string iTemplate = "<interface name=\"@NAME@\">";
+
+       iTemplate.replace(iTemplate.find(token), token.length(), name);
+
+       return iTemplate;
+}
+
+std::string Introspection::getInterfaceEndTag(void) const
+{
+       return "</interface>";
+}
+
+// TODO: Check more strict.
+void Introspection::checkDataFormat(const std::string &data) const
+{
+       if (data.empty() || data.length() < 3)
+               throw runtime::Exception("Invalid argument.");
+
+       const std::string beginChar = "<";
+       if (data.compare(0, beginChar.length(), beginChar) != 0)
+               throw runtime::Exception("Xml data should be begin as: " + beginChar);
+
+       const std::string endChar = "/>";
+       if (data.compare(data.length() - endChar.length(), endChar.length(), endChar) != 0 &&
+               data.find("/") == std::string::npos)
+               throw runtime::Exception("Xml data should be contain '/' or end as: " + endChar);
+}
+
+void Introspection::addMethod(const std::string &interfaceName,
+                                                         const std::string &methodData)
+{
+       addInternalData(interfaceName, methodData);
+}
+
+void Introspection::addSignal(const std::string &interfaceName,
+                                                         const std::string &signalData)
+{
+       addInternalData(interfaceName, signalData);
+}
+
+void Introspection::addProperty(const std::string &interfaceName,
+                                                               const std::string &propertyData)
+{
+       addInternalData(interfaceName, propertyData);
+}
+
+void Introspection::addInternalData(const std::string &interfaceName,
+                                                                       const std::string &data)
+{
+       checkDataFormat(data);
+
+       std::string iTemplate = getInterfaceBeginTag(interfaceName);
+       std::size_t offset = xmlData.find(iTemplate);
+       if (offset == std::string::npos)
+               throw runtime::Exception("Failed to find interface xml node: " + interfaceName);
+
+       xmlData.insert(offset + iTemplate.length() + 1, data);
+       update();
+}
+
+void Introspection::update(void)
+{
+       if (busNode)
+               ::g_dbus_node_info_unref(busNode);
+
+       busNode = getBusNode(xmlData);
+       xmlData = getXmlData();
+}
+
+} // namespace dbus
index 3cf2213f2e94f3b642e8b2bcb4c0d328cd7da816..a10f3095d0f71fca46121f5a32ee607d31af503f 100644 (file)
@@ -21,6 +21,7 @@
 #include <klay/audit/logger.h>
 #include <klay/dbus/variant.h>
 #include <klay/dbus/connection.h>
+#include <klay/dbus/introspection.h>
 #include <klay/latch.h>
 
 #include <klay/testbench.h>
@@ -33,6 +34,27 @@ const std::string TESTSVC_METHOD_NOOP    = "Noop";
 const std::string TESTSVC_METHOD_PROCESS = "Process";
 const std::string TESTSVC_METHOD_THROW   = "Throw";
 const std::string TESTSVC_SIGNAL_NOTIFY  = "Notify";
+const std::string TESTSVC_NOT_EXIST      = "None";
+
+const std::string TESTSVC_INTERFACE_NEW_NAME = "NewInterface";
+const std::string TESTSVC_METHOD_NEW_NAME    = "NewMethod";
+const std::string TESTSVC_SIGNAL_NEW_NAME    = "NewSignal";
+const std::string TESTSVC_PROPERTY_NEW_NAME  = "NewProperty";
+
+const std::string TESTSVC_METHOD_NEW_DATA =
+       "<method name='" + TESTSVC_METHOD_NEW_NAME + "'>"
+       "  <arg type='s' name='argument' direction='in'/>"
+       "  <arg type='s' name='response' direction='out'/>"
+       "</method>";
+const std::string TESTSVC_SIGNAL_NEW_DATA =
+       "<signal name='" + TESTSVC_SIGNAL_NEW_NAME + "'>"
+       "  <arg type='s' name='argument'/>"
+       "</signal>";
+const std::string TESTSVC_PROPERTY_NEW_DATA =
+       "<property name='" + TESTSVC_PROPERTY_NEW_NAME + "' type='y' access='readwrite'/>";
+
+const std::string TESTSVC_WRONG_DATA_TYPE1 = "signal'/>";
+const std::string TESTSVC_WRONG_DATA_TYPE2 = "<signal>";
 
 const std::string manifest =
        "<node>"
@@ -46,7 +68,7 @@ const std::string manifest =
        "                       <arg type='i' name='argument' direction='in'/>"
        "               </method>"
        "               <signal name='" + TESTSVC_SIGNAL_NOTIFY + "'>"
-       "                       <arg type='s' name='arument'/>"
+       "                       <arg type='s' name='argument'/>"
        "               </signal>"
        "       </interface>"
        "</node>";
@@ -147,3 +169,159 @@ TESTCASE(DbusRegisterObjectTest)
                ERROR(KSINK, e.what());
        }
 }
+
+TESTCASE(DBusIntrospectionGetterTest)
+{
+       try {
+               dbus::Introspection is(manifest);
+
+               dbus::Interface interface = is.getInterface(TESTSVC_NOT_EXIST);
+               TEST_EXPECT(true, interface == nullptr);
+
+               interface = is.getInterface(TESTSVC_INTERFACE);
+               TEST_EXPECT(true, interface != nullptr);
+
+               dbus::Method method = is.getMethod(TESTSVC_INTERFACE, TESTSVC_NOT_EXIST);
+               TEST_EXPECT(true, method == nullptr);
+
+               method = is.getMethod(TESTSVC_INTERFACE, TESTSVC_METHOD_THROW);
+               TEST_EXPECT(true, method != nullptr);
+
+               dbus::Signal signal = is.getSignal(TESTSVC_INTERFACE, TESTSVC_NOT_EXIST);
+               TEST_EXPECT(true, signal == nullptr);
+
+               signal = is.getSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NOTIFY);
+               TEST_EXPECT(true, signal != nullptr);
+
+               signal = is.getSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NEW_NAME);
+               TEST_EXPECT(true, signal == nullptr);
+
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionDuplicatedInterface)
+{
+       try {
+               dbus::Introspection is(manifest);
+               is.addInterface(TESTSVC_INTERFACE);
+               TEST_EXPECT(true, false);
+       } catch (std::exception& e) {
+               TEST_EXPECT(true, true);
+       }
+}
+
+TESTCASE(DBusIntrospectionAddInterfaceAndMethod)
+{
+       try {
+               dbus::Introspection is(manifest);
+               dbus::Interface interface = is.getInterface(TESTSVC_INTERFACE_NEW_NAME);
+               TEST_EXPECT(true, interface == nullptr);
+
+               is.addInterface(TESTSVC_INTERFACE_NEW_NAME);
+
+               interface = is.getInterface(TESTSVC_INTERFACE_NEW_NAME);
+               TEST_EXPECT(true, interface != nullptr);
+
+               dbus::Method method = is.getMethod(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_METHOD_NEW_NAME);
+               TEST_EXPECT(true, method == nullptr);
+
+               is.addMethod(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_METHOD_NEW_DATA);
+
+               method = is.getMethod(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_METHOD_NEW_NAME);
+               TEST_EXPECT(true, method != nullptr);
+
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionAddMethod)
+{
+       try {
+               dbus::Introspection is(manifest);
+               dbus::Method method = is.getMethod(TESTSVC_INTERFACE, TESTSVC_METHOD_NEW_NAME);
+               TEST_EXPECT(true, method == nullptr);
+
+               is.addMethod(TESTSVC_INTERFACE, TESTSVC_METHOD_NEW_DATA);
+
+               method = is.getMethod(TESTSVC_INTERFACE, TESTSVC_METHOD_NEW_NAME);
+               TEST_EXPECT(true, method != nullptr);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionAddSignal)
+{
+       try {
+               dbus::Introspection is(manifest);
+               dbus::Signal signal = is.getSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NEW_NAME);
+               TEST_EXPECT(true, signal == nullptr);
+
+               is.addSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NEW_DATA);
+
+               signal = is.getSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NEW_NAME);
+               TEST_EXPECT(true, signal != nullptr);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionAddProperty)
+{
+       try {
+               dbus::Introspection is(manifest);
+               dbus::Property property = is.getProperty(TESTSVC_INTERFACE, TESTSVC_PROPERTY_NEW_NAME);
+               TEST_EXPECT(true, property == nullptr);
+
+               is.addProperty(TESTSVC_INTERFACE, TESTSVC_PROPERTY_NEW_DATA);
+
+               property = is.getProperty(TESTSVC_INTERFACE, TESTSVC_PROPERTY_NEW_NAME);
+               TEST_EXPECT(true, property != nullptr);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionAddTotal)
+{
+       try {
+               dbus::Introspection is(manifest);
+               is.addMethod(TESTSVC_INTERFACE, TESTSVC_METHOD_NEW_DATA);
+               is.addSignal(TESTSVC_INTERFACE, TESTSVC_SIGNAL_NEW_DATA);
+               is.addProperty(TESTSVC_INTERFACE, TESTSVC_PROPERTY_NEW_DATA);
+
+               is.addInterface(TESTSVC_INTERFACE_NEW_NAME);
+               is.addMethod(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_METHOD_NEW_DATA);
+               is.addSignal(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_SIGNAL_NEW_DATA);
+               is.addProperty(TESTSVC_INTERFACE_NEW_NAME, TESTSVC_PROPERTY_NEW_DATA);
+       } catch (std::exception& e) {
+               ERROR(KSINK, e.what());
+               TEST_EXPECT(true, false);
+       }
+}
+
+TESTCASE(DBusIntrospectionCheckDataFormat)
+{
+       try {
+               dbus::Introspection is(manifest);
+               is.addMethod(TESTSVC_INTERFACE, TESTSVC_WRONG_DATA_TYPE1);
+               TEST_EXPECT(true, false);
+       } catch (std::exception& e) {
+               TEST_EXPECT(true, true);
+       }
+       try {
+               dbus::Introspection is(manifest);
+               is.addMethod(TESTSVC_INTERFACE, TESTSVC_WRONG_DATA_TYPE2);
+               TEST_EXPECT(true, false);
+       } catch (std::exception& e) {
+               TEST_EXPECT(true, true);
+       }
+}