>From 09053b9c2a54aa4dae0637ead9de2e1ba7dc5397 Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@redhat.com>
Date: Thu, 9 Jan 2014 23:04:45 +0100
Subject: [PATCH 1/6] sbus: Add meta data structures and code generator

These metadata structures hold the information about all the
details of a DBus interface. They are typically generated from
the canonical XML form of the DBus interface, although they
may also be hand crafted.

Add some handy functions for looking up methods, props, signals,
in the matadata of an interface. Currently metadata is just done
by looking through an array. If performance becomes an issue (ie:
very large interfaces), it would be really easy to sort things
and use bsearch().

Later commits will include some definitions using this metadata
and related functions.

DBus interfaces are defined here:

http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format

The introspection data format has become the standard way to represent a
DBus interface. For many examples see /usr/share/dbus-1/interfaces/ on a
typical linux machine.

A word about annotations. These are extra flags or values that can be
assigned to anything. So far, the codegen supports this annotation:

org.freedesktop.DBus.GLib.CSymbol
 - An annotation specified in the specification that tells us what C symbol
   to generate for a given interface or method. By default the codegen will
   build up a symbol name from the DBus name.

It is possible to confuse the code generator into producing invalid
C code (with strange method names, for example), but the C compiler
catches such silliness right away.

Add tests testing basic features of the codegen and poking through
the metadata it creats. Also test the metadata lookup functions

An example of the XML interface definitions can be found at:

  $(srcdir)/src/tests/sbus_codegen-defs.xml

And an example of the generated header can be found here:

  $(builddir)/src/tests/sbus_codegen-defs.h
---
 Makefile.am                     |  44 +++-
 src/sbus/sbus_codegen           | 509 ++++++++++++++++++++++++++++++++++++++++
 src/sbus/sssd_dbus_meta.c       |  64 +++++
 src/sbus/sssd_dbus_meta.h       |  82 +++++++
 src/tests/sbus_codegen-defs.xml |  45 ++++
 src/tests/sbus_codegen-tests.c  | 166 +++++++++++++
 6 files changed, 909 insertions(+), 1 deletion(-)
 create mode 100755 src/sbus/sbus_codegen
 create mode 100644 src/sbus/sssd_dbus_meta.c
 create mode 100644 src/sbus/sssd_dbus_meta.h
 create mode 100755 src/tests/sbus_codegen-defs.xml
 create mode 100644 src/tests/sbus_codegen-tests.c

diff --git a/Makefile.am b/Makefile.am
index 2733d4f..d4fb197 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -136,7 +136,8 @@ if HAVE_CHECK
         debug-tests \
         ipa_hbac-tests \
         sss_idmap-tests \
-        responder_socket_access-tests
+        responder_socket_access-tests \
+        sbus_codegen-tests
 
 if BUILD_SSH
     non_interactive_check_based_tests += sysdb_ssh-tests
@@ -277,6 +278,7 @@ AM_CPPFLAGS = \
     -I$(srcdir)/include \
     -I$(srcdir)/src/sss_client \
     -I$(srcdir)/src \
+    -I$(builddir)/src \
     -Iinclude \
     -I. \
     $(POPT_CFLAGS) \
@@ -311,6 +313,10 @@ AM_CPPFLAGS = \
 
 EXTRA_DIST = build/config.rpath
 
+BUILT_SOURCES =
+
+CLEAN_FILES =
+
 SSSD_RESPONDER_OBJ = \
     src/responder/common/negcache.c \
     src/responder/common/responder_cmd.c \
@@ -441,6 +447,7 @@ dist_noinst_HEADERS = \
     src/responder/ssh/sshsrv_private.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
+    src/sbus/sssd_dbus_meta.h \
     src/sbus/sssd_dbus_private.h \
     src/db/sysdb.h \
     src/db/sysdb_sudo.h \
@@ -570,6 +577,7 @@ libsss_util_la_SOURCES = \
     src/sbus/sbus_client.c \
     src/sbus/sssd_dbus_common.c \
     src/sbus/sssd_dbus_connection.c \
+    src/sbus/sssd_dbus_meta.c \
     src/sbus/sssd_dbus_server.c \
     src/util/util.c \
     src/util/memory.c \
@@ -647,6 +655,21 @@ include_HEADERS = \
     src/sss_client/idmap/sss_nss_idmap.h
 
 ####################
+# Sbus Codegen     #
+####################
+
+EXTRA_DIST += src/sbus/sbus_codegen
+
+SBUS_CODEGEN=$(srcdir)/src/sbus/sbus_codegen
+
+SUFFIXES = .xml
+
+.xml.h:
+	$(SBUS_CODEGEN) --mode=header --output=$@ $<
+.xml.c:
+	$(SBUS_CODEGEN) --mode=source --output=$@ $<
+
+####################
 # Program Binaries #
 ####################
 sssd_SOURCES = \
@@ -1274,6 +1297,25 @@ krb5_child_test_LDADD = \
     $(SSSD_INTERNAL_LTLIBS) \
     libsss_test_common.la
 
+SBUS_TEST_CODEGEN_OBJ = \
+   src/tests/sbus_codegen-defs.c \
+   src/tests/sbus_codegen-defs.h
+EXTRA_DIST += \
+   src/tests/sbus_codegen-defs.xml
+CLEAN_FILES += \
+   $(SBUS_TEST_CODEGEN_OBJ)
+BUILT_SOURCES += \
+    $(SBUS_TEST_CODEGEN_OBJ)
+sbus_codegen_tests_SOURCES = \
+   src/tests/sbus_codegen-tests.c \
+   $(SBUS_TEST_CODEGEN_OBJ)
+sbus_codegen_tests_CFLAGS = \
+    $(CHECK_CFLAGS)
+sbus_codegen_tests_LDADD = \
+    $(SSSD_INTERNAL_LTLIBS) \
+    $(SSSD_LIBS) \
+    $(CHECK_LIBS)
+
 if HAVE_CMOCKA
 
 TEST_MOCK_RESP_OBJ = \
diff --git a/src/sbus/sbus_codegen b/src/sbus/sbus_codegen
new file mode 100755
index 0000000..bf1698d
--- /dev/null
+++ b/src/sbus/sbus_codegen
@@ -0,0 +1,509 @@
+#!/bin/python
+
+#
+# Authors:
+#     Stef Walter <stefw@redhat.com>
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# Some parser code from GLib
+#
+# Copyright (C) 2008-2011 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General
+# Public License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+# Boston, MA 02111-1307, USA.
+#
+# Portions by: David Zeuthen <davidz@redhat.com>
+#
+
+#
+# DBus interfaces are defined here:
+#
+# http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
+#
+# The introspection data format has become the standard way to represent a
+# DBus interface. For many examples see /usr/share/dbus-1/interfaces/ on a
+# typical linux machine.
+#
+# A word about annotations. These are extra flags or values that can be
+# assigned to anything. So far, the codegen supports this annotation:
+#
+# org.freedesktop.DBus.GLib.CSymbol
+#  - An annotation specified in the specification that tells us what C symbol
+#    to generate for a given interface or method. By default the codegen will
+#    build up a symbol name from the DBus name.
+#
+
+import optparse
+import os
+import re
+import StringIO
+import sys
+import xml.parsers.expat
+
+# -----------------------------------------------------------------------------
+# Objects
+
+class DBusXmlException(Exception):
+    line = 0
+    file = None
+
+    # Lets us print problems like a compiler would
+    def __str__(self):
+        message = Exception.__str__(self)
+        if self.file and self.line:
+            return "%s:%d: %s" % (self.file, self.line, message)
+        elif self.file:
+            return "%s: %s" % (self.file, message)
+        else:
+            return message
+
+class Base:
+    def __init__(self, name):
+        if not name:
+            raise DBusXmlException('No name on element')
+        self.name = name
+        self.annotations = { }
+    def c_name(self):
+        return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol", self.name)
+
+class Arg(Base):
+    def __init__(self, method, name, signature):
+        Base.__init__(self, name)
+        self.method = method
+        self.signature = signature
+
+class Method(Base):
+    def __init__(self, iface, name):
+        Base.__init__(self, name)
+        self.iface = iface
+        self.in_args = []
+        self.out_args = []
+    def fq_c_name(self):
+        return "%s_%s" % (self.iface.c_name(), self.c_name())
+    def use_raw_handler(self):
+        anno = 'org.freedesktop.sssd.RawHandler'
+        return self.annotations.get(anno, self.iface.annotations.get(anno)) == 'true'
+
+class Signal(Base):
+    def __init__(self, iface, name):
+        Base.__init__(self, name)
+        self.iface = iface
+        self.args = []
+    def fq_c_name(self):
+        return "%s_%s" % (self.iface.c_name(), self.c_name())
+
+class Property(Base):
+    def __init__(self, iface, name, signature, access):
+        Base.__init__(self, name)
+        self.iface = iface
+        self.signature = signature
+        self.readable = False
+        self.writable = False
+        if access == 'readwrite':
+            self.readable = True
+            self.writable = True
+        elif access == 'read':
+            self.readable = True
+        elif access == 'write':
+            self.writable = True
+        else:
+            raise DBusXmlException('Invalid access type %s'%self.access)
+    def fq_c_name(self):
+        return "%s_%s" % (self.iface.c_name(), self.c_name())
+
+class Interface(Base):
+    def __init__(self, name):
+        Base.__init__(self, name)
+        self.methods = []
+        self.signals = []
+        self.properties = []
+    def c_name(self):
+        return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol",
+                                    self.name.replace(".", "_"))
+
+# -----------------------------------------------------------------------------
+# Code Generation
+
+class CodegenException(Exception):
+    pass
+
+def source_args(parent, args, suffix):
+    print "/* arguments for %s.%s */" % (parent.iface.name, parent.name)
+    print "const struct sbus_arg_meta %s%s[] = {" % (parent.fq_c_name(), suffix)
+    for arg in args:
+        print "    { \"%s\", \"%s\" }," % (arg.name, arg.signature)
+    print "    { NULL, }"
+    print "};"
+    print
+
+def source_methods(iface, methods):
+    for meth in methods:
+        if meth.in_args:
+            source_args(meth, meth.in_args, "__in")
+        if meth.out_args:
+            source_args(meth, meth.out_args, "__out")
+
+    print "/* methods for %s */" % iface.name
+    print "const struct sbus_method_meta %s__methods[] = {" % iface.c_name()
+    for meth in methods:
+        print "    {"
+        print "        \"%s\", /* name */" % meth.name
+        if meth.in_args:
+            print "        %s__in," % meth.fq_c_name()
+        else:
+            print "        NULL, /* no in_args */"
+        if meth.out_args:
+            print "        %s__out," % meth.fq_c_name()
+        else:
+            print "        NULL, /* no out_args */"
+        print "    },"
+    print "    { NULL, }"
+    print "};"
+    print
+
+def source_signals(iface, signals):
+    for sig in iface.signals:
+        if sig.args:
+            source_args(sig, sig.args, "__args")
+
+    print "/* signals for %s */" % iface.name
+    print "const struct sbus_signal_meta %s__signals[] = {" % iface.c_name()
+    for sig in signals:
+        print "    {"
+        print "        \"%s\", /* name */" % sig.name
+        if sig.args:
+            print "        %s__args" % sig.fq_c_name()
+        else:
+            print "        NULL, /* no args */"
+        print "    },"
+    print "    { NULL, }"
+    print "};"
+    print
+
+def source_properties(iface, properties):
+    print "/* property info for %s */" % iface.name
+    print "const struct sbus_property_meta %s__properties[] = {" % iface.c_name()
+    for prop in properties:
+        print "    {"
+        print "        \"%s\", /* name */" % prop.name
+        print "        \"%s\", /* signature */" % prop.signature
+        if prop.readable and prop.writable:
+            print "        SBUS_PROPERTY_READABLE | SBUS_PROPERTY_WRITABLE,"
+        elif prop.readable:
+            print "        SBUS_PROPERTY_READABLE,"
+        elif prop.writable:
+            print "        SBUS_PROPERTY_WRITABLE,"
+        else:
+            assert False, "should not be reached"
+        print "    },"
+    print "    { NULL, }"
+    print "};"
+    print
+
+def header_interface(iface):
+    print "/* interface info for %s */" % iface.name
+    print "extern const struct sbus_interface_meta %s_meta;" % iface.c_name();
+    print
+
+def source_interface(iface):
+    print "/* interface info for %s */" % iface.name
+    print "const struct sbus_interface_meta %s_meta = {" % iface.c_name();
+    print "    \"%s\", /* name */" % iface.name
+    if iface.methods:
+        print "    %s__methods, " % iface.c_name()
+    else:
+        print "    NULL, /* no methods */"
+    if iface.signals:
+        print "    %s__signals, " % iface.c_name()
+    else:
+        print "    NULL, /* no signals */"
+    if iface.properties:
+        print "    %s__properties" % iface.c_name()
+    else:
+        print "    NULL, /* no propetries */"
+    print "};"
+    print
+
+def generate_source(ifaces, filename):
+    basename = os.path.basename(filename)
+
+    print "/* The following definitions are auto-generated from %s */" % basename
+    print
+
+    print "#include \"util/util.h\""
+    print "#include \"sbus/sssd_dbus.h\""
+    print "#include \"sbus/sssd_dbus_meta.h\""
+    print
+
+    for iface in ifaces:
+
+        # The methods
+        if iface.methods:
+            source_methods(iface, iface.methods)
+
+        # The signals array
+        if iface.signals:
+            source_signals(iface, iface.signals)
+
+        # The properties array
+        if iface.properties:
+            source_properties(iface, iface.properties)
+
+        # The sbus_interface structure
+        source_interface(iface)
+
+def generate_header(ifaces, filename):
+    basename = os.path.basename(filename)
+    guard = "__%s__" % re.sub(r'([^_A-Z0-9])', "_", basename.upper())
+
+    print "/* The following declarations are auto-generated from %s */" % basename
+    print
+    print "#ifndef %s" % guard
+    print "#define %s" % guard
+    print
+    print "#include \"sbus/sssd_dbus.h\""
+    print
+
+    print "/* ------------------------------------------------------------------------"
+    print " * DBus Interface Metadata"
+    print " *"
+    print " * These structure definitions are filled in with the information about"
+    print " * the interfaces, methods, properties and so on."
+    print " *"
+    print " * The actual definitions are found in the accompanying C file next"
+    print " * to this header."
+    print " */"
+    print
+
+    for iface in ifaces:
+        header_interface(iface)
+
+    print
+    print "#endif /* %s */" % guard
+
+# -----------------------------------------------------------------------------
+# XML Interface Parsing
+
+STATE_TOP = 'top'
+STATE_NODE = 'node'
+STATE_INTERFACE = 'interface'
+STATE_METHOD = 'method'
+STATE_SIGNAL = 'signal'
+STATE_PROPERTY = 'property'
+STATE_ARG = 'arg'
+STATE_ANNOTATION = 'annotation'
+STATE_IGNORED = 'ignored'
+
+def expect_attr(attrs, name):
+    if name not in attrs:
+        raise DBusXmlException("Missing attribute '%s'" % name)
+    if attrs[name] == "":
+        raise DBusXmlException("Empty attribute '%s'" % name)
+    return attrs[name]
+
+class DBusXMLParser:
+    def __init__(self, filename):
+        parser = xml.parsers.expat.ParserCreate()
+        parser.CommentHandler = self.handle_comment
+        parser.CharacterDataHandler = self.handle_char_data
+        parser.StartElementHandler = self.handle_start_element
+        parser.EndElementHandler = self.handle_end_element
+
+        self.parsed_interfaces = []
+        self.cur_object = None
+
+        self.state = STATE_TOP
+        self.state_stack = []
+        self.cur_object = None
+        self.cur_object_stack = []
+        self.arg_count = 0
+
+        try:
+            with open(filename, "r") as f:
+                parser.ParseFile(f)
+        except DBusXmlException, ex:
+            ex.line = parser.CurrentLineNumber
+            ex.file = filename
+            raise
+        except xml.parsers.expat.ExpatError, ex:
+            exc = DBusXmlException(str(ex))
+            exc.line = ex.lineno
+            exc.file = filename
+            raise exc
+
+    def handle_comment(self, data):
+        pass
+
+    def handle_char_data(self, data):
+        pass
+
+    def handle_start_element(self, name, attrs):
+        old_state = self.state
+        old_cur_object = self.cur_object
+        if self.state == STATE_IGNORED:
+            self.state = STATE_IGNORED
+        elif self.cur_object and name == STATE_ANNOTATION:
+                val = attrs.get('value', '')
+                self.cur_object.annotations[expect_attr(attrs, 'name')] = val
+                self.state = STATE_IGNORED
+        elif self.state == STATE_TOP:
+            if name == STATE_NODE:
+                self.state = STATE_NODE
+            else:
+                self.state = STATE_IGNORED
+        elif self.state == STATE_NODE:
+            if name == STATE_INTERFACE:
+                self.state = STATE_INTERFACE
+                iface = Interface(expect_attr(attrs, 'name'))
+                self.cur_object = iface
+                self.parsed_interfaces.append(iface)
+            else:
+                self.state = STATE_IGNORED
+
+        elif self.state == STATE_INTERFACE:
+            if name == STATE_METHOD:
+                self.state = STATE_METHOD
+                method = Method(self.cur_object, expect_attr(attrs, 'name'))
+                self.cur_object.methods.append(method)
+                self.cur_object = method
+                self.arg_count = 0
+            elif name == STATE_SIGNAL:
+                self.state = STATE_SIGNAL
+                signal = Signal(self.cur_object, expect_attr(attrs, 'name'))
+                self.cur_object.signals.append(signal)
+                self.cur_object = signal
+                self.arg_count = 0
+            elif name == STATE_PROPERTY:
+                self.state = STATE_PROPERTY
+                prop = Property(self.cur_object,
+                                expect_attr(attrs, 'name'),
+                                expect_attr(attrs, 'type'),
+                                expect_attr(attrs, 'access'))
+                self.cur_object.properties.append(prop)
+                self.cur_object = prop
+            else:
+                self.state = STATE_IGNORED
+
+        elif self.state == STATE_METHOD:
+            if name == STATE_ARG:
+                self.state = STATE_ARG
+                arg = Arg(self.cur_object,
+                          expect_attr(attrs, 'name'),
+                          expect_attr(attrs, 'type'))
+                direction = attrs.get('direction', 'in')
+                if direction == 'in':
+                    self.cur_object.in_args.append(arg)
+                elif direction == 'out':
+                    self.cur_object.out_args.append(arg)
+                else:
+                    raise DBusXmlException('Invalid direction "%s"' % direction)
+                self.cur_object = arg
+            else:
+                self.state = STATE_IGNORED
+
+        elif self.state == STATE_SIGNAL:
+            if name == STATE_ARG:
+                self.state = STATE_ARG
+                arg = Arg(self.cur_object,
+                          expect_attr(attrs, 'name'),
+                          expect_attr(attrs, 'type'))
+                self.cur_object.args.append(arg)
+                self.cur_object = arg
+            else:
+                self.state = STATE_IGNORED
+
+        elif self.state == STATE_PROPERTY:
+            self.state = STATE_IGNORED
+
+        elif self.state == STATE_ARG:
+            self.state = STATE_IGNORED
+
+        else:
+            assert False, 'Unhandled state "%s" while entering element with name "%s"' % (self.state, name)
+
+        self.state_stack.append(old_state)
+        self.cur_object_stack.append(old_cur_object)
+
+    def handle_end_element(self, name):
+        self.state = self.state_stack.pop()
+        self.cur_object = self.cur_object_stack.pop()
+
+def parse_options():
+    parser = optparse.OptionParser("usage: %prog [options] introspect.xml ...")
+    parser.set_description("sbus_codegen generates sbus interface structures \
+                            from standard XML Introspect data.")
+    parser.add_option("--mode",
+                      dest="mode", default="header",
+                      help="'header' or 'source' (default: header)",
+                      metavar="MODE")
+    parser.add_option("--output",
+                      dest="output", default=None,
+                      help="Set output file name (default: stdout)",
+                      metavar="FILE")
+    (options, args) = parser.parse_args()
+
+    if not args:
+        print >> sys.stderr, "sbus_codegen: no input file specified"
+        sys.exit(2)
+
+    if options.mode not in ["header", "source"]:
+        print >> sys.stderr, "sbus_codegen: specify --mode=header or --mode=source"
+
+    return options, args
+
+def main():
+    options, args = parse_options()
+
+    if options.output:
+        sys.stdout = buf = StringIO.StringIO()
+
+    for filename in args:
+        parser = DBusXMLParser(filename)
+
+        if options.mode == "header":
+            generate_header(parser.parsed_interfaces, filename)
+        elif options.mode == "source":
+            generate_source(parser.parsed_interfaces, filename)
+        else:
+            assert False, "should not be reached"
+
+    # Write output at end to be nice to 'make'
+    if options.output:
+        output = open(options.output, "w")
+        output.write(buf.getvalue())
+        output.close()
+
+if __name__ == "__main__":
+    try:
+        main()
+    except DBusXmlException, ex:
+        print >> sys.stderr, str(ex)
+        sys.exit(1)
diff --git a/src/sbus/sssd_dbus_meta.c b/src/sbus/sssd_dbus_meta.c
new file mode 100644
index 0000000..3776c68
--- /dev/null
+++ b/src/sbus/sssd_dbus_meta.c
@@ -0,0 +1,64 @@
+/*
+    Authors:
+        Stef Walter <stefw@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "sbus/sssd_dbus_meta.h"
+
+const struct sbus_method_meta *
+sbus_meta_find_method (const struct sbus_interface_meta *interface,
+                       const char *method_name)
+{
+    const struct sbus_method_meta *method;
+
+    for (method = interface->methods; method && method->name; method++) {
+        if (strcmp (method_name, method->name) == 0)
+            return method;
+    }
+
+    return NULL;
+}
+
+const struct sbus_signal_meta *
+sbus_meta_find_signal (const struct sbus_interface_meta *interface,
+                       const char *signal_name)
+{
+    const struct sbus_signal_meta *signal;
+
+    for (signal = interface->signals; signal && signal->name; signal++) {
+        if (strcmp (signal_name, signal->name) == 0)
+            return signal;
+    }
+
+    return NULL;
+}
+
+const struct sbus_property_meta *
+sbus_meta_find_property (const struct sbus_interface_meta *interface,
+                         const char *property_name)
+{
+    const struct sbus_property_meta *property;
+
+    for (property = interface->properties; property && property->name; property++) {
+        if (strcmp (property_name, property->name) == 0)
+            return property;
+    }
+
+    return NULL;
+}
diff --git a/src/sbus/sssd_dbus_meta.h b/src/sbus/sssd_dbus_meta.h
new file mode 100644
index 0000000..17ea7a5
--- /dev/null
+++ b/src/sbus/sssd_dbus_meta.h
@@ -0,0 +1,82 @@
+/*
+    Authors:
+        Stef Walter <stefw@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SSSD_DBUS_META_H_
+#define _SSSD_DBUS_META_H_
+
+/*
+ * Interface metadata
+ *
+ * For arrays, the last item in each array will have a
+ * NULL .name field
+ *
+ * Typically these structs will be generated by sbus_codegen
+ * from canonical XML interface data:
+ *
+ * http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
+ */
+
+struct sbus_arg_meta {
+    const char *name;
+    const char *type;
+};
+
+struct sbus_method_meta {
+    const char *name;
+    const struct sbus_arg_meta *in_args;
+    const struct sbus_arg_meta *out_args;
+};
+
+enum {
+    SBUS_PROPERTY_READABLE = 1 << 0,
+    SBUS_PROPERTY_WRITABLE = 1 << 1
+};
+
+struct sbus_property_meta {
+    const char *name;
+    const char *type;
+    int flags;
+};
+
+struct sbus_signal_meta {
+    const char *name;
+    const struct sbus_arg_meta *args;
+};
+
+struct sbus_interface_meta {
+    const char *name;
+    const struct sbus_method_meta *methods;
+    const struct sbus_signal_meta *signals;
+    const struct sbus_property_meta *properties;
+};
+
+const struct sbus_method_meta *
+sbus_meta_find_method (const struct sbus_interface_meta *interface,
+                       const char *method_name);
+
+const struct sbus_signal_meta *
+sbus_meta_find_signal (const struct sbus_interface_meta *interface,
+                       const char *signal_name);
+
+const struct sbus_property_meta *
+sbus_meta_find_property (const struct sbus_interface_meta *interface,
+                         const char *property_name);
+
+#endif /* _SSSD_DBUS_META_H_ */
diff --git a/src/tests/sbus_codegen-defs.xml b/src/tests/sbus_codegen-defs.xml
new file mode 100755
index 0000000..0def358
--- /dev/null
+++ b/src/tests/sbus_codegen-defs.xml
@@ -0,0 +1,45 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+    <!--
+      This file should exercise as many aspects of the sbus_codegen as
+      possible. See sbus_codegen_test.c for verification.
+    -->
+
+    <!--
+      This is an interface, it will get a sbus_interface_meta struct.
+      Its name will be com_planetexpress_Ship__meta, since no c symbol
+      is specified.
+    -->
+    <interface name="com.planetexpress.Ship">
+        <!-- A property -->
+        <property name="Color" type="s" access="read"/>
+
+        <!-- A method with two in and one out argument -->
+        <method name="MoveUniverse">
+            <!-- This is a boolean arg -->
+            <arg name="smoothly" type="b" direction="in"/>
+            <!-- This is an uint32 arg -->
+            <arg name="speed_factor" type="u" direction="in"/>
+            <!-- This is a string arg -->
+            <arg name="where_we_crashed" type="s" direction="out"/>
+        </method>
+
+        <!-- A signal with one argument -->
+        <signal name="BecameSentient">
+            <arg name="gender" type="s"/>
+        </signal>
+    </interface>
+
+    <!--
+      Another interface. It's C name will be test_pilot, since we've overridden
+      the c symbol name.
+    -->
+    <interface name="com.planetexpress.Pilot">
+        <annotation value="test_pilot" name="org.freedesktop.DBus.GLib.CSymbol"/>
+
+        <!-- A property -->
+        <property name="FullName" type="s" access="readwrite"/>
+    </interface>
+
+</node>
diff --git a/src/tests/sbus_codegen-tests.c b/src/tests/sbus_codegen-tests.c
new file mode 100644
index 0000000..8878d9b
--- /dev/null
+++ b/src/tests/sbus_codegen-tests.c
@@ -0,0 +1,166 @@
+/*
+   SSSD
+
+   Reference counting tests.
+
+   Authors:
+        Stef Walter <stefw@redhat.com>
+
+   Copyright (C) Red Hat, Inc 2014
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+
+#include "sbus/sssd_dbus_meta.h"
+#include "tests/sbus_codegen-defs.h"
+
+static const struct sbus_arg_meta *
+find_arg (const struct sbus_arg_meta *args,
+          const char *name)
+{
+    const struct sbus_arg_meta *arg;
+    for (arg = args; arg->name != NULL; arg++) {
+        if (strcmp (arg->name, name) == 0)
+            return arg;
+    }
+
+    return NULL;
+}
+
+START_TEST(test_interfaces)
+{
+    ck_assert_str_eq(com_planetexpress_Ship_meta.name, "com.planetexpress.Ship");
+    ck_assert(com_planetexpress_Ship_meta.methods != NULL);
+    ck_assert(com_planetexpress_Ship_meta.signals != NULL);
+    ck_assert(com_planetexpress_Ship_meta.properties != NULL);
+
+    /* Explicit C Symbol */
+    ck_assert_str_eq(test_pilot_meta.name, "com.planetexpress.Pilot");
+    ck_assert(test_pilot_meta.methods == NULL); /* no methods */
+    ck_assert(test_pilot_meta.signals == NULL); /* no signals */
+    ck_assert(test_pilot_meta.properties != NULL);
+
+}
+END_TEST
+
+START_TEST(test_methods)
+{
+    const struct sbus_method_meta *method;
+    const struct sbus_arg_meta *arg;
+
+    method = sbus_meta_find_method(&com_planetexpress_Ship_meta, "MoveUniverse");
+    ck_assert(method != NULL);
+    ck_assert_str_eq(method->name, "MoveUniverse");
+    ck_assert(method->in_args != NULL);
+    ck_assert(method->out_args != NULL);
+
+    arg = find_arg(method->in_args, "smoothly");
+    ck_assert(arg != NULL);
+    ck_assert_str_eq(arg->name, "smoothly");
+    ck_assert_str_eq(arg->type, "b");
+
+    arg = find_arg(method->out_args, "where_we_crashed");
+    ck_assert(arg != NULL);
+    ck_assert_str_eq(arg->name, "where_we_crashed");
+    ck_assert_str_eq(arg->type, "s");
+}
+END_TEST
+
+START_TEST(test_properties)
+{
+    const struct sbus_property_meta *prop;
+
+    prop = sbus_meta_find_property(&com_planetexpress_Ship_meta, "Color");
+    ck_assert(prop != NULL);
+    ck_assert_str_eq(prop->name, "Color");
+    ck_assert_str_eq(prop->type, "s");
+    ck_assert_int_eq(prop->flags, SBUS_PROPERTY_READABLE);
+}
+END_TEST
+
+START_TEST(test_signals)
+{
+    const struct sbus_signal_meta *signal;
+    const struct sbus_arg_meta *arg;
+
+    signal = sbus_meta_find_signal(&com_planetexpress_Ship_meta, "BecameSentient");
+    ck_assert(signal != NULL);
+    ck_assert_str_eq(signal->name, "BecameSentient");
+    ck_assert(signal->args != NULL);
+
+    arg = find_arg(signal->args, "gender");
+    ck_assert(arg != NULL);
+    ck_assert_str_eq(arg->name, "gender");
+    ck_assert_str_eq(arg->type, "s");
+}
+END_TEST
+
+Suite *create_suite(void)
+{
+    Suite *s = suite_create("sbus_codegen");
+
+    TCase *tc = tcase_create("defs");
+
+    /* Do some testing */
+    tcase_add_test(tc, test_interfaces);
+    tcase_add_test(tc, test_methods);
+    tcase_add_test(tc, test_properties);
+    tcase_add_test(tc, test_signals);
+
+    /* Add all test cases to the test suite */
+    suite_add_tcase(s, tc);
+
+    return s;
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    int failure_count;
+    Suite *suite;
+    SRunner *sr;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        POPT_TABLEEND
+    };
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    suite = create_suite();
+    sr = srunner_create(suite);
+    srunner_set_fork_status(sr, CK_FORK);
+    /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+    srunner_run_all(sr, CK_ENV);
+    failure_count = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (failure_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
-- 
1.8.4.2

