aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Nettle <nettle.paul@gmail.com>2017-08-25 09:30:39 -0500
committerPaul Nettle <nettle.paul@gmail.com>2017-08-25 09:30:39 -0500
commit06f646aec4dbce64d28bae1be6111bd833f8e79e (patch)
treee98859f400af75fb43150c3d03bde83de9b77f79 /src
downloadgobbledegook-06f646aec4dbce64d28bae1be6111bd833f8e79e.tar.gz
gobbledegook-06f646aec4dbce64d28bae1be6111bd833f8e79e.tar.bz2
gobbledegook-06f646aec4dbce64d28bae1be6111bd833f8e79e.zip
Initial version 1.0
Diffstat (limited to 'src')
-rw-r--r--src/DBusInterface.cpp202
-rw-r--r--src/DBusInterface.h140
-rw-r--r--src/DBusMethod.cpp88
-rw-r--r--src/DBusMethod.h118
-rw-r--r--src/DBusObject.cpp286
-rw-r--r--src/DBusObject.h135
-rw-r--r--src/DBusObjectPath.h172
-rw-r--r--src/GattCharacteristic.cpp217
-rw-r--r--src/GattCharacteristic.h219
-rw-r--r--src/GattDescriptor.cpp176
-rw-r--r--src/GattDescriptor.h183
-rw-r--r--src/GattInterface.cpp123
-rw-r--r--src/GattInterface.h231
-rw-r--r--src/GattProperty.cpp185
-rw-r--r--src/GattProperty.h106
-rw-r--r--src/GattService.cpp93
-rw-r--r--src/GattService.h99
-rw-r--r--src/GattUuid.h282
-rw-r--r--src/Globals.h71
-rw-r--r--src/Gobbledegook.cpp433
-rw-r--r--src/HciAdapter.cpp384
-rw-r--r--src/HciAdapter.h138
-rw-r--r--src/HciSocket.cpp198
-rw-r--r--src/HciSocket.h90
-rw-r--r--src/Init.cpp1207
-rw-r--r--src/Init.h43
-rw-r--r--src/Logger.cpp200
-rw-r--r--src/Logger.h185
-rw-r--r--src/Makefile.am48
-rw-r--r--src/Makefile.in909
-rw-r--r--src/Mgmt.cpp339
-rw-r--r--src/Mgmt.h213
-rw-r--r--src/Server.cpp618
-rw-r--r--src/Server.h146
-rw-r--r--src/ServerUtils.cpp314
-rw-r--r--src/ServerUtils.h62
-rw-r--r--src/TickEvent.h149
-rw-r--r--src/Utils.cpp431
-rw-r--r--src/Utils.h212
-rw-r--r--src/standalone.cpp320
40 files changed, 9765 insertions, 0 deletions
diff --git a/src/DBusInterface.cpp b/src/DBusInterface.cpp
new file mode 100644
index 0000000..d96171e
--- /dev/null
+++ b/src/DBusInterface.cpp
@@ -0,0 +1,202 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is an abstraction layer for a D-Bus interface, the base class for all interfaces.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Not sure what a D-Bus Interface is? Well, chedk the Readme for resources, but here's the TL;DR:
+//
+// A D-Bus interface is a contract (similar to programming language interfaces.) An interface defines a set of methods and
+// properties for others to use.
+//
+// Interfaces are identified by their name, such as "org.freedesktop.DBus.Properties". In fact, if an object on the bus is found
+// to have that interface, then you know that it provides an interface to access its properties via the methods "Get", "GetAll" and
+// "Set". To see the details for this interface (and all of the D-Bus defined interfaces), see:
+//
+// https://dbus.freedesktop.org/doc/dbus-specification.html
+//
+// We're also interested in working with BlueZ which has their own set of interfaces. One example is "org.bluez.GattManager1" which
+// is the interface used to create and register GATT services with BlueZ.
+//
+// Remember, interfaces are not implementations; they're just contracts to provide an implementation. That means some interfaces
+// are intended for us to implement. One example is "org.bluez.GattService1" which defines the interface that we must conform to
+// so that others (likely BlueZ) can access our GATT service(s). For more information on these, have a look at:
+//
+// https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+//
+// Our interfaces also store a collection of events. Here, an event is much like a timer in modern UIs, which repeatedly fires
+// after a defined time. A practical example of an event would be a BLE server that provides a Battery service. By adding a timer
+// to the interface for this service, the server could wake up every minute to check the battery level and if it has changed, send
+// a notifications to clients over BLE with the new battery level. This saves a lot of additional code on the server's part.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "DBusInterface.h"
+#include "GattProperty.h"
+#include "DBusObject.h"
+#include "Logger.h"
+
+//
+// Construction
+//
+
+DBusInterface::DBusInterface(DBusObject &owner, const std::string &name)
+: owner(owner), name(name)
+{
+}
+
+DBusInterface::~DBusInterface()
+{
+}
+
+//
+// Interface name
+//
+
+// Returns the name of this interface (ex: "org.freedesktop.DBus.Properties")
+const std::string &DBusInterface::getName() const
+{
+ return name;
+}
+
+// Sets the name of the interface (ex: "org.freedesktop.DBus.Properties")
+DBusInterface &DBusInterface::setName(const std::string &name)
+{
+ this->name = name;
+ return *this;
+}
+
+//
+// Owner information
+//
+
+// Returns the owner (DBusObject) of this interface
+DBusObject &DBusInterface::getOwner() const
+{
+ return owner;
+}
+
+// Returns the path node of this interface's owner
+DBusObjectPath DBusInterface::getPathNode() const
+{
+ return owner.getPathNode();
+}
+
+// Returns the full path of this interface's owner
+DBusObjectPath DBusInterface::getPath() const
+{
+ return owner.getPath();
+}
+
+//
+// D-Bus interface methods
+//
+
+// Add a named method to this interface
+//
+// This method returns a reference to `this` in order to enable chaining inside the server description.
+DBusInterface &DBusInterface::addMethod(const std::string &name, const char *pInArgs[], const char *pOutArgs, DBusMethod::Callback callback)
+{
+ methods.push_back(DBusMethod(this, name, pInArgs, pOutArgs, callback));
+ return *this;
+}
+
+// Calls a named method on this interface
+//
+// This method returns false if the method could not be found, otherwise it returns true. Note that the return value is not related
+// to the result of the method call itself (methods do not return values.)
+//
+// NOTE: Subclasses are encouraged to override this method in order to support different callback types that are specific to
+// their subclass type.
+bool DBusInterface::callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const
+{
+ for (const DBusMethod &method : methods)
+ {
+ if (methodName == method.getName())
+ {
+ method.call<DBusInterface>(pConnection, getPath(), getName(), methodName, pParameters, pInvocation, pUserData);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Add an event to this interface
+//
+// For details on events, see TickEvent.cpp.
+//
+// This method returns a reference to `this` in order to enable chaining inside the server description.
+//
+// NOTE: Subclasses are encouraged to overload this method in order to support different callback types that are specific to
+// their subclass type. In addition, they should return their own type. This simplifies the server description by allowing
+// calls to chain.
+DBusInterface &DBusInterface::onEvent(int tickFrequency, void *pUserData, TickEvent::Callback callback)
+{
+ events.push_back(TickEvent(this, tickFrequency, callback, pUserData));
+ return *this;
+}
+
+// Ticks each event within this interface
+//
+// For details on events, see TickEvent.cpp.
+//
+// NOTE: Subclasses are encouraged to override this method in order to support different callback types that are specific to
+// their subclass type.
+void DBusInterface::tickEvents(GDBusConnection *pConnection, void *pUserData) const
+{
+ for (const TickEvent &event : events)
+ {
+ event.tick<DBusInterface>(getPath(), pConnection, pUserData);
+ }
+}
+
+// Internal method used to generate introspection XML used to describe our services on D-Bus
+std::string DBusInterface::generateIntrospectionXML(int depth) const
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ std::string xml = std::string();
+
+ if (methods.empty())
+ {
+ xml += prefix + "<interface name='" + getName() + "' />\n";
+ }
+ else
+ {
+ xml += prefix + "<interface name='" + getName() + "'>\n";
+
+ // Describe our methods
+ for (const DBusMethod &method : methods)
+ {
+ xml += method.generateIntrospectionXML(depth + 1);
+ }
+
+ xml += prefix + "</interface>\n";
+ }
+
+ return xml;
+}
diff --git a/src/DBusInterface.h b/src/DBusInterface.h
new file mode 100644
index 0000000..8598675
--- /dev/null
+++ b/src/DBusInterface.h
@@ -0,0 +1,140 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is an abstraction layer for a D-Bus interface, the base class for all interfaces.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion in DBusInterface.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "TickEvent.h"
+#include "DBusMethod.h"
+#include "DBusObjectPath.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct DBusInterface;
+struct GattProperty;
+struct DBusObject;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Useful Lambdas
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+#define INTERFACE_METHOD_CALLBACK_LAMBDA [] \
+( \
+ const DBusInterface &self, \
+ GDBusConnection *pConnection, \
+ const std::string &methodName, \
+ GVariant *pParameters, \
+ GDBusMethodInvocation *pInvocation, \
+ void *pUserData \
+)
+
+#define TRY_GET_INTERFACE_OF_TYPE(pInterface, type) \
+ (pInterface->getInterfaceType() == type::kInterfaceType ? \
+ std::static_pointer_cast<type>(pInterface) : \
+ nullptr)
+
+#define TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, type) \
+ (pInterface->getInterfaceType() == type::kInterfaceType ? \
+ std::static_pointer_cast<const type>(pInterface) : \
+ nullptr)
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Representation of a D-Bus interface
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct DBusInterface
+{
+ // Our interface type
+ static constexpr const char *kInterfaceType = "DBusInterface";
+
+ typedef void (*MethodCallback)(const DBusInterface &self, GDBusConnection *pConnection, const std::string &methodName, GVariant *pParameters, GDBusMethodInvocation *pInvocation, void *pUserData);
+ typedef void (*EventCallback)(const DBusInterface &self, const TickEvent &event, GDBusConnection *pConnection, void *pUserData);
+
+ // Standard constructor
+ DBusInterface(DBusObject &owner, const std::string &name);
+ virtual ~DBusInterface();
+
+ // Returns a string identifying the type of interface
+ virtual const std::string getInterfaceType() const { return DBusInterface::kInterfaceType; }
+
+ //
+ // Interface name (ex: "org.freedesktop.DBus.Properties")
+ //
+
+ const std::string &getName() const;
+ DBusInterface &setName(const std::string &name);
+
+ //
+ // Owner information
+ //
+
+ DBusObject &getOwner() const;
+ DBusObjectPath getPathNode() const;
+ DBusObjectPath getPath() const;
+
+ //
+ // D-Bus interface methods
+ //
+
+ DBusInterface &addMethod(const std::string &name, const char *pInArgs[], const char *pOutArgs, DBusMethod::Callback callback);
+
+ // NOTE: Subclasses are encouraged to override this method in order to support different callback types that are specific to
+ // their subclass type.
+ virtual bool callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const;
+
+ //
+ // Interface events (our home-grown poor-mans's method of allowing interfaces to do things periodically)
+ //
+
+ // NOTE: Subclasses are encouraged to overload this method in order to support different callback types that are specific to
+ // their subclass type. In addition, they should return their own type. This simplifies the server description by allowing
+ // calls to chain.
+ DBusInterface &onEvent(int tickFrequency, void *pUserData, TickEvent::Callback callback);
+
+ // NOTE: Subclasses are encouraged to override this method in order to support different callback types that are specific to
+ // their subclass type.
+ virtual void tickEvents(GDBusConnection *pConnection, void *pUserData) const;
+
+ // Internal method used to generate introspection XML used to describe our services on D-Bus
+ virtual std::string generateIntrospectionXML(int depth) const;
+
+protected:
+ DBusObject &owner;
+ std::string name;
+ std::list<DBusMethod> methods;
+ std::list<TickEvent> events;
+};
diff --git a/src/DBusMethod.cpp b/src/DBusMethod.cpp
new file mode 100644
index 0000000..716a454
--- /dev/null
+++ b/src/DBusMethod.cpp
@@ -0,0 +1,88 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is a representation of a D-Bus interface method.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Methods are identified by their name (such as "ReadValue" or "WriteValue"). They have argument definitions (defined as part of
+// their interface) that describe the type of arguments passed into the method and returned from the method.
+//
+// In addition to the method itself, we also store a callback delegate that is responsible for performing the tasks for this method.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <gio/gio.h>
+#include <string>
+#include <vector>
+
+#include "DBusMethod.h"
+
+// Instantiate a named method on a given interface (pOwner) with a given set of arguments and a callback delegate
+DBusMethod::DBusMethod(const DBusInterface *pOwner, const std::string &name, const char *pInArgs[], const char *pOutArgs, Callback callback)
+: pOwner(pOwner), name(name), callback(callback)
+{
+ const char **ppInArg = pInArgs;
+ while(*ppInArg)
+ {
+ this->inArgs.push_back(std::string(*ppInArg));
+ ppInArg++;
+ }
+
+ if (nullptr != pOutArgs)
+ {
+ this->outArgs = pOutArgs;
+ }
+}
+
+// Internal method used to generate introspection XML used to describe our services on D-Bus
+std::string DBusMethod::generateIntrospectionXML(int depth) const
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ std::string xml = std::string();
+
+ xml += prefix + "<method name='" + getName() + "'>\n";
+
+ // Add our input arguments
+ for (const std::string &inArg : getInArgs())
+ {
+ xml += prefix + " <arg type='" + inArg + "' direction='in'>\n";
+ xml += prefix + " <annotation name='org.gtk.GDBus.C.ForceGVariant' value='true' />\n";
+ xml += prefix + " </arg>\n";
+ }
+
+ const std::string &outArgs = getOutArgs();
+ if (!outArgs.empty())
+ {
+ xml += prefix + " <arg type='" + outArgs + "' direction='out'>\n";
+ xml += prefix + " <annotation name='org.gtk.GDBus.C.ForceGVariant' value='true' />\n";
+ xml += prefix + " </arg>\n";
+ }
+
+ xml += prefix + "</method>\n";
+
+ return xml;
+}
diff --git a/src/DBusMethod.h b/src/DBusMethod.h
new file mode 100644
index 0000000..89bc7e3
--- /dev/null
+++ b/src/DBusMethod.h
@@ -0,0 +1,118 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is a representation of a D-Bus interface method.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of DBusMethod.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// This file contains a representation of a D-Bus interface method.
+//
+// Methods are identified by their name (such as "Get" or "Set"). They have argument definitions (defined as part of their
+// interface) that describe the type of arguments passed into the method and returned from the method.
+//
+// In addition to the method itself, we also store a callback that can be called whenever the method is invoked.
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <vector>
+
+#include "Globals.h"
+#include "DBusObjectPath.h"
+#include "Logger.h"
+
+struct DBusInterface;
+
+struct DBusMethod
+{
+ // A method callback delegate
+ typedef void (*Callback)(const DBusInterface &self, GDBusConnection *pConnection, const std::string &methodName, GVariant *pParameters, GDBusMethodInvocation *pInvocation, void *pUserData);
+
+ // Instantiate a named method on a given interface (pOwner) with a given set of arguments and a callback delegate
+ DBusMethod(const DBusInterface *pOwner, const std::string &name, const char *pInArgs[], const char *pOutArgs, Callback callback);
+
+ //
+ // Accessors
+ //
+
+ // Returns the name of the method
+ const std::string &getName() const { return name; }
+
+ // Sets the name of the method
+ //
+ // This method should generally not be called directly. Rather, the name should be set by the constructor
+ DBusMethod &setName(const std::string &name) { this->name = name; return *this; }
+
+ // Get the input argument type string (a GVariant type string format)
+ const std::vector<std::string> &getInArgs() const { return inArgs; }
+
+ // Get the output argument type string (a GVariant type string format)
+ const std::string &getOutArgs() const { return outArgs; }
+
+ // Set the argument types for this method
+ //
+ // This method should generally not be called directly. Rather, the arguments should be set by the constructor
+ DBusMethod &setArgs(const std::vector<std::string> &inArgs, const std::string &outArgs)
+ {
+ this->inArgs = inArgs;
+ this->outArgs = outArgs;
+ return *this;
+ }
+
+ //
+ // Call the method
+ //
+
+ // Calls the method
+ //
+ // If a callback delegate has been set, then this method will call that delegate, otherwise this method will do nothing
+ template<typename T>
+ void call(GDBusConnection *pConnection, const DBusObjectPath &path, const std::string &interfaceName, const std::string &methodName, GVariant *pParameters, GDBusMethodInvocation *pInvocation, void *pUserData) const
+ {
+ // This should never happen, but technically possible if instantiated with a nullptr for `callback`
+ if (!callback)
+ {
+ Logger::error(SSTR << "DBusMethod contains no callback: [" << path << "]:[" << interfaceName << "]:[" << methodName << "]");
+ g_dbus_method_invocation_return_dbus_error(pInvocation, kErrorNotImplemented.c_str(), "This method is not implemented");
+ return;
+ }
+
+ Logger::info(SSTR << "Calling method: [" << path << "]:[" << interfaceName << "]:[" << methodName << "]");
+ callback(*static_cast<const T *>(pOwner), pConnection, methodName, pParameters, pInvocation, pUserData);
+ }
+
+ // Internal method used to generate introspection XML used to describe our services on D-Bus
+ std::string generateIntrospectionXML(int depth) const;
+
+private:
+ const DBusInterface *pOwner;
+ std::string name;
+ std::vector<std::string> inArgs;
+ std::string outArgs;
+ Callback callback;
+};
diff --git a/src/DBusObject.cpp b/src/DBusObject.cpp
new file mode 100644
index 0000000..c476ea9
--- /dev/null
+++ b/src/DBusObject.cpp
@@ -0,0 +1,286 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is an abstraction of a D-Bus object.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A D-Bus object is a container for any number of functional interfaces to expose on the bus. Objects are referred to by their
+// path ("/com/acme/widgets"). Here is a simple example of how D-Bus objects relate to Bluetooth services:
+//
+// Object (path) Interface (name)
+//
+// /com/acme/widget org.bluez.GattService1
+// /com/acme/widget/manufacturer_name org.bluez.GattCharacteristic1
+// /com/acme/widget/serial_number org.bluez.GattCharacteristic1
+//
+// In English, this would be read as "The Acme company has a widget, which has two characteristics defining the manufacturer name
+// and serial number for the widget."
+//
+// Finally, we'll include a published flag. Here's what that's all about:
+//
+// BlueZ uses the GetManagedObjects method (from the org.freedesktop.DBus.ObjectManager interface) to interrogate our
+// service(s). Our Server, however, includes all objects and interfaces, including the GetManagedObjects as well as the various
+// interfaces we expose over Bluetooth. Therefore, we'll need a way to know which ones to expose over Bluetooth (which is, in
+// general, everything EXCEPT the object containing the org.freedesktop.DBus.ObjectManager interface.) Since we manage our
+// objects in a hierarchy, only the root object's publish flag matters.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "GattProperty.h"
+#include "DBusInterface.h"
+#include "GattService.h"
+#include "DBusObject.h"
+#include "Utils.h"
+#include "GattUuid.h"
+#include "Logger.h"
+
+// Construct a root object with no parent
+//
+// We'll include a publish flag since only root objects can be published
+DBusObject::DBusObject(const DBusObjectPath &path, bool publish)
+: publish(publish), path(path), pParent(nullptr)
+{
+}
+
+// Construct a node object
+//
+// Nodes inherit their parent's publish path
+DBusObject::DBusObject(DBusObject *pParent, const DBusObjectPath &pathElement)
+: publish(pParent->publish), path(pathElement), pParent(pParent)
+{
+}
+
+//
+// Accessors
+//
+
+// Returns the `publish` flag
+bool DBusObject::isPublished() const
+{
+ return publish;
+}
+
+// Returns the path node for this object within the hierarchy
+//
+// This method only returns the node. To get the full path, use `getPath()`
+const DBusObjectPath &DBusObject::getPathNode() const
+{
+ return path;
+}
+
+// Returns the full path for this object within the hierarchy
+//
+// This method returns the full path. To get the current node, use `getPathNode()`
+DBusObjectPath DBusObject::getPath() const
+{
+ DBusObjectPath path = getPathNode();
+ const DBusObject *pCurrent = pParent;
+
+ // Traverse up my chain, adding nodes to the path until we have the full thing
+ while(nullptr != pCurrent)
+ {
+ path = pCurrent->getPathNode() + path;
+ pCurrent = pCurrent->pParent;
+ }
+
+ return path;
+}
+
+// Returns the parent object in the hierarchy
+DBusObject &DBusObject::getParent()
+{
+ return *pParent;
+}
+
+// Returns the list of children objects
+const std::list<DBusObject> &DBusObject::getChildren() const
+{
+ return children;
+}
+
+// Add a child to this object
+DBusObject &DBusObject::addChild(const DBusObjectPath &pathElement)
+{
+ children.push_back(DBusObject(this, pathElement));
+ return children.back();
+}
+
+// Returns a list of interfaces for this object
+const DBusObject::InterfaceList &DBusObject::getInterfaces() const
+{
+ return interfaces;
+}
+
+// Convenience functions to add a GATT service to the hierarchy
+//
+// We simply add a new child at the given path and add an interface configured as a GATT service to it using the given UUID.
+GattService &DBusObject::gattServiceBegin(const std::string &pathElement, const GattUuid &uuid)
+{
+ DBusObject &child = addChild(DBusObjectPath(pathElement));
+ GattService &service = *child.addInterface(std::make_shared<GattService>(child, "org.bluez.GattService1"));
+ service.addProperty<GattService>("UUID", uuid);
+ service.addProperty<GattService>("Primary", true);
+ return service;
+}
+
+//
+// Helpful routines for searching objects
+//
+
+// Finds an interface by name within this D-Bus object
+std::shared_ptr<const DBusInterface> DBusObject::findInterface(const DBusObjectPath &path, const std::string &interfaceName, const DBusObjectPath &basePath) const
+{
+ if ((basePath + getPathNode()) == path)
+ {
+ for (std::shared_ptr<const DBusInterface> interface : interfaces)
+ {
+ if (interfaceName == interface->getName())
+ {
+ return interface;
+ }
+ }
+ }
+
+ for (const DBusObject &child : getChildren())
+ {
+ std::shared_ptr<const DBusInterface> pInterface = child.findInterface(path, interfaceName, basePath + getPathNode());
+ if (nullptr != pInterface)
+ {
+ return pInterface;
+ }
+ }
+
+ return nullptr;
+}
+
+// Finds a BlueZ method by name within the specified D-Bus interface
+bool DBusObject::callMethod(const DBusObjectPath &path, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData, const DBusObjectPath &basePath) const
+{
+ if ((basePath + getPathNode()) == path)
+ {
+ for (std::shared_ptr<const DBusInterface> interface : interfaces)
+ {
+ if (interfaceName == interface->getName())
+ {
+ if (interface->callMethod(methodName, pConnection, pParameters, pInvocation, pUserData))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ for (const DBusObject &child : getChildren())
+ {
+ if (child.callMethod(path, interfaceName, methodName, pConnection, pParameters, pInvocation, pUserData, basePath + getPathNode()))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Periodic timer tick propagation
+void DBusObject::tickEvents(GDBusConnection *pConnection, void *pUserData) const
+{
+ for (std::shared_ptr<const DBusInterface> interface : interfaces)
+ {
+ interface->tickEvents(pConnection, pUserData);
+ }
+
+ for (const DBusObject &child : getChildren())
+ {
+ child.tickEvents(pConnection, pUserData);
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// XML generation for a D-Bus introspection
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Internal method used to generate introspection XML used to describe our services on D-Bus
+std::string DBusObject::generateIntrospectionXML(int depth) const
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ std::string xml = std::string();
+
+ if (depth == 0)
+ {
+ xml += "<?xml version='1.0'?>\n";
+ xml += "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n";
+ }
+
+ xml += prefix + "<node name='" + getPathNode().toString() + "'>\n";
+ xml += prefix + " <annotation name='" + kServerOwnedName + ".DBusObject.path' value='" + getPath().toString() + "' />\n";
+
+ for (std::shared_ptr<const DBusInterface> interface : interfaces)
+ {
+ xml += interface->generateIntrospectionXML(depth + 1);
+ }
+
+ for (DBusObject child : getChildren())
+ {
+ xml += child.generateIntrospectionXML(depth + 1);
+ }
+
+ xml += prefix + "</node>\n";
+
+ if (depth == 0)
+ {
+ Logger::debug("Generated XML:");
+ Logger::debug(xml);
+ }
+
+ return xml;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// D-Bus signals
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Emits a signal on the bus from the given path, interface name and signal name, containing a GVariant set of parameters
+void DBusObject::emitSignal(GDBusConnection *pBusConnection, const std::string &interfaceName, const std::string &signalName, GVariant *pParameters)
+{
+ GError *pError = nullptr;
+ gboolean result = g_dbus_connection_emit_signal
+ (
+ pBusConnection, // GDBusConnection *connection
+ NULL, // const gchar *destination_bus_name
+ getPath().c_str(), // const gchar *object_path
+ interfaceName.c_str(), // const gchar *interface_name
+ signalName.c_str(), // const gchar *signal_name
+ pParameters, // GVariant *parameters
+ &pError // GError **error
+ );
+
+ if (0 == result)
+ {
+ Logger::error(SSTR << "Failed to emit signal named '" << signalName << "': " << (nullptr == pError ? "Unknown" : pError->message));
+ }
+}
+
diff --git a/src/DBusObject.h b/src/DBusObject.h
new file mode 100644
index 0000000..123964e
--- /dev/null
+++ b/src/DBusObject.h
@@ -0,0 +1,135 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is an abstraction of a D-Bus object.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussino at the top of DBusObject.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+#include <memory>
+
+#include "DBusInterface.h"
+#include "DBusObjectPath.h"
+
+struct GattProperty;
+struct GattService;
+struct GattUuid;
+
+struct DBusObject
+{
+ // A convenience typedef for describing our list of interface
+ typedef std::list<std::shared_ptr<DBusInterface> > InterfaceList;
+
+ // Construct a root object with no parent
+ //
+ // We'll include a publish flag since only root objects can be published
+ DBusObject(const DBusObjectPath &path, bool publish = true);
+
+ // Construct a node object
+ //
+ // Nodes inherit their parent's publish path
+ DBusObject(DBusObject *pParent, const DBusObjectPath &pathElement);
+
+ //
+ // Accessors
+ //
+
+ // Returns the `publish` flag
+ bool isPublished() const;
+
+ // Returns the path node for this object within the hierarchy
+ //
+ // This method only returns the node. To get the full path, use `getPath()`
+ const DBusObjectPath &getPathNode() const;
+
+ // Returns the full path for this object within the hierarchy
+ //
+ // This method returns the full path. To get the current node, use `getPathNode()`
+ DBusObjectPath getPath() const;
+
+ // Returns the parent object in the hierarchy
+ DBusObject &getParent();
+
+ // Returns the list of children objects
+ const std::list<DBusObject> &getChildren() const;
+
+ // Add a child to this object
+ DBusObject &addChild(const DBusObjectPath &pathElement);
+
+ // Returns a list of interfaces for this object
+ const InterfaceList &getInterfaces() const;
+
+ // Templated method for adding typed interfaces to the object
+ template<typename T>
+ std::shared_ptr<T> addInterface(std::shared_ptr<T> interface)
+ {
+ interfaces.push_back(interface);
+ return std::static_pointer_cast<T>(interfaces.back());
+ }
+
+ // Internal method used to generate introspection XML used to describe our services on D-Bus
+ std::string generateIntrospectionXML(int depth = 0) const;
+
+ // Convenience functions to add a GATT service to the hierarchy
+ //
+ // We simply add a new child at the given path and add an interface configured as a GATT service to it using the given UUID.
+ //
+ // To end a service, call `gattServiceEnd()`
+ GattService &gattServiceBegin(const std::string &pathElement, const GattUuid &uuid);
+
+ //
+ // Helpful routines for searching objects
+ //
+
+ // Finds an interface by name within this D-Bus object
+ std::shared_ptr<const DBusInterface> findInterface(const DBusObjectPath &path, const std::string &interfaceName, const DBusObjectPath &basePath = DBusObjectPath()) const;
+
+ // Finds a BlueZ method by name within the specified D-Bus interface
+ bool callMethod(const DBusObjectPath &path, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData, const DBusObjectPath &basePath = DBusObjectPath()) const;
+
+ // Periodic timer tick propagation
+ void tickEvents(GDBusConnection *pConnection, void *pUserData = nullptr) const;
+
+ // -----------------------------------------------------------------------------------------------------------------------------
+ // D-Bus signals
+ // -----------------------------------------------------------------------------------------------------------------------------
+
+ // Emits a signal on the bus from the given path, interface name and signal name, containing a GVariant set of parameters
+ void emitSignal(GDBusConnection *pBusConnection, const std::string &interfaceName, const std::string &signalName, GVariant *pParameters);
+
+private:
+ bool publish;
+ DBusObjectPath path;
+ InterfaceList interfaces;
+ std::list<DBusObject> children;
+ DBusObject *pParent;
+};
diff --git a/src/DBusObjectPath.h b/src/DBusObjectPath.h
new file mode 100644
index 0000000..78cba15
--- /dev/null
+++ b/src/DBusObjectPath.h
@@ -0,0 +1,172 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This represents a custom string type for a D-Bus object path.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A D-Bus object path is normal string in the form "/com/example/foo/bar". This class provides a set of methods for building
+// these paths safely in such a way that they are guaranteed to always provide a valid path.
+//
+// In addition to this functionality, our DBusObjectPath is its own distinct type requiring explicit conversion, providing a level
+// of protection against accidentally using an arbitrary string as an object path.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <string>
+#include <ostream>
+
+struct DBusObjectPath
+{
+ // Default constructor (creates a root path)
+ inline DBusObjectPath() { path = "/"; }
+
+ // Copy constructor
+ inline DBusObjectPath(const DBusObjectPath &path) : path(path.path) {}
+
+ // Constructor that accepts a C string
+ //
+ // Note: explicit because we don't want accidental conversion. Creating a DBusObjectPath must be intentional.
+ inline explicit DBusObjectPath(const char *pPath) : path(pPath) {}
+
+ // Constructor that accepts a std::string
+ //
+ // Note: explicit because we don't want accidental conversion. Creating a DBusObjectPath must be intentional.
+ inline explicit DBusObjectPath(const std::string &path) : path(path) {}
+
+ // Explicit conversion to std::string
+ inline const std::string &toString() const { return path; }
+
+ // Explicit conversion to a C string
+ inline const char *c_str() const { return path.c_str(); }
+
+ // Assignment
+ inline DBusObjectPath &operator =(const DBusObjectPath &rhs)
+ {
+ if (this == &rhs) return *this;
+ path = rhs.path;
+ return *this;
+ }
+
+ // Concatenation
+ inline const DBusObjectPath &append(const char *rhs)
+ {
+ if (nullptr == rhs || !*rhs) { return *this; }
+ if (path.empty()) { path = rhs; return *this; }
+
+ bool ls = path.back() == '/';
+ bool rs = *rhs == '/';
+ if (ls && rs) { path.erase(path.length()-1); }
+ if (!ls && !rs) { path += "/"; }
+
+ path += rhs;
+ return *this;
+ }
+
+ // Adds a path node (in the form of an std::string) to the end of the path
+ inline const DBusObjectPath &append(const std::string &rhs)
+ {
+ return append(rhs.c_str());
+ }
+
+ // Adds a path node (in the form of a DBusObjectPath) to the end of the path
+ inline const DBusObjectPath &append(const DBusObjectPath &rhs)
+ {
+ return append(rhs.path.c_str());
+ }
+
+ // Adds a path node (in the form of a DBusObjectPath) to the end of the path
+ inline void operator +=(const DBusObjectPath &rhs)
+ {
+ append(rhs);
+ }
+
+ // Adds a path node (in the form of a C string) to the end of the path
+ inline void operator +=(const char *rhs)
+ {
+ append(rhs);
+ }
+
+ // Adds a path node (in the form of an std::string) to the end of the path
+ inline void operator +=(const std::string &rhs)
+ {
+ append(rhs);
+ }
+
+ // Concats two DBusObjectPaths into one, returning the resulting path
+ inline DBusObjectPath operator +(const DBusObjectPath &rhs) const
+ {
+ DBusObjectPath result(*this);
+ result += rhs;
+ return result;
+ }
+
+ // Concats a C string onto a DBusObjectPath, returning the resulting path
+ inline DBusObjectPath operator +(const char *rhs) const
+ {
+ DBusObjectPath result(*this);
+ result += rhs;
+ return result;
+ }
+
+ // Concats a std::string onto a DBusObjectPath, returning the resulting path
+ inline DBusObjectPath operator +(const std::string &rhs) const
+ {
+ DBusObjectPath result(*this);
+ result += rhs;
+ return result;
+ }
+
+ // Tests two DBusObjectPaths for equality, returning true of the two strings are identical
+ inline bool operator ==(const DBusObjectPath &rhs) const
+ {
+ return path == rhs.path;
+ }
+
+private:
+
+ std::string path;
+};
+
+// Mixed-mode override for adding a DBusObjectPath to a C string, returning a new DBusObjectPath result
+inline DBusObjectPath operator +(const char *lhs, const DBusObjectPath &rhs) { return DBusObjectPath(lhs) + rhs; }
+
+// Mixed-mode override for adding a DBusObjectPath to a std::string, returning a new DBusObjectPath result
+inline DBusObjectPath operator +(const std::string &lhs, const DBusObjectPath &rhs) { return DBusObjectPath(lhs) + rhs; }
+
+// Streaming support for our DBusObjectPath (useful for our logging mechanism)
+inline std::ostream& operator<<(std::ostream &os, const DBusObjectPath &path)
+{
+ os << path.toString();
+ return os;
+}
+
+// Streaming support for our DBusObjectPath (useful for our logging mechanism)
+inline std::ostream& operator +(std::ostream &os, const DBusObjectPath &path)
+{
+ os << path.toString();
+ return os;
+}
diff --git a/src/GattCharacteristic.cpp b/src/GattCharacteristic.cpp
new file mode 100644
index 0000000..2c43c9e
--- /dev/null
+++ b/src/GattCharacteristic.cpp
@@ -0,0 +1,217 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our representation of a GATT Characteristic which is intended to be used in our server description
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A GATT characteristic is the component within the Bluetooth LE standard that holds and serves data over Bluetooth. This class
+// is intended to be used within the server description. For an explanation of how this class is used, see the detailed discussion
+// in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "GattCharacteristic.h"
+#include "GattDescriptor.h"
+#include "GattProperty.h"
+#include "GattUuid.h"
+#include "DBusObject.h"
+#include "GattService.h"
+#include "Utils.h"
+#include "Logger.h"
+
+//
+// Standard constructor
+//
+
+// Construct a GattCharacteristic
+//
+// Genreally speaking, these objects should not be constructed directly. Rather, use the `gattCharacteristicBegin()` method
+// in `GattService`.
+GattCharacteristic::GattCharacteristic(DBusObject &owner, GattService &service, const std::string &name)
+: GattInterface(owner, name), service(service), pOnUpdatedValueFunc(nullptr)
+{
+}
+
+// Returning the owner pops us one level up the hierarchy
+//
+// This method compliments `GattService::gattCharacteristicBegin()`
+GattService &GattCharacteristic::gattCharacteristicEnd()
+{
+ return service;
+}
+
+// Locates a D-Bus method within this D-Bus interface and invokes the method
+bool GattCharacteristic::callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const
+{
+ for (const DBusMethod &method : methods)
+ {
+ if (methodName == method.getName())
+ {
+ method.call<GattCharacteristic>(pConnection, getPath(), getName(), methodName, pParameters, pInvocation, pUserData);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Adds an event to the characteristic and returns a refereence to 'this` to enable method chaining in the server description
+//
+// NOTE: We specifically overload this method in order to accept our custom EventCallback type and transform it into a
+// TickEvent::Callback type. We also return our own type. This simplifies the server description by allowing call to chain.
+GattCharacteristic &GattCharacteristic::onEvent(int tickFrequency, void *pUserData, EventCallback callback)
+{
+ events.push_back(TickEvent(this, tickFrequency, reinterpret_cast<TickEvent::Callback>(callback), pUserData));
+ return *this;
+}
+
+// Ticks events within this characteristic
+//
+// Note: we specifically override this method in order to translate the generic TickEvent::Callback into our own EventCallback
+void GattCharacteristic::tickEvents(GDBusConnection *pConnection, void *pUserData) const
+{
+ for (const TickEvent &event : events)
+ {
+ event.tick<GattCharacteristic>(getPath(), pConnection, pUserData);
+ }
+}
+
+// Specialized support for ReadlValue method
+//
+// Defined as: array{byte} ReadValue(dict options)
+//
+// D-Bus breakdown:
+//
+// Input args: options - "a{sv}"
+// Output args: value - "ay"
+GattCharacteristic &GattCharacteristic::onReadValue(MethodCallback callback)
+{
+ // array{byte} ReadValue(dict options)
+ static const char *inArgs[] = {"a{sv}", nullptr};
+ addMethod("ReadValue", inArgs, "ay", reinterpret_cast<DBusMethod::Callback>(callback));
+ return *this;
+}
+
+// Specialized support for WriteValue method
+//
+// Defined as: void WriteValue(array{byte} value, dict options)
+//
+// D-Bus breakdown:
+//
+// Input args: value - "ay"
+// options - "a{sv}"
+// Output args: void
+GattCharacteristic &GattCharacteristic::onWriteValue(MethodCallback callback)
+{
+ static const char *inArgs[] = {"ay", "a{sv}", nullptr};
+ addMethod("WriteValue", inArgs, nullptr, reinterpret_cast<DBusMethod::Callback>(callback));
+ return *this;
+}
+
+// Custom support for handling updates to our characteristic's value
+//
+// Defined as: (NOT defined by Bluetooth or BlueZ - this method is internal only)
+//
+// This method is called by our framework whenever a characteristic's value is updated. If you need to perform any actions
+// when a value is updatd, this is a good place to do that work.
+//
+// If you need to perform the same action(s) when a value is updated from the client (via `onWriteValue`) or from this server,
+// then it may be beneficial to call this method from within your onWriteValue callback to reduce duplicated code. See
+// `callOnUpdatedValue` for more information.
+GattCharacteristic &GattCharacteristic::onUpdatedValue(UpdatedValueCallback callback)
+{
+ pOnUpdatedValueFunc = callback;
+ return *this;
+}
+
+// Calls the onUpdatedValue method, if one was set.
+//
+// Returns false if there was no method set, otherwise, returns the boolean result of the method call.
+//
+// If you need to perform the same action(s) when a value is updated from the client (via onWriteValue) or from this server,
+// then it may be beneficial to place those actions in the `onUpdatedValue` method and call it from from within your
+// `onWriteValue` callback to reduce duplicated code. To call the `onUpdatedValue` method from within your `onWriteValue`, you
+// can use this pattern:
+//
+// .onWriteValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
+// {
+// // Update your value
+// ...
+//
+// // Call the onUpdateValue method that was set in the same Characteristic
+// self.callOnUpdatedValue(pConnection, pUserData);
+// })
+bool GattCharacteristic::callOnUpdatedValue(GDBusConnection *pConnection, void *pUserData) const
+{
+ if (nullptr == pOnUpdatedValueFunc)
+ {
+ return false;
+ }
+
+ Logger::info(SSTR << "Calling OnUpdatedValue function for interface at path '" << getPath() << "'");
+ return pOnUpdatedValueFunc(*this, pConnection, pUserData);
+}
+
+// Convenience functions to add a GATT descriptor to the hierarchy
+//
+// We simply add a new child at the given path and add an interface configured as a GATT descriptor to it. The
+// new descriptor is declared with a UUID and a variable argument list of flags (in string form.) For a complete and
+// up-to-date list of flag values, see: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+//
+// At the time of this writing, the list of flags is as follows:
+//
+// "read"
+// "write"
+// "encrypt-read"
+// "encrypt-write"
+// "encrypt-authenticated-read"
+// "encrypt-authenticated-write"
+// "secure-read" (Server Only)
+// "secure-write" (Server Only)
+//
+//
+// To end a descriptor, call `GattDescriptor::gattDescriptorEnd()`
+GattDescriptor &GattCharacteristic::gattDescriptorBegin(const std::string &pathElement, const GattUuid &uuid, const std::vector<const char *> &flags)
+{
+ DBusObject &child = owner.addChild(DBusObjectPath(pathElement));
+ GattDescriptor &descriptor = *child.addInterface(std::make_shared<GattDescriptor>(child, *this, "org.bluez.GattDescriptor1"));
+ descriptor.addProperty<GattDescriptor>("UUID", uuid);
+ descriptor.addProperty<GattDescriptor>("Characteristic", getPath());
+ descriptor.addProperty<GattDescriptor>("Flags", flags);
+ return descriptor;
+}
+
+// Sends a change notification to subscribers to this characteristic
+//
+// This is a generalized method that accepts a `GVariant *`. A templated version is available that supports common types called
+// `sendChangeNotificationValue()`.
+void GattCharacteristic::sendChangeNotificationVariant(GDBusConnection *pBusConnection, GVariant *pNewValue) const
+{
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add(&builder, "{sv}", "Value", pNewValue);
+ GVariant *pSasv = g_variant_new("(sa{sv})", "org.bluez.GattCharacteristic1", &builder);
+ owner.emitSignal(pBusConnection, "org.freedesktop.DBus.Properties", "PropertiesChanged", pSasv);
+}
diff --git a/src/GattCharacteristic.h b/src/GattCharacteristic.h
new file mode 100644
index 0000000..e7052dc
--- /dev/null
+++ b/src/GattCharacteristic.h
@@ -0,0 +1,219 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our representation of a GATT Characteristic which is intended to be used in our server description
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of GattCharacteristic.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "Utils.h"
+#include "TickEvent.h"
+#include "GattInterface.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattCharacteristic;
+struct GattDescriptor;
+struct GattProperty;
+struct GattService;
+struct GattUuid;
+struct DBusObject;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Useful Lambdas
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+#define CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA [] \
+( \
+ const GattCharacteristic &self, \
+ GDBusConnection *pConnection, \
+ void *pUserData \
+) -> bool
+
+#define CHARACTERISTIC_EVENT_CALLBACK_LAMBDA [] \
+( \
+ const GattCharacteristic &self, \
+ const TickEvent &event, \
+ GDBusConnection *pConnection, \
+ void *pUserData \
+)
+
+#define CHARACTERISTIC_METHOD_CALLBACK_LAMBDA [] \
+( \
+ const GattCharacteristic &self, \
+ GDBusConnection *pConnection, \
+ const std::string &methodName, \
+ GVariant *pParameters, \
+ GDBusMethodInvocation *pInvocation, \
+ void *pUserData \
+)
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Representation of a Bluetooth GATT Characteristic
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattCharacteristic : GattInterface
+{
+ // Our interface type
+ static constexpr const char *kInterfaceType = "GattCharacteristic";
+
+ typedef void (*MethodCallback)(const GattCharacteristic &self, GDBusConnection *pConnection, const std::string &methodName, GVariant *pParameters, GDBusMethodInvocation *pInvocation, void *pUserData);
+ typedef void (*EventCallback)(const GattCharacteristic &self, const TickEvent &event, GDBusConnection *pConnection, void *pUserData);
+ typedef bool (*UpdatedValueCallback)(const GattCharacteristic &self, GDBusConnection *pConnection, void *pUserData);
+
+ // Construct a GattCharacteristic
+ //
+ // Genreally speaking, these objects should not be constructed directly. Rather, use the `gattCharacteristicBegin()` method
+ // in `GattService`.
+ GattCharacteristic(DBusObject &owner, GattService &service, const std::string &name);
+ virtual ~GattCharacteristic() {}
+
+ // Returns a string identifying the type of interface
+ virtual const std::string getInterfaceType() const { return GattCharacteristic::kInterfaceType; }
+
+ // Returning the owner pops us one level up the hierarchy
+ //
+ // This method compliments `GattService::gattCharacteristicBegin()`
+ GattService &gattCharacteristicEnd();
+
+ // Locates a D-Bus method within this D-Bus interface and invokes the method
+ virtual bool callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const;
+
+ // Adds an event to the characteristic and returns a refereence to 'this` to enable method chaining in the server description
+ //
+ // NOTE: We specifically overload this method in order to accept our custom EventCallback type and transform it into a
+ // TickEvent::Callback type. We also return our own type. This simplifies the server description by allowing call to chain.
+ GattCharacteristic &onEvent(int tickFrequency, void *pUserData, EventCallback callback);
+
+ // Ticks events within this characteristic
+ //
+ // Note: we specifically override this method in order to translate the generic TickEvent::Callback into our own EventCallback
+ virtual void tickEvents(GDBusConnection *pConnection, void *pUserData) const;
+
+ // Specialized support for Characteristic ReadlValue method
+ //
+ // Defined as: array{byte} ReadValue(dict options)
+ //
+ // D-Bus breakdown:
+ //
+ // Input args: options - "a{sv}"
+ // Output args: value - "ay"
+ GattCharacteristic &onReadValue(MethodCallback callback);
+
+ // Specialized support for Characteristic WriteValue method
+ //
+ // Defined as: void WriteValue(array{byte} value, dict options)
+ //
+ // D-Bus breakdown:
+ //
+ // Input args: value - "ay"
+ // options - "a{sv}"
+ // Output args: void
+ GattCharacteristic &onWriteValue(MethodCallback callback);
+
+ // Custom support for handling updates to our characteristic's value
+ //
+ // Defined as: (NOT defined by Bluetooth or BlueZ - this method is internal only)
+ //
+ // This method is called by our framework whenever a characteristic's value is updated. If you need to perform any actions
+ // when a value is updatd, this is a good place to do that work.
+ //
+ // If you need to perform the same action(s) when a value is updated from the client (via `onWriteValue`) or from this server,
+ // then it may be beneficial to call this method from within your onWriteValue callback to reduce duplicated code. See
+ // `callOnUpdatedValue` for more information.
+ GattCharacteristic &onUpdatedValue(UpdatedValueCallback callback);
+
+ // Calls the onUpdatedValue method, if one was set.
+ //
+ // Returns false if there was no method set, otherwise, returns the boolean result of the method call.
+ //
+ // If you need to perform the same action(s) when a value is updated from the client (via onWriteValue) or from this server,
+ // then it may be beneficial to place those actions in the `onUpdatedValue` method and call it from from within your
+ // `onWriteValue` callback to reduce duplicated code. To call the `onUpdatedValue` method from within your `onWriteValue`, you
+ // can use this pattern:
+ //
+ // .onWriteValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
+ // {
+ // // Update your value
+ // ...
+ //
+ // // Call the onUpdateValue method that was set in the same Characteristic
+ // self.callOnUpdatedValue(pConnection, pUserData);
+ // })
+ bool callOnUpdatedValue(GDBusConnection *pConnection, void *pUserData) const;
+
+ // Convenience functions to add a GATT descriptor to the hierarchy
+ //
+ // We simply add a new child at the given path and add an interface configured as a GATT descriptor to it. The
+ // new descriptor is declared with a UUID and a variable argument list of flags (in string form.) For a complete and
+ // up-to-date list of flag values, see: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+ //
+ // At the time of this writing, the list of flags is as follows:
+ //
+ // "read"
+ // "write"
+ // "encrypt-read"
+ // "encrypt-write"
+ // "encrypt-authenticated-read"
+ // "encrypt-authenticated-write"
+ // "secure-read" (Server Only)
+ // "secure-write" (Server Only)
+ //
+ // To end the descriptor, call `gattDescriptorEnd()`
+ GattDescriptor &gattDescriptorBegin(const std::string &pathElement, const GattUuid &uuid, const std::vector<const char *> &flags);
+
+ // Sends a change notification to subscribers to this characteristic
+ //
+ // This is a generalized method that accepts a `GVariant *`. A templated version is available that supports common types called
+ // `sendChangeNotificationValue()`.
+ void sendChangeNotificationVariant(GDBusConnection *pBusConnection, GVariant *pNewValue) const;
+
+ // Sends a change notification to subscribers to this characteristic
+ //
+ // This is a helper method that accepts common types. For custom types, there is a form that accepts a `GVariant *`, called
+ // `sendChangeNotificationVariant()`.
+ template<typename T>
+ void sendChangeNotificationValue(GDBusConnection *pBusConnection, T value) const
+ {
+ GVariant *pVariant = Utils::gvariantFromByteArray(value);
+ sendChangeNotificationVariant(pBusConnection, pVariant);
+ }
+
+protected:
+
+ GattService &service;
+ UpdatedValueCallback pOnUpdatedValueFunc;
+};
diff --git a/src/GattDescriptor.cpp b/src/GattDescriptor.cpp
new file mode 100644
index 0000000..a0e1829
--- /dev/null
+++ b/src/GattDescriptor.cpp
@@ -0,0 +1,176 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our representation of a GATT Characteristic which is intended to be used in our server description
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A GATT descriptor is the component within the Bluetooth LE standard that holds and serves metadata about a Characteristic over
+// Bluetooth. This class is intended to be used within the server description. For an explanation of how this class is used, see the
+// detailed discussion in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "GattDescriptor.h"
+#include "GattProperty.h"
+#include "DBusObject.h"
+#include "Utils.h"
+#include "Logger.h"
+
+//
+// Standard constructor
+//
+
+// Construct a GattDescriptor
+//
+// Genreally speaking, these objects should not be constructed directly. Rather, use the `gattDescriptorBegin()` method
+// in `GattCharacteristic`.
+GattDescriptor::GattDescriptor(DBusObject &owner, GattCharacteristic &characteristic, const std::string &name)
+: GattInterface(owner, name), characteristic(characteristic), pOnUpdatedValueFunc(nullptr)
+{
+}
+
+// Returning the owner pops us one level up the hierarchy
+//
+// This method compliments `GattCharacteristic::gattDescriptorBegin()`
+GattCharacteristic &GattDescriptor::gattDescriptorEnd()
+{
+ return characteristic;
+}
+
+//
+// D-Bus interface methods
+//
+
+// Locates a D-Bus method within this D-Bus interface
+bool GattDescriptor::callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const
+{
+ for (const DBusMethod &method : methods)
+ {
+ if (methodName == method.getName())
+ {
+ method.call<GattDescriptor>(pConnection, getPath(), getName(), methodName, pParameters, pInvocation, pUserData);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Adds an event to the descriptor and returns a refereence to 'this` to enable method chaining in the server description
+//
+// NOTE: We specifically overload this method in order to accept our custom EventCallback type and transform it into a
+// TickEvent::Callback type. We also return our own type. This simplifies the server description by allowing call to chain.
+GattDescriptor &GattDescriptor::onEvent(int tickFrequency, void *pUserData, EventCallback callback)
+{
+ events.push_back(TickEvent(this, tickFrequency, reinterpret_cast<TickEvent::Callback>(callback), pUserData));
+ return *this;
+}
+
+// Ticks events within this descriptor
+//
+// Note: we specifically override this method in order to translate the generic TickEvent::Callback into our own EventCallback
+void GattDescriptor::tickEvents(GDBusConnection *pConnection, void *pUserData) const
+{
+ for (const TickEvent &event : events)
+ {
+ event.tick<GattDescriptor>(getPath(), pConnection, pUserData);
+ }
+}
+
+// Specialized support for ReadlValue method
+//
+// Defined as: array{byte} ReadValue(dict options)
+//
+// D-Bus breakdown:
+//
+// Input args: options - "a{sv}"
+// Output args: value - "ay"
+GattDescriptor &GattDescriptor::onReadValue(MethodCallback callback)
+{
+ // array{byte} ReadValue(dict options)
+ const char *inArgs[] = {"a{sv}", nullptr};
+ addMethod("ReadValue", inArgs, "ay", reinterpret_cast<DBusMethod::Callback>(callback));
+ return *this;
+}
+
+// Specialized support for WriteValue method
+//
+// Defined as: void WriteValue(array{byte} value, dict options)
+//
+// D-Bus breakdown:
+//
+// Input args: value - "ay"
+// options - "a{sv}"
+// Output args: void
+GattDescriptor &GattDescriptor::onWriteValue(MethodCallback callback)
+{
+ const char *inArgs[] = {"ay", "a{sv}", nullptr};
+ addMethod("WriteValue", inArgs, nullptr, reinterpret_cast<DBusMethod::Callback>(callback));
+ return *this;
+}
+
+// Custom support for handling updates to our descriptor's value
+//
+// Defined as: (NOT defined by Bluetooth or BlueZ - this method is internal only)
+//
+// This method is called by our framework whenever a Descriptor's value is updated. If you need to perform any actions
+// when a value is updatd, this is a good place to do that work.
+//
+// If you need to perform the same action(s) when a value is updated from the client (via `onWriteValue`) or from this server,
+// then it may be beneficial to call this method from within your onWriteValue callback to reduce duplicated code. See
+// `callOnUpdatedValue` for more information.
+GattDescriptor &GattDescriptor::onUpdatedValue(UpdatedValueCallback callback)
+{
+ pOnUpdatedValueFunc = callback;
+ return *this;
+}
+
+// Calls the onUpdatedValue method, if one was set.
+//
+// Returns false if there was no method set, otherwise, returns the boolean result of the method call.
+//
+// If you need to perform the same action(s) when a value is updated from the client (via onWriteValue) or from this server,
+// then it may be beneficial to place those actions in the `onUpdatedValue` method and call it from from within your
+// `onWriteValue` callback to reduce duplicated code. To call the `onUpdatedValue` method from within your `onWriteValue`, you
+// can use this pattern:
+//
+// .onUpdatedValue(DESCRIPTOR_UPDATED_VALUE_CALLBACK_LAMBDA
+// {
+// // Update your value
+// ...
+//
+// // Call the onUpdateValue method that was set in the same Descriptor
+// self.callOnUpdatedValue(pConnection, pUserData);
+// })
+bool GattDescriptor::callOnUpdatedValue(GDBusConnection *pConnection, void *pUserData) const
+{
+ if (nullptr == pOnUpdatedValueFunc)
+ {
+ return false;
+ }
+
+ Logger::info(SSTR << "Calling OnUpdatedValue function for interface at path '" << getPath() << "'");
+ return pOnUpdatedValueFunc(*this, pConnection, pUserData);
+}
diff --git a/src/GattDescriptor.h b/src/GattDescriptor.h
new file mode 100644
index 0000000..cdda126
--- /dev/null
+++ b/src/GattDescriptor.h
@@ -0,0 +1,183 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our representation of a GATT Characteristic which is intended to be used in our server description
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of GattDescriptor.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "TickEvent.h"
+#include "Utils.h"
+#include "GattInterface.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattCharacteristic;
+struct GattDescriptor;
+struct GattProperty;
+struct DBusObject;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Useful Lambdas
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+#define DESCRIPTOR_UPDATED_VALUE_CALLBACK_LAMBDA [] \
+( \
+ const GattDescriptor &self, \
+ GDBusConnection *pConnection, \
+ void *pUserData \
+) -> bool
+
+#define DESCRIPTOR_EVENT_CALLBACK_LAMBDA [] \
+( \
+ const GattDescriptor &self, \
+ const TickEvent &event, \
+ GDBusConnection *pConnection, \
+ void *pUserData \
+)
+
+#define DESCRIPTOR_METHOD_CALLBACK_LAMBDA [] \
+( \
+ const GattDescriptor &self, \
+ GDBusConnection *pConnection, \
+ const std::string &methodName, \
+ GVariant *pParameters, \
+ GDBusMethodInvocation *pInvocation, \
+ void *pUserData \
+)
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Representation of a Bluetooth GATT Descriptor
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattDescriptor : GattInterface
+{
+ // Our interface type
+ static constexpr const char *kInterfaceType = "GattDescriptor";
+
+ typedef void (*MethodCallback)(const GattDescriptor &self, GDBusConnection *pConnection, const std::string &methodName, GVariant *pParameters, GDBusMethodInvocation *pInvocation, void *pUserData);
+ typedef void (*EventCallback)(const GattDescriptor &self, const TickEvent &event, GDBusConnection *pConnection, void *pUserData);
+ typedef bool (*UpdatedValueCallback)(const GattDescriptor &self, GDBusConnection *pConnection, void *pUserData);
+
+ //
+ // Standard constructor
+ //
+
+ // Construct a GattDescriptor
+ //
+ // Genreally speaking, these objects should not be constructed directly. Rather, use the `gattDescriptorBegin()` method
+ // in `GattCharacteristic`.
+ GattDescriptor(DBusObject &owner, GattCharacteristic &characteristic, const std::string &name);
+ virtual ~GattDescriptor() {}
+
+ // Returns a string identifying the type of interface
+ virtual const std::string getInterfaceType() const { return GattDescriptor::kInterfaceType; }
+
+ // Returning the owner pops us one level up the hierarchy
+ //
+ // This method compliments `GattCharacteristic::gattDescriptorBegin()`
+ GattCharacteristic &gattDescriptorEnd();
+
+ // Locates a D-Bus method within this D-Bus interface and invokes the method
+ virtual bool callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const;
+
+ // Adds an event to the descriptor and returns a refereence to 'this` to enable method chaining in the server description
+ //
+ // NOTE: We specifically overload this method in order to accept our custom EventCallback type and transform it into a
+ // TickEvent::Callback type. We also return our own type. This simplifies the server description by allowing call to chain.
+ GattDescriptor &onEvent(int tickFrequency, void *pUserData, EventCallback callback);
+
+ // Ticks events within this descriptor
+ //
+ // Note: we specifically override this method in order to translate the generic TickEvent::Callback into our own EventCallback
+ virtual void tickEvents(GDBusConnection *pConnection, void *pUserData) const;
+
+ // Specialized support for Descriptor ReadlValue method
+ //
+ // Defined as: array{byte} ReadValue(dict options)
+ //
+ // D-Bus breakdown:
+ //
+ // Input args: options - "a{sv}"
+ // Output args: value - "ay"
+ GattDescriptor &onReadValue(MethodCallback callback);
+
+ // Specialized support for Descriptor WriteValue method
+ //
+ // Defined as: void WriteValue(array{byte} value, dict options)
+ //
+ // D-Bus breakdown:
+ //
+ // Input args: value - "ay"
+ // options - "a{sv}"
+ // Output args: void
+ GattDescriptor &onWriteValue(MethodCallback callback);
+
+ // Custom support for handling updates to our descriptor's value
+ //
+ // Defined as: (NOT defined by Bluetooth or BlueZ - this method is internal only)
+ //
+ // This method is called by our framework whenever a Descriptor's value is updated. If you need to perform any actions
+ // when a value is updatd, this is a good place to do that work.
+ //
+ // If you need to perform the same action(s) when a value is updated from the client (via `onWriteValue`) or from this server,
+ // then it may be beneficial to call this method from within your onWriteValue callback to reduce duplicated code. See
+ // `callOnUpdatedValue` for more information.
+ GattDescriptor &onUpdatedValue(UpdatedValueCallback callback);
+
+ // Calls the onUpdatedValue method, if one was set.
+ //
+ // Returns false if there was no method set, otherwise, returns the boolean result of the method call.
+ //
+ // If you need to perform the same action(s) when a value is updated from the client (via onWriteValue) or from this server,
+ // then it may be beneficial to place those actions in the `onUpdatedValue` method and call it from from within your
+ // `onWriteValue` callback to reduce duplicated code. To call the `onUpdatedValue` method from within your `onWriteValue`, you
+ // can use this pattern:
+ //
+ // .onUpdatedValue(DESCRIPTOR_UPDATED_VALUE_CALLBACK_LAMBDA
+ // {
+ // // Update your value
+ // ...
+ //
+ // // Call the onUpdateValue method that was set in the same Descriptor
+ // self.callOnUpdatedValue(pConnection, pUserData);
+ // })
+ bool callOnUpdatedValue(GDBusConnection *pConnection, void *pUserData) const;
+
+protected:
+
+ GattCharacteristic &characteristic;
+ UpdatedValueCallback pOnUpdatedValueFunc;
+};
diff --git a/src/GattInterface.cpp b/src/GattInterface.cpp
new file mode 100644
index 0000000..bb80481
--- /dev/null
+++ b/src/GattInterface.cpp
@@ -0,0 +1,123 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our abstraction layer for GATT interfaces, used by GattService, GattCharacteristic & GattDescriptor
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This class is intended to be used within the server description. For an explanation of how this class is used, see the detailed
+// description in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "GattInterface.h"
+#include "GattProperty.h"
+#include "DBusObject.h"
+#include "Logger.h"
+
+//
+// Standard constructor
+//
+GattInterface::GattInterface(DBusObject &owner, const std::string &name)
+: DBusInterface(owner, name)
+{
+}
+
+GattInterface::~GattInterface()
+{
+}
+
+//
+// GATT Characteristic properties
+//
+
+// Returns the list of GATT properties
+const std::list<GattProperty> &GattInterface::getProperties() const
+{
+ return properties;
+}
+
+// When responding to a method, we need to return a GVariant value wrapped in a tuple. This method will simplify this slightly by
+// wrapping a GVariant of the type "ay" and wrapping it in a tuple before sending it off as the method response.
+//
+// This is the generalized form that accepts a GVariant *. There is a templated helper method (`methodReturnValue()`) that accepts
+// common types.
+void GattInterface::methodReturnVariant(GDBusMethodInvocation *pInvocation, GVariant *pVariant, bool wrapInTuple) const
+{
+ if (wrapInTuple)
+ {
+ pVariant = g_variant_new_tuple(&pVariant, 1);
+ }
+ g_dbus_method_invocation_return_value(pInvocation, pVariant);
+}
+
+// Locates a `GattProperty` within the interface
+//
+// This method returns a pointer to the property or nullptr if not found
+const GattProperty *GattInterface::findProperty(const std::string &name) const
+{
+ for (const GattProperty &property : properties)
+ {
+ if (property.getName() == name)
+ {
+ return &property;
+ }
+ }
+
+ return nullptr;
+}
+
+// Internal method used to generate introspection XML used to describe our services on D-Bus
+std::string GattInterface::generateIntrospectionXML(int depth) const
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ std::string xml = std::string();
+
+ if (methods.size() && getProperties().empty())
+ {
+ xml += prefix + "<interface name='" + getName() + "' />\n";
+ }
+ else
+ {
+ xml += prefix + "<interface name='" + getName() + "'>\n";
+
+ // Describe our methods
+ for (const DBusMethod &method : methods)
+ {
+ xml += method.generateIntrospectionXML(depth + 1);
+ }
+
+ // Describe our properties
+ for (const GattProperty &property : getProperties())
+ {
+ xml += property.generateIntrospectionXML(depth + 1);
+ }
+
+ xml += prefix + "</interface>\n";
+ }
+
+ return xml;
+}
diff --git a/src/GattInterface.h b/src/GattInterface.h
new file mode 100644
index 0000000..3c4075e
--- /dev/null
+++ b/src/GattInterface.h
@@ -0,0 +1,231 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our abstraction layer for GATT interfaces, used by GattService, GattCharacteristic & GattDescriptor
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of GattInterface.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "TickEvent.h"
+#include "DBusInterface.h"
+#include "GattProperty.h"
+#include "GattUuid.h"
+#include "Server.h"
+#include "Utils.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattInterface;
+struct DBusObject;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Pure virtual representation of a Bluetooth GATT Interface, the base class for Services, Characteristics and Descriptors
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattInterface : DBusInterface
+{
+ // Standard constructor
+ GattInterface(DBusObject &owner, const std::string &name);
+ virtual ~GattInterface();
+
+ // Returns a string identifying the type of interface
+ virtual const std::string getInterfaceType() const = 0;
+
+ //
+ // GATT Characteristic properties
+ //
+
+ // Returns the list of GATT properties
+ const std::list<GattProperty> &getProperties() const;
+
+ // Add a `GattProperty` to the interface
+ //
+ // There are helper methods for adding properties for common types as well as a generalized helper method for adding a
+ // `GattProperty` of a generic GVariant * type.
+ template<typename T>
+ T &addProperty(const GattProperty &property)
+ {
+ properties.push_back(property);
+ return *static_cast<T *>(this);
+ }
+
+ // Add a named property with a GVariant *
+ //
+ // There are helper methods for common types (UUIDs, strings, boolean, etc.) Use this method when no helper method exists for
+ // the type you want to use. There is also a helper method for adding a named property of a pre-built `GattProperty`.
+ template<typename T>
+ T &addProperty(const std::string &name, GVariant *pValue, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, pValue, getter, setter));
+ }
+
+ // Helper method for adding a named property with a `GattUuid`
+ template<typename T>
+ T &addProperty(const std::string &name, const GattUuid &uuid, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromString(uuid.toString128().c_str()), getter, setter));
+ }
+
+ // Helper method for adding a named property with a `DBusObjectPath`
+ template<typename T>
+ T &addProperty(const std::string &name, const DBusObjectPath &path, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromObject(path), getter, setter));
+ }
+
+ // Helper method for adding a named property with a std::strings
+ template<typename T>
+ T &addProperty(const std::string &name, const std::string &str, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromString(str), getter, setter));
+ }
+
+ // Helper method for adding a named property with an array of std::strings
+ template<typename T>
+ T &addProperty(const std::string &name, const std::vector<std::string> &arr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromStringArray(arr), getter, setter));
+ }
+
+ // Helper method for adding a named property with an array of C strings
+ template<typename T>
+ T &addProperty(const std::string &name, const std::vector<const char *> &arr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromStringArray(arr), getter, setter));
+ }
+
+ // Helper method for adding a named property with a given C string
+ template<typename T>
+ T &addProperty(const std::string &name, const char *pStr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromString(pStr), getter, setter));
+ }
+
+ // Helper method for adding a named property with a given boolean value
+ template<typename T>
+ T &addProperty(const std::string &name, bool value, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr)
+ {
+ return addProperty<T>(GattProperty(name, Utils::gvariantFromBoolean(value), getter, setter));
+ }
+
+ // Return a data value from the server's registered data getter (GGKServerDataGetter)
+ //
+ // This method is for use with non-pointer types. For pointer types, use `getDataPointer()` instead.
+ //
+ // This method is intended to be used in the server description. An example usage would be:
+ //
+ // uint8_t batteryLevel = self.getDataValue<uint8_t>("battery/level", 0);
+ template<typename T>
+ T getDataValue(const char *pName, const T defaultValue) const
+ {
+ const void *pData = TheServer->getDataGetter()(pName);
+ return nullptr == pData ? defaultValue : *static_cast<const T *>(pData);
+ }
+
+ // Return a data pointer from the server's registered data getter (GGKServerDataGetter)
+ //
+ // This method is for use with pointer types. For non-pointer types, use `getDataValue()` instead.
+ //
+ // This method is intended to be used in the server description. An example usage would be:
+ //
+ // const char *pTextString = self.getDataPointer<const char *>("text/string", "");
+ template<typename T>
+ T getDataPointer(const char *pName, const T defaultValue) const
+ {
+ const void *pData = TheServer->getDataGetter()(pName);
+ return nullptr == pData ? defaultValue : static_cast<const T>(pData);
+ }
+
+ // Sends a data value from the server back to the application through the server's registered data setter
+ // (GGKServerDataSetter)
+ //
+ // This method is for use with non-pointer types. For pointer types, use `setDataPointer()` instead.
+ //
+ // This method is intended to be used in the server description. An example usage would be:
+ //
+ // self.setDataValue("battery/level", batteryLevel);
+ template<typename T>
+ bool setDataValue(const char *pName, const T value) const
+ {
+ return TheServer->getDataSetter()(pName, static_cast<const void *>(&value)) != 0;
+ }
+
+ // Sends a data pointer from the server back to the application through the server's registered data setter
+ // (GGKServerDataSetter)
+ //
+ // This method is for use with pointer types. For non-pointer types, use `setDataValue()` instead.
+ //
+ // This method is intended to be used in the server description. An example usage would be:
+ //
+ // self.setDataPointer("text/string", stringFromGVariantByteArray(pAyBuffer).c_str());
+ template<typename T>
+ bool setDataPointer(const char *pName, const T pointer) const
+ {
+ return TheServer->getDataSetter()(pName, static_cast<const void *>(pointer)) != 0;
+ }
+
+ // When responding to a ReadValue method, we need to return a GVariant value in the form "(ay)" (a tuple containing an array of
+ // bytes). This method will simplify this slightly by wrapping a GVariant of the type "ay" and wrapping it in a tuple before
+ // sending it off as the method response.
+ //
+ // This is the generalized form that accepts a GVariant *. There is a templated helper method (`methodReturnValue()`) that accepts
+ // common types.
+ void methodReturnVariant(GDBusMethodInvocation *pInvocation, GVariant *pVariant, bool wrapInTuple = false) const;
+
+ // When responding to a ReadValue method, we need to return a GVariant value in the form "(ay)" (a tuple containing an array of
+ // bytes). This method will simplify this slightly by wrapping a GVariant of the type "ay" and wrapping it in a tuple before
+ // sending it off as the method response.
+ //
+ // This is a templated helper method that only works with common types. For a more generic form which can be used for custom
+ // types, see `methodReturnVariant()'.
+ template<typename T>
+ void methodReturnValue(GDBusMethodInvocation *pInvocation, T value, bool wrapInTuple = false) const
+ {
+ GVariant *pVariant = Utils::gvariantFromByteArray(value);
+ methodReturnVariant(pInvocation, pVariant, wrapInTuple);
+ }
+
+ // Locates a `GattProperty` within the interface
+ //
+ // This method returns a pointer to the property or nullptr if not found
+ const GattProperty *findProperty(const std::string &name) const;
+
+ // Internal method used to generate introspection XML used to describe our services on D-Bus
+ virtual std::string generateIntrospectionXML(int depth) const;
+
+protected:
+
+ std::list<GattProperty> properties;
+};
diff --git a/src/GattProperty.cpp b/src/GattProperty.cpp
new file mode 100644
index 0000000..b829ac8
--- /dev/null
+++ b/src/GattProperty.cpp
@@ -0,0 +1,185 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// A GATT Property is simply a name/value pair.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This class is intended to be used within the server description. For an explanation of how this class is used, see the detailed
+// description in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <gio/gio.h>
+#include <string>
+
+#include "Utils.h"
+#include "GattProperty.h"
+
+// Constructs a named property
+//
+// In general, properties should not be constructed directly as properties are typically instanticated by adding them to to an
+// interface using one of the the interface's `addProperty` methods.
+GattProperty::GattProperty(const std::string &name, GVariant *pValue, GDBusInterfaceGetPropertyFunc getter, GDBusInterfaceSetPropertyFunc setter)
+: name(name), pValue(pValue), getterFunc(getter), setterFunc(setter)
+{
+}
+
+//
+// Name
+//
+
+// Returns the name of the property
+const std::string &GattProperty::getName() const
+{
+ return name;
+}
+
+// Sets the name of the property
+//
+// In general, this method should not be called directly as properties are typically added to an interface using one of the the
+// interface's `addProperty` methods.
+GattProperty &GattProperty::setName(const std::string &name)
+{
+ this->name = name;
+ return *this;
+}
+
+//
+// Value
+//
+
+// Returns the property's value
+const GVariant *GattProperty::getValue() const
+{
+ return pValue;
+}
+
+// Sets the property's value
+//
+// In general, this method should not be called directly as properties are typically added to an interface using one of the the
+// interface's `addProperty` methods.
+GattProperty &GattProperty::setValue(GVariant *pValue)
+{
+ this->pValue = pValue;
+ return *this;
+}
+
+//
+// Callbacks to get/set this property
+//
+
+// Internal use method to retrieve the getter delegate method used to return custom values for a property
+GDBusInterfaceGetPropertyFunc GattProperty::getGetterFunc() const
+{
+ return getterFunc;
+}
+
+// Internal use method to set the getter delegate method used to return custom values for a property
+//
+// In general, this method should not be called directly as properties are typically added to an interface using one of the the
+// interface's `addProperty` methods.
+GattProperty &GattProperty::setGetterFunc(GDBusInterfaceGetPropertyFunc func)
+{
+ getterFunc = func;
+ return *this;
+}
+
+// Internal use method to retrieve the setter delegate method used to return custom values for a property
+GDBusInterfaceSetPropertyFunc GattProperty::getSetterFunc() const
+{
+ return setterFunc;
+}
+
+// Internal use method to set the setter delegate method used to return custom values for a property
+//
+// In general, this method should not be called directly as properties are typically added to an interface using one of the the
+// interface's `addProperty` methods.
+GattProperty &GattProperty::setSetterFunc(GDBusInterfaceSetPropertyFunc func)
+{
+ setterFunc = func;
+ return *this;
+}
+
+// Internal method used to generate introspection XML used to describe our services on D-Bus
+std::string GattProperty::generateIntrospectionXML(int depth) const
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ std::string xml = std::string();
+
+ GVariant *pValue = const_cast<GVariant *>(getValue());
+ const gchar *pType = g_variant_get_type_string(pValue);
+ xml += prefix + "<property name='" + getName() + "' type='" + pType + "' access='read'>\n";
+
+ if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_BOOLEAN))
+ {
+ xml += prefix + " <annotation name='name' value='" + (g_variant_get_boolean(pValue) != 0 ? "true":"false") + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT16))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_int16(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT16))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_uint16(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT32))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_int32(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT32))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_uint32(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT64))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_int64(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT64))
+ {
+ xml += prefix + " <annotation name='name' value='" + std::to_string(g_variant_get_uint64(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_DOUBLE))
+ {
+ xml += prefix + " <annotation value='" + std::to_string(g_variant_get_double(pValue)) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_STRING))
+ {
+ xml += prefix + " <annotation name='name' value='" + g_variant_get_string(pValue, nullptr) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_OBJECT_PATH))
+ {
+ xml += prefix + " <annotation name='name' value='" + g_variant_get_string(pValue, nullptr) + "' />\n";
+ }
+ else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_BYTESTRING))
+ {
+ xml += prefix + " <annotation name='name' value='" + g_variant_get_bytestring(pValue) + "' />\n";
+ }
+
+ xml += prefix + "</property>\n";
+
+ return xml;
+}
diff --git a/src/GattProperty.h b/src/GattProperty.h
new file mode 100644
index 0000000..b340945
--- /dev/null
+++ b/src/GattProperty.h
@@ -0,0 +1,106 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// A GATT Property is simply a name/value pair.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of GattProperty.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+
+struct DBusObjectPath;
+
+// Representation of a GATT Property
+struct GattProperty
+{
+ // Constructs a named property
+ //
+ // In general, properties should not be constructed directly as properties are typically instanticated by adding them to to an
+ // interface using one of the the interface's `addProperty` methods.
+ GattProperty(const std::string &name, GVariant *pValue, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr);
+
+ //
+ // Name
+ //
+
+ // Returns the name of the property
+ const std::string &getName() const;
+
+ // Sets the name of the property
+ //
+ // In general, this method should not be called directly as properties are typically added to an interface using one of the the
+ // interface's `addProperty` methods.
+ GattProperty &setName(const std::string &name);
+
+ //
+ // Value
+ //
+
+ // Returns the property's value
+ const GVariant *getValue() const;
+
+ // Sets the property's value
+ //
+ // In general, this method should not be called directly as properties are typically added to an interface using one of the the
+ // interface's `addProperty` methods.
+ GattProperty &setValue(GVariant *pValue);
+
+ //
+ // Callbacks to get/set this property
+ //
+
+ // Internal use method to retrieve the getter delegate method used to return custom values for a property
+ GDBusInterfaceGetPropertyFunc getGetterFunc() const;
+
+ // Internal use method to set the getter delegate method used to return custom values for a property
+ //
+ // In general, this method should not be called directly as properties are typically added to an interface using one of the the
+ // interface's `addProperty` methods.
+ GattProperty &setGetterFunc(GDBusInterfaceGetPropertyFunc func);
+
+ // Internal use method to retrieve the setter delegate method used to return custom values for a property
+ GDBusInterfaceSetPropertyFunc getSetterFunc() const;
+
+ // Internal use method to set the setter delegate method used to return custom values for a property
+ //
+ // In general, this method should not be called directly as properties are typically added to an interface using one of the the
+ // interface's `addProperty` methods.
+ GattProperty &setSetterFunc(GDBusInterfaceSetPropertyFunc func);
+
+ // Internal method used to generate introspection XML used to describe our services on D-Bus
+ std::string generateIntrospectionXML(int depth) const;
+
+private:
+
+ std::string name;
+ GVariant *pValue;
+ GDBusInterfaceGetPropertyFunc getterFunc;
+ GDBusInterfaceSetPropertyFunc setterFunc;
+};
diff --git a/src/GattService.cpp b/src/GattService.cpp
new file mode 100644
index 0000000..b5b0b84
--- /dev/null
+++ b/src/GattService.cpp
@@ -0,0 +1,93 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// A GATT Service, used to add services to a Bluetooth server
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A GATT Service is really a collection of
+//
+// This class is intended to be used within the server description. For an explanation of how this class is used, see the detailed
+// description in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "GattService.h"
+#include "GattInterface.h"
+#include "DBusObject.h"
+#include "GattCharacteristic.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Representation of a Bluetooth GATT Service
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Standard constructor
+GattService::GattService(DBusObject &owner, const std::string &name)
+: GattInterface(owner, name)
+{
+}
+
+// Returning the parent pops us one level up the hierarchy
+DBusObject &GattService::gattServiceEnd()
+{
+ return getOwner().getParent();
+}
+
+// Convenience functions to add a GATT characteristic to the hierarchy
+//
+// We simply add a new child at the given path and add an interface configured as a GATT characteristic to it. The
+// new characteristic is declared with a UUID and a variable argument list of flags (in string form.) For a complete and
+// up-to-date list of flag values, see: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+//
+// At the time of this writing, the list of flags is as follows:
+//
+// "broadcast"
+// "read"
+// "write-without-response"
+// "write"
+// "notify"
+// "indicate"
+// "authenticated-signed-writes"
+// "reliable-write"
+// "writable-auxiliaries"
+// "encrypt-read"
+// "encrypt-write"
+// "encrypt-authenticated-read"
+// "encrypt-authenticated-write"
+// "secure-read" (Server only)
+// "secure-write" (Server only)
+//
+GattCharacteristic &GattService::gattCharacteristicBegin(const std::string &pathElement, const GattUuid &uuid, const std::vector<const char *> &flags)
+{
+ DBusObject &child = owner.addChild(DBusObjectPath(pathElement));
+ GattCharacteristic &characteristic = *child.addInterface(std::make_shared<GattCharacteristic>(child, *this, "org.bluez.GattCharacteristic1"));
+ characteristic.addProperty<GattCharacteristic>("UUID", uuid);
+ characteristic.addProperty<GattCharacteristic>("Service", owner.getPath());
+ characteristic.addProperty<GattCharacteristic>("Flags", flags);
+ return characteristic;
+}
diff --git a/src/GattService.h b/src/GattService.h
new file mode 100644
index 0000000..b9e15ba
--- /dev/null
+++ b/src/GattService.h
@@ -0,0 +1,99 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// A GATT Service, used to add services to a Bluetooth server
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A GATT Service is really a collection of
+//
+// This class is intended to be used within the server description. For an explanation of how this class is used, see the detailed
+// description in Server.cpp.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+#include <list>
+
+#include "TickEvent.h"
+#include "GattInterface.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattService;
+struct GattCharacteristic;
+struct GattProperty;
+struct DBusObject;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Representation of a Bluetooth GATT Service
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct GattService : GattInterface
+{
+ // Our interface type
+ static constexpr const char *kInterfaceType = "GattService";
+
+ // Standard constructor
+ GattService(DBusObject &owner, const std::string &name);
+
+ virtual ~GattService() {}
+
+ // Returning the parent pops us one level up the hierarchy
+ DBusObject &gattServiceEnd();
+
+ // Convenience functions to add a GATT characteristic to the hierarchy
+ //
+ // We simply add a new child at the given path and add an interface configured as a GATT characteristic to it. The
+ // new characteristic is declared with a UUID and a variable argument list of flags (in string form.) For a complete and
+ // up-to-date list of flag values, see: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+ //
+ // At the time of this writing, the list of flags is as follows:
+ //
+ // "broadcast"
+ // "read"
+ // "write-without-response"
+ // "write"
+ // "notify"
+ // "indicate"
+ // "authenticated-signed-writes"
+ // "reliable-write"
+ // "writable-auxiliaries"
+ // "encrypt-read"
+ // "encrypt-write"
+ // "encrypt-authenticated-read"
+ // "encrypt-authenticated-write"
+ // "secure-read" (Server only)
+ // "secure-write" (Server only)
+ //
+ GattCharacteristic &gattCharacteristicBegin(const std::string &pathElement, const GattUuid &uuid, const std::vector<const char *> &flags);
+
+ // Returns a string identifying the type of interface
+ virtual const std::string getInterfaceType() const { return GattService::kInterfaceType; }
+};
diff --git a/src/GattUuid.h b/src/GattUuid.h
new file mode 100644
index 0000000..ee89332
--- /dev/null
+++ b/src/GattUuid.h
@@ -0,0 +1,282 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This class manages the creation of properly formatted GATT UUIDs
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// A GATT UUID represents a unique identifier for a GATT service, characteristic or descriptor. UUIDs allow clients to recognize
+// the purpose of a service (battery level, temperature data, etc.) Similarly, they are used to identify characteristics (the
+// battery percentage or actual temperature value) and descriptors (for example, the format of a characteristic data item.)
+//
+// GATT UUIDs come in three flavors: 16-bit, 32-bit and 128-bit. In practice, you will see two different kinds of UUIDs: a short
+// UUID (ex: "2901") and a long UUID (ex: "00000002-1E3C-FAD4-74E2-97A033F1BFAA").
+//
+// The short form refer to 16-bit UUID that is assigned to a standard interface defined as part of the Bluetooth Wireless Standard.
+// The short UUID "2901" refers to a standard (assigned by the Bluetooth Wireless Standard) descriptor named "Characteristic User
+// Description". These are commonly called "Assigned Numbers".
+//
+// In fact, all UUIDs are 128-bit values. The 16-bit form is just an abbreviated way of specifying a standard interface that uses
+// what Bluetooth calls the "Base Universally Unique Identifier (UUID)". This UUID is defined by the standard as
+// "00000000-0000-1000-8000-00805f9b34fb". The 16-bit value "2901" would be inserted into the Base UUID like so:
+// "00002901-0000-1000-8000-00805f9b34fb".
+//
+// When defining a custom service (or characteristic or descriptor) UUIDs are typically generated by the developer. These UUIDs are
+// then used by the client software to recognize the custom service (or characteristic or descriptor.)
+//
+// The `GattUuid` class is a helper class for creating instances of UUIDs. By instantiating a GattUuid with a 4-character string,
+// the GattUuid will treat the string as a 16-bit UUID and wrap it with the Base Universally Unique Identifier. Similarly, an 8-
+// character string will be treated as a standard 32-bit UUID, also wrapping it with the Base Universally Unique Identifier. A 32-
+// character string will be treated as a 128-bit GATT UUID.
+//
+// When specifying your UUIDs, feel free to use the format that suits you best (with or without dashes, dots in place of dashes,
+// etc.) The string will first be sanitized, removing all non-hex characters, and then re-formatted appropriately with the dashes
+// in their standard places: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".
+//
+// By represetng a UUID in a custom class like this, we are able to give a UUID its own type, and use type safety to ensure that we
+// don't confuse regular strings with GATT UUIDs throughout the codebase.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <string>
+#include <algorithm>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <iostream>
+#include "Logger.h"
+
+// "0000180A-0000-1000-8000-00805f9b34fb"
+struct GattUuid
+{
+ static constexpr const char *kGattStandardUuidPart1Prefix = "0000";
+ static constexpr const char *kGattStandardUuidSuffix = "-0000-1000-8000-00805f9b34fb";
+
+ // Construct a GattUuid from a partial or complete string UUID
+ //
+ // This constructor will do the best it can with the data it is given. It will first clean the input by removing all non-hex
+ // characters (see `clean`) and the remaining characters are processed in the following way:
+ //
+ // 4-character string is treated as a 16-bit UUID
+ // 8-character string is treated as a 32-bit UUID
+ // 32-character string is treated as a 128-bit UUID
+ //
+ // If the input string is not one of the above lengths, the UUID will be left uninitialized as an empty string with a bit
+ // count of 0.
+ //
+ // Finally, dashes are inserted into the string at the appropriate locations (see `dashify`).
+ GattUuid(const char *strUuid)
+ {
+ *this = GattUuid(std::string(strUuid));
+ }
+
+ // Construct a GattUuid from a partial or complete string UUID
+ //
+ // This constructor will do the best it can with the data it is given. It will first clean the input by removing all non-hex
+ // characters (see `clean`) and the remaining characters are processed in the following way:
+ //
+ // 4-character string is treated as a 16-bit UUID
+ // 8-character string is treated as a 32-bit UUID
+ // 32-character string is treated as a 128-bit UUID
+ //
+ // If the input string is not one of the above lengths, the UUID will be left uninitialized as an empty string with a bit
+ // count of 0.
+ //
+ // Finally, dashes are inserted into the string at the appropriate locations (see `dashify`).
+ GattUuid(std::string strUuid)
+ {
+ // Clean the string
+ strUuid = clean(strUuid);
+
+ // It's hex, so each character represents 4 bits
+ bitCount = strUuid.length() * 4;
+
+ if (bitCount == 16)
+ {
+ strUuid = kGattStandardUuidPart1Prefix + strUuid + kGattStandardUuidSuffix;
+ }
+ else if (bitCount == 32)
+ {
+ strUuid += kGattStandardUuidSuffix;
+ }
+ else if (bitCount != 128)
+ {
+ bitCount = 0;
+ strUuid = "";
+ }
+
+ uuid = dashify(strUuid);
+ }
+
+ // Constructs a GattUuid from a 16-bit Uuid value
+ //
+ // The result will take the form:
+ //
+ // 0000????-0000-1000-8000-00805f9b34fb
+ //
+ // ...where "????" is replaced by the 4-digit hex value of `part`
+ GattUuid(const uint16_t part)
+ {
+ bitCount = 16;
+ char partStr[5];
+ snprintf(partStr, sizeof(partStr), "%04x", part);
+ uuid = std::string(kGattStandardUuidPart1Prefix) + partStr + kGattStandardUuidSuffix;
+ }
+
+ // Constructs a GattUuid from a 32-bit Uuid value
+ //
+ // The result will take the form:
+ //
+ // ????????-0000-1000-8000-00805f9b34fb
+ //
+ // ...where "????????" is replaced by the 8-digit hex value of `part`
+ GattUuid(const uint32_t part)
+ {
+ bitCount = 32;
+ char partStr[9];
+ snprintf(partStr, sizeof(partStr), "%04x", part);
+ uuid = std::string(partStr) + kGattStandardUuidSuffix;
+ }
+
+ // Constructs a GattUuid from a 5-part set of input values
+ //
+ // The result will take the form:
+ //
+ // 11111111-2222-3333-4444-555555555555
+ //
+ // ...where each digit represents the part from which its hex digits will be pulled from.
+ //
+ // Note that `part5` is a 48-bit value and will be masked such that only the lower 48-bits of `part5` are used with all other
+ // bits ignored.
+ GattUuid(const uint32_t part1, const uint16_t part2, const uint16_t part3, const uint16_t part4, const uint64_t part5)
+ {
+ bitCount = 128;
+ char partsStr[37];
+ uint32_t part5a = (part5 >> 4) & 0xffffffff;
+ uint32_t part5b = part5 & 0xffff;
+ snprintf(partsStr, sizeof(partsStr), "%08x-%04x-%04x-%04x-%08x%04x", part1, part2, part3, part4, part5a, part5b);
+ uuid = std::string(partsStr);
+ }
+
+ // Returns the bit count of the input when the GattUuid was constructed. Valid values are 16, 32, 128.
+ //
+ // If the GattUuid was constructed imporperly, this method will return 0.
+ int getBitCount() const
+ {
+ return bitCount;
+ }
+
+ // Returns the 16-bit portion of the GATT UUID or an empty string if the GattUuid was not created correctly
+ //
+ // Note that a 16-bit GATT UUID is only valid for standarg GATT UUIDs (prefixed with "0000" and ending with
+ // "0000-1000-8000-00805f9b34fb").
+ std::string toString16() const
+ {
+ if (uuid.empty()) { return uuid; }
+ return uuid.substr(4, 4);
+ }
+
+ // Returns the 32-bit portion of the GATT UUID or an empty string if the GattUuid was not created correctly
+ //
+ // Note that a 32-bit GATT UUID is only valid for standarg GATT UUIDs (ending with "0000-1000-8000-00805f9b34fb").
+ std::string toString32() const
+ {
+ if (uuid.empty()) { return uuid; }
+ return uuid.substr(0, 8);
+ }
+
+ // Returns the full 128-bit GATT UUID or an empty string if the GattUuid was not created correctly
+ std::string toString128() const
+ {
+ return uuid;
+ }
+
+ // Returns a string form of the UUID, based on the bit count used when the UUID was created. A 16-bit UUID will return a
+ // 4-character hex string. A 32-bit UUID will return an 8-character hex string. Otherwise the UUID is assumed to be 128 bits
+ // (which is only true if it was created correctly) and the full UUID is returned
+ std::string toString() const
+ {
+ if (bitCount == 16) return toString16();
+ if (bitCount == 32) return toString32();
+ return toString128();
+ }
+
+ // Returns a new string containing the lower case contents of `strUuid` with all non-hex characters (0-9, A-F) removed
+ static std::string clean(const std::string &strUuid)
+ {
+ if (strUuid.empty()) return strUuid;
+
+ // Lower case
+ std::string cleanStr = strUuid;
+ std::transform(cleanStr.begin(), cleanStr.end(), cleanStr.begin(), ::tolower);
+
+ // Remove all non-hex characters
+ cleanStr.erase
+ (
+ std::remove_if
+ (
+ cleanStr.begin(),
+ cleanStr.end(),
+ [](char c)
+ {
+ return !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+ }
+ ), cleanStr.end()
+ );
+
+ return cleanStr;
+ }
+
+ // Returns a clean string (see `clean`) that has dashes ('-') inserted at the proper locations. If the string is not a full
+ // UUID, this routine will cleanup and add as many dashes as possible until it runs out of characters.
+ //
+ // Example transforms:
+ //
+ // "0000180A-0000-1000-8000-00805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb"
+ // "0000180A00001000800000805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb"
+ // "0000180A/0000.1000_zzzzzz_8000+00805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb"
+ // "0000180A" -> "0000180a"
+ // "0000180A.0000.100" -> "0000180a-0000-100"
+ // "rqzp" -> ""
+ //
+ static std::string dashify(const std::string &str)
+ {
+ // Ensure we have a clean string to start with
+ std::string dashed = clean(str);
+
+ // Add each dash, provided there are enough characters
+ if (dashed.length() > 8) { dashed.insert(8, 1, '-'); }
+ if (dashed.length() > 13) { dashed.insert(13, 1, '-'); }
+ if (dashed.length() > 18) { dashed.insert(18, 1, '-'); }
+ if (dashed.length() > 23) { dashed.insert(23, 1, '-'); }
+ return dashed;
+ }
+
+private:
+
+ std::string uuid;
+ int bitCount;
+};
diff --git a/src/Globals.h b/src/Globals.h
new file mode 100644
index 0000000..02b7fa0
--- /dev/null
+++ b/src/Globals.h
@@ -0,0 +1,71 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// System-wide globals.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// The globals below define the name of the server, along with any name-based items (such as errors.)
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <string>
+
+// The name for this controller, as advertised over LE
+//
+// IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set BOTH
+// kCustomGlobalAdvertisingName and kCustomGlobalAdvertisingShortName to as empty string ("") to prevent setting the advertising
+// name.
+#define kCustomGlobalAdvertisingName std::string("Gobbledegook")
+
+// The short name for this controller, as advertised over LE
+//
+// According to the spec, the short name is used in case the full name doesn't fit within Extended Inquiry Response (EIR) or
+// Advertising Data (AD).
+//
+// IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set BOTH
+// kCustomGlobalAdvertisingName and kCustomGlobalAdvertisingShortName to as empty string ("") to prevent setting the advertising
+// name.
+#define kCustomGlobalAdvertisingShortName std::string("Gobbledegook")
+
+// The name of our server (collectino of services)
+//
+// This is used to build the path for our Bluetooth services (and we'll go ahead and use it as the owned name as well for
+// consistency.)
+#define kServerName std::string("gobbledegook")
+
+// Our owned name
+//
+// D-Bus uses owned names to locate servers on the bus. Think of this as a namespace within D-Bus. Building this with the server
+// name, though it's not necessary to do so. We can call this anything we want, really.
+#define kServerOwnedName (std::string("com.") + kServerName)
+
+//
+// Custom defined errors
+//
+// In order to avoid confusion, we should use the owned name here, so errors are like extensions to that name. This way, if a
+// client gets one of these errors, it'll be clear which server it came from.
+#define kErrorNotImplemented (kServerOwnedName + ".NotImplemented")
diff --git a/src/Gobbledegook.cpp b/src/Gobbledegook.cpp
new file mode 100644
index 0000000..f571be7
--- /dev/null
+++ b/src/Gobbledegook.cpp
@@ -0,0 +1,433 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// The methods in this file represent the complete external C interface for a Gobbledegook server.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Although Gobbledegook requires customization (see Server.cpp), it still maintains a public interface (this file.) This keeps the
+// interface to the server simple and compact, while also allowing the server to be built as a library if the developer so chooses.
+//
+// As an alternative, it is also possible to publish customized Gobbledegook servers, allowing others to use the server through the
+// public interface defined in this file.
+//
+// In addition, this interface is compatible with the C language, allowing non-C++ programs to interface with Gobbledegook, even
+// though Gobbledgook is a C++ codebase. This should also simplify the use of this interface with other languages, such as Swift.
+//
+// The interface below has the following categories:
+//
+// Log registration - used to register methods that accept all Gobbledegook logs
+// Update queue management - used for notifying the server that data has been updated
+// Server state - used to track the server's current running state and health
+// Server control - running and stopping the server
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <string.h>
+#include <string>
+#include <thread>
+#include <deque>
+#include <mutex>
+
+#include "Init.h"
+#include "Logger.h"
+#include "Server.h"
+
+// During initialization, we'll check for complation at this interval
+static const int kMaxAsyncInitCheckIntervalMS = 10;
+
+// Our server thread
+static std::thread serverThread;
+
+// The current server state
+volatile static GGKServerRunState serverRunState = EUninitialized;
+
+// The current server health
+volatile static GGKServerHealth serverHealth = EOk;
+
+// Our update queue
+typedef std::tuple<std::string, std::string> QueueEntry;
+std::deque<QueueEntry> updateQueue;
+std::mutex updateQueueMutex;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _ _ _ _ _
+// | | ___ __ _ _ __ ___ __ _(_)___| |_ _ __ __ _| |_(_) ___ _ ___
+// | | / _ \ / _` | | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_ |
+// | |__| (_) | (_| | | | | __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | |
+// |_____\___/ \__, | |_| \___|\__, |_|___/\__|_| \__,_|\__|_|\___/|_| |_|
+// |___/ |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+void ggkLogRegisterDebug(GGKLogReceiver receiver) { Logger::registerDebugReceiver(receiver); }
+void ggkLogRegisterInfo(GGKLogReceiver receiver) { Logger::registerInfoReceiver(receiver); }
+void ggkLogRegisterStatus(GGKLogReceiver receiver) { Logger::registerStatusReceiver(receiver); }
+void ggkLogRegisterWarn(GGKLogReceiver receiver) { Logger::registerWarnReceiver(receiver); }
+void ggkLogRegisterError(GGKLogReceiver receiver) { Logger::registerErrorReceiver(receiver); }
+void ggkLogRegisterFatal(GGKLogReceiver receiver) { Logger::registerFatalReceiver(receiver); }
+void ggkLogRegisterTrace(GGKLogReceiver receiver) { Logger::registerTraceReceiver(receiver); }
+void ggkLogRegisterAlways(GGKLogReceiver receiver) { Logger::registerAlwaysReceiver(receiver); }
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _ _ _ _ _
+// | | | |_ __ __| | __ _| |_ ___ __ _ _ _ ___ _ _ ___ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
+// | | | | '_ \ / _` |/ _` | __/ _ \ / _` | | | |/ _ \ | | |/ _ \ | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
+// | |_| | |_) | (_| | (_| | || __/ | (_| | |_| | __/ |_| | __/ | | | | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
+// \___/| .__/ \__,_|\__,_|\__\___| \__, |\__,_|\___|\__,_|\___| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
+// |_| |_| |___/
+//
+// Push/pop update notifications onto a queue. As these methods are where threads collide (i.e., this is how they communicate),
+// these methods are thread-safe.
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Adds an update to the front of the queue for a characteristic at the given object path
+//
+// Returns non-zero value on success or 0 on failure.
+int ggkNofifyUpdatedCharacteristic(const char *pObjectPath)
+{
+ return ggkPushUpdateQueue(pObjectPath, "org.bluez.GattCharacteristic1") != 0;
+}
+
+// Adds an update to the front of the queue for a descriptor at the given object path
+//
+// Returns non-zero value on success or 0 on failure.
+int ggkNofifyUpdatedDescriptor(const char *pObjectPath)
+{
+ return ggkPushUpdateQueue(pObjectPath, "org.bluez.GattDescriptor1") != 0;
+}
+
+// Adds a named update to the front of the queue. Generally, this routine should not be used directly. Instead, use the
+// `ggkNofifyUpdatedCharacteristic()` instead.
+//
+// Returns non-zero value on success or 0 on failure.
+int ggkPushUpdateQueue(const char *pObjectPath, const char *pInterfaceName)
+{
+ QueueEntry t(pObjectPath, pInterfaceName);
+
+ std::lock_guard<std::mutex> guard(updateQueueMutex);
+ updateQueue.push_front(t);
+ return 1;
+}
+
+// Get the next update from the back of the queue and returns the element in `element` as a string in the format:
+//
+// "com/object/path|com.interface.name"
+//
+// If the queue is empty, this method returns `0` and does nothing.
+//
+// `elementLen` is the size of the `element` buffer in bytes. If the resulting string (including the null terminator) will not
+// fit within `elementLen` bytes, the method returns `-1` and does nothing.
+//
+// If `keep` is set to non-zero, the entry is not removed and will be retrieved again on the next call. Otherwise, the element
+// is removed.
+//
+// Returns 1 on success, 0 if the queue is empty, -1 on error (such as the length too small to store the element)
+int ggkPopUpdateQueue(char *pElementBuffer, int elementLen, int keep)
+{
+ std::string result;
+
+ {
+ std::lock_guard<std::mutex> guard(updateQueueMutex);
+
+ // Check for an empty queue
+ if (updateQueue.empty()) { return 0; }
+
+ // Get the last element
+ QueueEntry t = updateQueue.back();
+
+ // Get the result string
+ result = std::get<0>(t) + "|" + std::get<1>(t);
+
+ // Ensure there's enough room for it
+ if (result.length() + 1 > static_cast<size_t>(elementLen)) { return -1; }
+
+ if (keep == 0)
+ {
+ updateQueue.pop_back();
+ }
+ }
+
+ // Copy the element string
+ memcpy(pElementBuffer, result.c_str(), result.length() + 1);
+
+ return 1;
+}
+
+// Returns 1 if the queue is empty, otherwise 0
+int ggkUpdateQueueIsEmpty()
+{
+ std::lock_guard<std::mutex> guard(updateQueueMutex);
+ return updateQueue.empty() ? 1 : 0;
+}
+
+// Returns the number of entries waiting in the queue
+int ggkUpdateQueueSize()
+{
+ std::lock_guard<std::mutex> guard(updateQueueMutex);
+ return updateQueue.size();
+}
+
+// Removes all entries from the queue
+void ggkUpdateQueueClear()
+{
+ std::lock_guard<std::mutex> guard(updateQueueMutex);
+ updateQueue.clear();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _
+// | _ \ _ _ _ __ ___| |_ __ _| |_ ___
+// | |_) | | | | '_ \ / __| __/ _` | __/ _ )
+// | _ <| |_| | | | | \__ \ || (_| | || __/
+// |_| \_\\__,_|_| |_| |___/\__\__,_|\__\___|
+//
+// Methods for maintaining and reporting the state of the server
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Retrieve the current running state of the server
+//
+// See `GGKServerRunState` (enumeration) for more information.
+GGKServerRunState ggkGetServerRunState()
+{
+ return serverRunState;
+}
+
+// Convert a `GGKServerRunState` into a human-readable string
+const char *ggkGetServerRunStateString(GGKServerRunState state)
+{
+ switch(state)
+ {
+ case EUninitialized: return "Uninitialized";
+ case EInitializing: return "Initializing";
+ case ERunning: return "Running";
+ case EStopping: return "Stopping";
+ case EStopped: return "Stopped";
+ default: return "Unknown";
+ }
+}
+
+// Internal method to set the run state of the server
+void setServerRunState(GGKServerRunState newState)
+{
+ Logger::status(SSTR << "** SERVER RUN STATE CHANGED: " << ggkGetServerRunStateString(serverRunState) << " -> " << ggkGetServerRunStateString(newState));
+ serverRunState = newState;
+}
+
+// Convenience method to check ServerRunState for a running server
+int ggkIsServerRunning()
+{
+ return serverRunState == ERunning ? 1 : 0;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _ _
+// / ___| ___ _ ____ _____ _ __ | |__ ___ __ _| | |_| |___
+// \___ \ / _ \ '__\ \ / / _ \ '__| | '_ \ / _ \/ _` | | __| '_ |
+// ___) | __/ | \ V / __/ | | | | | __/ (_| | | |_| | | |
+// |____/ \___|_| \_/ \___|_| |_| |_|\___|\__,_|_|\__|_| |_|
+//
+// Methods for maintaining and reporting the health of the server
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Retrieve the current health of the server
+//
+// See `GGKServerHealth` (enumeration) for more information.
+GGKServerHealth ggkGetServerHealth()
+{
+ return serverHealth;
+}
+
+// Convert a `GGKServerHealth` into a human-readable string
+const char *ggkGetServerHealthString(GGKServerHealth state)
+{
+ switch(state)
+ {
+ case EOk: return "Ok";
+ case EFailedInit: return "Failed initialization";
+ case EFailedRun: return "Failed run";
+ default: return "Unknown";
+ }
+}
+
+// Internal method to set the health of the server
+void setServerHealth(GGKServerHealth newState)
+{
+ serverHealth = newState;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _
+// / ___|| |_ ___ _ __ | |_| |__ ___ ___ ___ _ ____ _____ _ __
+// \___ \| __/ _ \| '_ \ | __| '_ \ / _ \ / __|/ _ \ '__\ \ / / _ \ '__|
+// ___) | || (_) | |_) | | |_| | | | __/ \__ \ __/ | \ V / __/ |
+// |____/ \__\___/| .__/ \__|_| |_|\___| |___/\___|_| \_/ \___|_|
+// |_|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Tells the server to begin the shutdown process
+//
+// The shutdown process will interrupt any currently running asynchronous operation and prevent new operations from starting.
+// Once the server has stabilized, its event processing loop is terminated and the server is cleaned up.
+//
+// `ggkGetServerRunState` will return EStopped when shutdown is complete. To block until the shutdown is complete, see
+// `ggkWait()`.
+//
+// Alternatively, you can use `ggkShutdownAndWait()` to request the shutdown and block until the shutdown is complete.
+void ggkTriggerShutdown()
+{
+ shutdown();
+}
+
+// Convenience method to trigger a shutdown (via `ggkTriggerShutdown()`) and also waits for shutdown to complete (via
+// `ggkWait()`)
+int ggkShutdownAndWait()
+{
+ // Tell the server to shut down
+ ggkTriggerShutdown();
+
+ // Block until it has shut down completely
+ return ggkWait();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// __ __ _ _
+// \ \ / /_ _(_) |_ ___ _ __ ___ ___ _ ____ _____ _ __
+// \ \ /\ / / _` | | __| / _ \| '_ \ / __|/ _ \ '__\ \ / / _ \ '__|
+// \ V V / (_| | | |_ | (_) | | | | \__ \ __/ | \ V / __/ |
+// \_/\_/ \__,_|_|\__| \___/|_| |_| |___/\___|_| \_/ \___|_|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Blocks for up to maxAsyncInitTimeoutMS milliseconds until the server shuts down.
+//
+// If shutdown is successful, this method will return a non-zero value. Otherwise, it will return 0.
+//
+// If the server fails to stop for some reason, the thread will be killed.
+//
+// Typically, a call to this method would follow `ggkTriggerShutdown()`.
+int ggkWait()
+{
+ try
+ {
+ serverThread.join();
+ }
+ catch(std::system_error &ex)
+ {
+ if (ex.code() == std::errc::invalid_argument)
+ {
+ Logger::warn(SSTR << "Server thread was not joinable during stop(): " << ex.what());
+ }
+ else if (ex.code() == std::errc::no_such_process)
+ {
+ Logger::warn(SSTR << "Server thread was not valid during stop(): " << ex.what());
+ }
+ else if (ex.code() == std::errc::resource_deadlock_would_occur)
+ {
+ Logger::warn(SSTR << "Deadlock avoided in call to stop() (did the server thread try to stop itself?): " << ex.what());
+ }
+ else
+ {
+ Logger::warn(SSTR << "Unknown system_error code (" << ex.code() << "): " << ex.what());
+ }
+ }
+
+ // Return true if we're stopped, otherwise false
+ return ggkGetServerRunState() == EStopped ? 1 : 0;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _ _
+// / ___|| |_ __ _ _ __| |_ | |_| |__ ___ ___ ___ _ ____ _____ _ __
+// \___ \| __/ _` | '__| __| | __| '_ \ / _ \ / __|/ _ \ '__\ \ / / _ \ '__|
+// ___) | || (_| | | | |_ | |_| | | | __/ \__ \ __/ | \ V / __/ |
+// |____/ \__\__,_|_| \__| \__|_| |_|\___| |___/\___|_| \_/ \___|_|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Set the server state to 'EInitializing' and then immediately create a server thread and initiate the server's async
+// processing on the server thread.
+//
+// At that point the current thread will block for maxAsyncInitTimeoutMS milliseconds or until initialization completes.
+//
+// If initialization was successful, the method will return a non-zero value with the server running on its own thread in
+// 'runServerThread'.
+//
+// If initialization was unsuccessful, this method will continue to block until the server has stopped. This method will then
+// return 0.
+//
+// IMPORTANT:
+//
+// The data setter uses void* types to allow receipt of unknown data types from the server. Ensure that you do not store these
+// pointers. Copy the data before returning from your getter delegate.
+//
+// Similarly, the pointer to data returned to the data getter should point to non-volatile memory so that the server can use it
+// safely for an indefinite period of time.
+int ggkStart(GGKServerDataGetter getter, GGKServerDataSetter setter, int maxAsyncInitTimeoutMS)
+{
+ // Allocate our server
+ TheServer = std::make_shared<Server>(getter, setter);
+
+ // Start our server thread
+ try
+ {
+ serverThread = std::thread(runServerThread);
+ }
+ catch(std::system_error &ex)
+ {
+ if (ex.code() == std::errc::resource_unavailable_try_again)
+ {
+ Logger::error(SSTR << "Server thread was unable to start during start(): " << ex.what());
+
+ setServerRunState(EStopped);
+ return 0;
+ }
+ }
+
+ // Waits for the server to pass the EInitializing state
+ int retryTimeMS = 0;
+ while (retryTimeMS < maxAsyncInitTimeoutMS && ggkGetServerRunState() <= EInitializing)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(kMaxAsyncInitCheckIntervalMS));
+ retryTimeMS += kMaxAsyncInitCheckIntervalMS;
+ }
+
+ // If something went wrong, shut down if we've not already done so
+ if (ggkGetServerRunState() < ERunning || ggkGetServerHealth() != EOk)
+ {
+ setServerHealth(EFailedInit);
+
+ // Stop the server (this is a blocking call)
+ if (!ggkShutdownAndWait())
+ {
+ Logger::warn(SSTR << "Unable to stop the server from error in start()");
+ }
+
+ return 0;
+ }
+
+ // Everything looks good
+ return 1;
+} \ No newline at end of file
diff --git a/src/HciAdapter.cpp b/src/HciAdapter.cpp
new file mode 100644
index 0000000..8e906e6
--- /dev/null
+++ b/src/HciAdapter.cpp
@@ -0,0 +1,384 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Protocol-level code for the Bluetooth Management API, which is used to configure the Bluetooth adapter
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This class is intended for use by `Mgmt` (see Mgmt.cpp).
+//
+// The Bluetooth Management API, is used to configure the Bluetooth adapter (such as enabling LE, setting the device name, etc.)
+// This class uses HciSocket (HciSocket.h) for the raw communications.
+//
+// The information for this implementation (as well as HciSocket.h) came from:
+//
+// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt
+//
+// KNOWN LIMITATIONS:
+//
+// This is far from a complete implementation. I'm not even sure how reliable of an implementation this is. However, I can say with
+// _complete_confidence_ that it works on my machine after numerous minutes of testing.
+//
+// One notable limitation is that this code doesn't work with the Bluetooth Management API in the way it was intended. The Bluetooth
+// Management API treats the sending and receiving of data differently. It receives commands on the HCI socket and acts upon them.
+// It also sends events on the same socket. It is important to note that there is not necessarily a 1:1 correlation from commands
+// received to events generated. For example, an event can be generated when a bluetooth client disconnects, even though no command
+// was sent for which that event is associated.
+//
+// However, for initialization, it seems to be generally safe to treat them as "nearly 1:1". The solution below is to consume all
+// events and look for the event that we're waiting on. This seems to work in my environment (Raspberry Pi) fairly well, but please
+// do use this with caution.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <string.h>
+
+#include "HciAdapter.h"
+#include "HciSocket.h"
+#include "Utils.h"
+#include "Logger.h"
+
+static const int kMinCommandCode = 0x0001;
+static const int kMaxCommandCode = 0x0043;
+static const char *kCommandCodeNames[kMaxCommandCode] =
+{
+ "Read Management Version Information Command", // 0x0001
+ "Read Management Supported Commands Command", // 0x0002
+ "Read Controller Index List Command", // 0x0003
+ "Read Controller Information Command", // 0x0004
+ "Set Powered Command", // 0x0005
+ "Set Discoverable Command", // 0x0006
+ "Set Connectable Command", // 0x0007
+ "Set Fast Connectable Command", // 0x0008
+ "Set Bondable Command", // 0x0009
+ "Set Link Security Command", // 0x000A
+ "Set Secure Simple Pairing Command", // 0x000B
+ "Set High Speed Command", // 0x000C
+ "Set Low Energy Command", // 0x000D
+ "Set Device Class", // 0x000E
+ "Set Local Name Command", // 0x000F
+ "Add UUID Command", // 0x0010
+ "Remove UUID Command", // 0x0011
+ "Load Link Keys Command", // 0x0012
+ "Load Long Term Keys Command", // 0x0013
+ "Disconnect Command", // 0x0014
+ "Get Connections Command", // 0x0015
+ "PIN Code Reply Command", // 0x0016
+ "PIN Code Negative Reply Command", // 0x0017
+ "Set IO Capability Command", // 0x0018
+ "Pair Device Command", // 0x0019
+ "Cancel Pair Device Command", // 0x001A
+ "Unpair Device Command", // 0x001B
+ "User Confirmation Reply Command", // 0x001C
+ "User Confirmation Negative Reply Command", // 0x001D
+ "User Passkey Reply Command", // 0x001E
+ "User Passkey Negative Reply Command", // 0x001F
+ "Read Local Out Of Band Data Command", // 0x0020
+ "Add Remote Out Of Band Data Command", // 0x0021
+ "Remove Remote Out Of Band Data Command", // 0x0022
+ "Start Discovery Command", // 0x0023
+ "Stop Discovery Command", // 0x0024
+ "Confirm Name Command", // 0x0025
+ "Block Device Command", // 0x0026
+ "Unblock Device Command", // 0x0027
+ "Set Device ID Command", // 0x0028
+ "Set Advertising Command", // 0x0029
+ "Set BR/EDR Command", // 0x002A
+ "Set Static Address Command", // 0x002B
+ "Set Scan Parameters Command", // 0x002C
+ "Set Secure Connections Command", // 0x002D
+ "Set Debug Keys Command", // 0x002E
+ "Set Privacy Command", // 0x002F
+ "Load Identity Resolving Keys Command", // 0x0030
+ "Get Connection Information Command", // 0x0031
+ "Get Clock Information Command", // 0x0032
+ "Add Device Command", // 0x0033
+ "Remove Device Command", // 0x0034
+ "Load Connection Parameters Command", // 0x0035
+ "Read Unconfigured Controller Index List Command", // 0x0036
+ "Read Controller Configuration Information Command", // 0x0037
+ "Set External Configuration Command", // 0x0038
+ "Set Public Address Command", // 0x0039
+ "Start Service Discovery Command", // 0x003a
+ "Read Local Out Of Band Extended Data Command", // 0x003b
+ "Read Extended Controller Index List Command", // 0x003c
+ "Read Advertising Features Command", // 0x003d
+ "Add Advertising Command", // 0x003e
+ "Remove Advertising Command", // 0x003f
+ "Get Advertising Size Information Command", // 0x0040
+ "Start Limited Discovery Command", // 0x0041
+ "Read Extended Controller Information Command", // 0x0042
+ // NOTE: The documentation at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt) states that the command
+ // code for "Set Appearance Command" is 0x0042. It also says this about the previous command in the list ("Read Extended
+ // Controller Information Command".) This is likely an error, so I'm following the order of the commands as they appear in the
+ // documentation. This makes "Set Appearance Code" have a command code of 0x0043.
+ "Set Appearance Command", // 0x0043
+};
+
+static const int kMinEventType = 0x0001;
+static const int kMaxEventType = 0x0025;
+static const char *kEventTypeNames[kMaxEventType] =
+{
+ "Command Complete Event", // 0x0001
+ "Command Status Event", // 0x0002
+ "Controller Error Event", // 0x0003
+ "Index Added Event", // 0x0004
+ "Index Removed Event", // 0x0005
+ "New Settings Event", // 0x0006
+ "Class Of Device Changed Event", // 0x0007
+ "Local Name Changed Event", // 0x0008
+ "New Link Key Event", // 0x0009
+ "New Long Term Key Event", // 0x000A
+ "Device Connected Event", // 0x000B
+ "Device Disconnected Event", // 0x000C
+ "Connect Failed Event", // 0x000D
+ "PIN Code Request Event", // 0x000E
+ "User Confirmation Request Event", // 0x000F
+ "User Passkey Request Event", // 0x0010
+ "Authentication Failed Event", // 0x0011
+ "Device Found Event", // 0x0012
+ "Discovering Event", // 0x0013
+ "Device Blocked Event", // 0x0014
+ "Device Unblocked Event", // 0x0015
+ "Device Unpaired Event", // 0x0016
+ "Passkey Notify Event", // 0x0017
+ "New Identity Resolving Key Event", // 0x0018
+ "New Signature Resolving Key Event", // 0x0019
+ "Device Added Event", // 0x001a
+ "Device Removed Event", // 0x001b
+ "New Connection Parameter Event", // 0x001c
+ "Unconfigured Index Added Event", // 0x001d
+ "Unconfigured Index Removed Event", // 0x001e
+ "New Configuration Options Event", // 0x001f
+ "Extended Index Added Event", // 0x0020
+ "Extended Index Removed Event", // 0x0021
+ "Local Out Of Band Extended Data Updated Event", // 0x0022
+ "Advertising Added Event", // 0x0023
+ "Advertising Removed Event", // 0x0024
+ "Extended Controller Information Changed Event" // 0x0025
+};
+
+// Connects the HCI socket if a connection does not already exist
+//
+// If a connection already exists, this method will do nothing and return true.
+//
+// Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect
+//
+// Returns true if the HCI socket is connected (either via a new connection or an existing one), otherwise false
+bool HciAdapter::connect()
+{
+ // Connect if we aren't already connected
+ if (!isConnected() && !hciSocket.connect())
+ {
+ disconnect();
+ return false;
+ }
+
+ return true;
+}
+
+// Returns true if connected to the HCI socket, otherwise false
+//
+// Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect
+bool HciAdapter::isConnected() const
+{
+ return hciSocket.isConnected();
+}
+
+// Disconnects from the HCI Socket
+//
+// If the connection is not connected, this method will do nothing.
+//
+// It isn't necessary to disconnect manually; the HCI socket will get disocnnected automatically upon destruction
+void HciAdapter::disconnect()
+{
+ if (isConnected())
+ {
+ hciSocket.disconnect();
+ }
+}
+
+// Sends a command over the HCI socket
+//
+// If the HCI socket is not connected, it will auto-connect prior to sending the command. In the case of a failed auto-connect,
+// a failure is returned.
+//
+// Returns true on success, otherwise false
+bool HciAdapter::sendCommand(Header &request, ResponseEvent &response, int responseLen)
+{
+ // Auto-connect
+ if (!connect()) { return false; }
+
+ Logger::debug(" + Request header");
+ Logger::debug(SSTR << " + Event code : " << Utils::hex(request.code));
+ Logger::debug(SSTR << " + Controller Id : " << Utils::hex(request.controllerId));
+ Logger::debug(SSTR << " + Data size : " << request.dataSize << " bytes");
+
+ request.toNetwork();
+ uint8_t *pRequest = reinterpret_cast<uint8_t *>(&request);
+ std::vector<uint8_t> requestPacket = std::vector<uint8_t>(pRequest, pRequest + sizeof(request) + request.dataSize);
+ if (!hciSocket.write(requestPacket))
+ {
+ return false;
+ }
+
+ // Read the response for this particular command
+ return readResponse(request.code, response, responseLen);
+}
+
+// Reads a response from the HCI socket
+//
+// Responses are generally triggered by sending commands (see `sendCommand`) but not always. In HCI parlance, a response is
+// actually an event. Performing commands triggers events. There is not always a 1:1 ratio betwee command and event, and a
+// command may trigger different events based on the result of the command.
+//
+// Unlike the other methods in this class, this method does not auto-connect, as this method is private and can only be called
+// from methods that have alreay auto-connected.
+bool HciAdapter::readResponse(uint16_t commandCode, ResponseEvent &response, size_t responseLen) const
+{
+ if (!hciSocket.isConnected()) { return false; }
+
+ std::vector<uint8_t> responsePacket = std::vector<uint8_t>();
+ if (!hciSocket.read(responsePacket))
+ {
+ return false;
+ }
+
+ // Validate our events and remove everything that isn't a command complete or status event
+ if (!filterAndValidateEvents(commandCode, responsePacket))
+ {
+ return false;
+ }
+
+ // Do we have enough to check the event code?
+ if (responsePacket.size() < 2)
+ {
+ Logger::error(SSTR << "Command's response was invalid");
+ return false;
+ }
+
+ // If this wasn't a Command Complete Event, just return an error
+ uint16_t eventCode = Utils::endianToHost(*reinterpret_cast<uint16_t *>(responsePacket.data()));
+ if (eventCode != 1)
+ {
+ return false;
+ }
+
+ // It's a Command Complete event, verify the size is what we expect
+ if (responsePacket.size() != responseLen)
+ {
+ Logger::error(SSTR << "Command's response was " << responsePacket.size() << " bytes but was supposed to be " << responseLen << " bytes");
+ return false;
+ }
+
+ // Copy the response data into the requested structure
+ memcpy(&response, responsePacket.data(), responseLen);
+
+ // Perform endian conversion
+ response.toHost();
+
+ // Log the header information
+ Logger::debug(" + Response event");
+ Logger::debug(SSTR << " + Event code : " << Utils::hex(response.header.code));
+ Logger::debug(SSTR << " + Controller Id : " << Utils::hex(response.header.controllerId));
+ Logger::debug(SSTR << " + Data size : " << response.header.dataSize << " bytes");
+ Logger::debug(SSTR << " + Command code : " << response.commandCode);
+ Logger::debug(SSTR << " + Status : " << Utils::hex(response.status));
+
+ // One last check... let's verify that our data size is what it should be
+ //
+ // Note that we add 3 - this is because there are two fields that always follow the
+ // data size (so they were included in our ResponseEvent structure) even
+ // though they are not counted as part of the size, so we account for that here.
+ int adjustedHeaderSize = sizeof(ResponseEvent) - 3;
+ int expectedDataSize = responseLen - adjustedHeaderSize;
+ if (response.header.dataSize != expectedDataSize)
+ {
+ Logger::error(SSTR << "The data size from the response (" << response.header.dataSize
+ << ") does not match the response structure (" << expectedDataSize
+ << ": " << responseLen << " - " << adjustedHeaderSize
+ << ") - this is likely a kernel version mismatch or bug in the code");
+ }
+
+ // Everything checks out - good to go!
+ return true;
+}
+
+// Filter out events that we aren't interested in
+//
+// NOTE: We're just dipping our toe into the HCI stuff here, so we only care about command complete and status events. This
+// isn't the most robust way to do things, but it is effective.
+bool HciAdapter::filterAndValidateEvents(uint16_t commandCode, std::vector<uint8_t> &buffer) const
+{
+ // Chew through each event in the buffer, removing those that are not related to the requested commandCode
+ std::vector<uint8_t> lastGoodEvent;
+ while (!buffer.empty())
+ {
+ // Get an event pointer into the start of our buffer
+ ResponseEvent *pEvent = reinterpret_cast<ResponseEvent *>(buffer.data());
+
+ // Validate that there is enough space for this event according to its size
+ size_t dataLength = sizeof(Header) + pEvent->header.dataSize;
+ if (dataLength > buffer.size())
+ {
+ Logger::error(SSTR << " + Not enough data for the current event");
+ return false;
+ }
+
+ // Check the event type
+ if (pEvent->header.code < kMinEventType || pEvent->header.code > kMaxEventType)
+ {
+ Logger::error(SSTR << " + Unknown event type " << Utils::hex(pEvent->header.code) << " - ditching all response data to resynchronize");
+ return false;
+ }
+
+ std::string eventTypeName = kEventTypeNames[pEvent->header.code - kMinEventType];
+ std::string commandCodeName = pEvent->commandCode < kMinCommandCode || pEvent->commandCode > kMaxCommandCode ? "Unknown" : kCommandCodeNames[pEvent->commandCode - kMinCommandCode];
+
+ Logger::info(SSTR << " + Received event type " << Utils::hex(pEvent->header.code) << " (" << eventTypeName << ")");
+
+ // Success event for our command?
+ if (pEvent->header.code != 1 && pEvent->commandCode != commandCode)
+ {
+ Logger::warn(SSTR << " + Skipping event type " << Utils::hex(pEvent->header.code) << " (" << eventTypeName << ")");
+ }
+ else
+ {
+ lastGoodEvent = std::vector<uint8_t>(buffer.begin(), buffer.begin() + dataLength);
+ }
+
+ // Remove the current event and move along
+ buffer.erase(buffer.begin(), buffer.begin() + dataLength);
+ }
+
+ // If we don't have a last good event, return an error
+ if (lastGoodEvent.empty())
+ {
+ return false;
+ }
+
+ // Return the last good event
+ buffer = lastGoodEvent;
+ return true;
+}
diff --git a/src/HciAdapter.h b/src/HciAdapter.h
new file mode 100644
index 0000000..3f6c929
--- /dev/null
+++ b/src/HciAdapter.h
@@ -0,0 +1,138 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Protocol-level code for the Bluetooth Management API, which is used to configure the Bluetooth adapter
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This class is intended for use by `Mgmt` (see Mgmt.cpp).
+//
+// See the discussion at the top of HciAdapter.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <stdint.h>
+#include <vector>
+
+#include "HciSocket.h"
+#include "Utils.h"
+
+class HciAdapter
+{
+public:
+
+ struct Header
+ {
+ uint16_t code;
+ uint16_t controllerId;
+ uint16_t dataSize;
+
+ void toNetwork()
+ {
+ code = Utils::endianToHci(code);
+ controllerId = Utils::endianToHci(controllerId);
+ dataSize = Utils::endianToHci(dataSize);
+ }
+
+ void toHost()
+ {
+ code = Utils::endianToHost(code);
+ controllerId = Utils::endianToHost(controllerId);
+ dataSize = Utils::endianToHost(dataSize);
+ }
+
+ } __attribute__((packed));
+
+ struct ResponseEvent
+ {
+ Header header;
+ uint16_t commandCode;
+ uint8_t status;
+
+ void toNetwork()
+ {
+ header.toNetwork();
+ commandCode = Utils::endianToHci(commandCode);
+ }
+
+ void toHost()
+ {
+ header.toHost();
+ commandCode = Utils::endianToHost(commandCode);
+ }
+
+ } __attribute__((packed));
+
+ // Connects the HCI socket if a connection does not already exist
+ //
+ // If a connection already exists, this method will do nothing and return true.
+ //
+ // Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect
+ //
+ // Returns true if the HCI socket is connected (either via a new connection or an existing one), otherwise false
+ bool connect();
+
+ // Returns true if connected to the HCI socket, otherwise false
+ //
+ // Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect
+ bool isConnected() const;
+
+ // Disconnects from the HCI Socket
+ //
+ // If the connection is not connected, this method will do nothing.
+ //
+ // It isn't necessary to disconnect manually; the HCI socket will get disocnnected automatically upon destruction
+ void disconnect();
+
+ // Sends a command over the HCI socket
+ //
+ // If the HCI socket is not connected, it will auto-connect prior to sending the command. In the case of a failed auto-connect,
+ // a failure is returned.
+ //
+ // Returns true on success, otherwise false
+ bool sendCommand(Header &request, ResponseEvent &response, int responseLen);
+
+private:
+
+ // Reads a response from the HCI socket
+ //
+ // Responses are generally triggered by sending commands (see `sendCommand`) but not always. In HCI parlance, a response is
+ // actually an event. Performing commands triggers events. There is not always a 1:1 ratio betwee command and event, and a
+ // command may trigger different events based on the result of the command.
+ //
+ // Unlike the other methods in this class, this method does not auto-connect, as this method is private and can only be called
+ // from methods that have alreay auto-connected.
+ bool readResponse(uint16_t commandCode, ResponseEvent &response, size_t responseLen) const;
+
+ // Filter out events that we aren't interested in
+ //
+ // NOTE: We're just dipping our toe into the HCI stuff here, so we only care about command complete and status events. This
+ // isn't the most robust way to do things, but it is effective.
+ bool filterAndValidateEvents(uint16_t commandCode, std::vector<uint8_t> &buffer) const;
+
+ // Our HCI Socket, which allows us to talk directly to the kernel
+ HciSocket hciSocket;
+};
diff --git a/src/HciSocket.cpp b/src/HciSocket.cpp
new file mode 100644
index 0000000..09da6d6
--- /dev/null
+++ b/src/HciSocket.cpp
@@ -0,0 +1,198 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Low-level socket interface used to communicate with the Bluetooth Management API (see HciAdapter.cpp)
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This class intended to support `HciAdapter` - see HciAdapter.cpp for more information.
+//
+// This file contains raw communication layer for the Bluetooth Management API, which is used to configure the Bluetooth adapter
+// (such as enabling LE, setting the device name, etc.) This class is used by HciAdapter (HciAdapter.h) to perform higher-level
+// functions.
+//
+// This code is for example purposes only. If you plan to use this in a production environment, I suggest rewriting it.
+//
+// The information for this implementation (as well as HciAdapter.h) came from:
+//
+// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <thread>
+
+#include "HciSocket.h"
+#include "Logger.h"
+#include "Utils.h"
+
+// Initializes an unconnected socket
+HciSocket::HciSocket()
+: fdSocket(-1)
+{
+}
+
+// Socket destructor
+//
+// This will automatically disconnect the socket if it is currently connected
+HciSocket::~HciSocket()
+{
+ disconnect();
+}
+
+// Connects to an HCI socket using the Bluetooth Management API protocol
+//
+// Returns true on success, otherwise false
+bool HciSocket::connect()
+{
+ disconnect();
+
+ fdSocket = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI);
+ if (fdSocket < 0)
+ {
+ logErrno("Connect(socket)");
+ return false;
+ }
+
+ struct sockaddr_hci addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.hci_family = AF_BLUETOOTH;
+ addr.hci_dev = HCI_DEV_NONE;
+ addr.hci_channel = HCI_CHANNEL_CONTROL;
+
+ if (bind(fdSocket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0)
+ {
+ logErrno("Connect(bind)");
+ disconnect();
+ return false;
+ }
+
+ Logger::debug(SSTR << "Connected to HCI control socket (file descriptor = " << fdSocket << ")");
+
+ return true;
+}
+
+// Returns true if the socket is currently connected, otherwise false
+bool HciSocket::isConnected() const
+{
+ return fdSocket >= 0;
+}
+
+// Disconnects from the HCI socket
+void HciSocket::disconnect()
+{
+ if (isConnected())
+ {
+ close(fdSocket);
+ fdSocket = -1;
+ }
+}
+
+// Reads data from the HCI socket
+//
+// Raw data is read until no more data is available. If no data is available when this method initially starts to read, it will
+// retry for a maximum timeout defined by `kMaxRetryTimeMS`.
+//
+// Returns true if any data was read successfully, otherwise false is returned in the case of an error or a timeout.
+bool HciSocket::read(std::vector<uint8_t> &response) const
+{
+ // Clear out our response
+ response.clear();
+
+ uint8_t responseChunk[kResponseChunkSize];
+
+ int retryTimeMS = 0;
+ while (retryTimeMS < kMaxRetryTimeMS && !ggkIsServerRunning())
+ {
+ ssize_t bytesRead = ::read(fdSocket, responseChunk, kResponseChunkSize);
+ if (bytesRead > 0)
+ {
+ if (response.size() + bytesRead > kResponseMaxSize)
+ {
+ Logger::warn(SSTR << "Response has exceeded maximum size");
+ return false;
+ }
+ // We just received some data, add it to our buffer
+ std::vector<uint8_t> insertBuf(responseChunk, responseChunk + bytesRead);
+ response.insert(response.end(), insertBuf.begin(), insertBuf.end());
+ }
+ else
+ {
+ // If we have data, we're at the end
+ if (response.size() != 0)
+ {
+ break;
+ }
+ }
+
+ // Retry (or continue reading)
+ std::this_thread::sleep_for(std::chrono::milliseconds(kRetryIntervalMS));
+ retryTimeMS += kRetryIntervalMS;
+ }
+
+ // Did we time out?
+ if (retryTimeMS >= kMaxRetryTimeMS)
+ {
+ logErrno("read(header)");
+ return false;
+ }
+
+ Logger::debug(SSTR << " + Read " << response.size() << " bytes");
+ Logger::debug(SSTR << Utils::hex(response.data(), response.size()));
+ return true;
+}
+
+// Writes the array of bytes of a given count
+//
+// This method returns true if the bytes were written successfully, otherwise false
+bool HciSocket::write(std::vector<uint8_t> buffer) const
+{
+ return write(buffer.data(), buffer.size());
+}
+
+// Writes the array of bytes of a given count
+//
+// This method returns true if the bytes were written successfully, otherwise false
+bool HciSocket::write(const uint8_t *pBuffer, size_t count) const
+{
+ Logger::debug(SSTR << " + Writing " << count << " bytes");
+ Logger::debug(SSTR << Utils::hex(pBuffer, count));
+
+ size_t len = ::write(fdSocket, pBuffer, count);
+
+ if (len != count)
+ {
+ logErrno("write");
+ return false;
+ }
+
+ return true;
+}
+
+// Utilitarian function for logging errors for the given operation
+void HciSocket::logErrno(const char *pOperation) const
+{
+ Logger::error(SSTR << "" << pOperation << " on Bluetooth management socket error (" << errno << "): " << strerror(errno));
+}
diff --git a/src/HciSocket.h b/src/HciSocket.h
new file mode 100644
index 0000000..45f5538
--- /dev/null
+++ b/src/HciSocket.h
@@ -0,0 +1,90 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Low-level socket interface used to communicate with the Bluetooth Management API (see HciAdapter.cpp)
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of HciSocket.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <unistd.h>
+#include <stdint.h>
+#include <vector>
+
+class HciSocket
+{
+public:
+ // Initializes an unconnected socket
+ HciSocket();
+
+ // Socket destructor
+ //
+ // This will automatically disconnect the socket if it is currently connected
+ ~HciSocket();
+
+ // Connects to an HCI socket using the Bluetooth Management API protocol
+ //
+ // Returns true on success, otherwise false
+ bool connect();
+
+ // Returns true if the socket is currently connected, otherwise false
+ bool isConnected() const;
+
+ // Disconnects from the HCI socket
+ void disconnect();
+
+ // Reads data from the HCI socket
+ //
+ // Raw data is read until no more data is available. If no data is available when this method initially starts to read, it will
+ // retry for a maximum timeout defined by `kMaxRetryTimeMS`.
+ //
+ // Returns true if any data was read successfully, otherwise false is returned in the case of an error or a timeout.
+ bool read(std::vector<uint8_t> &response) const;
+
+ // Writes the array of bytes of a given count
+ //
+ // This method returns true if the bytes were written successfully, otherwise false
+ bool write(std::vector<uint8_t> buffer) const;
+
+ // Writes the array of bytes of a given count
+ //
+ // This method returns true if the bytes were written successfully, otherwise false
+ bool write(const uint8_t *pBuffer, size_t count) const;
+
+private:
+
+ // Utilitarian function for logging errors for the given operation
+ void logErrno(const char *pOperation) const;
+
+ int fdSocket;
+
+ const size_t kResponseMaxSize = 64 * 1024;
+ const int kResponseChunkSize = 1024;
+ const int kRetryIntervalMS = 20;
+ const int kMaxRetryTimeMS = 5000;
+};
diff --git a/src/Init.cpp b/src/Init.cpp
new file mode 100644
index 0000000..5ed25e2
--- /dev/null
+++ b/src/Init.cpp
@@ -0,0 +1,1207 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Herein lies the code that manages the full initialization (including the running) of the server
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This file contains the highest-level framework for our server:
+//
+// Initialization
+// Adapter configuration (mode, settings, name, etc.)
+// GATT server registration with BlueZ
+// Signal handling (such as CTRL-C)
+// Event management
+// Graceful shutdown
+//
+// Want to poke around and see how things work? Here's a tip: Start at the bottom of the file and work upwards. It'll make a lot
+// more sense, I promise.
+//
+// Want to become your own boss while working from home? (Just kidding.)
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <gio/gio.h>
+#include <string>
+#include <vector>
+#include <atomic>
+#include <chrono>
+#include <thread>
+
+#include "Server.h"
+#include "Globals.h"
+#include "Mgmt.h"
+#include "HciAdapter.h"
+#include "DBusObject.h"
+#include "DBusInterface.h"
+#include "GattCharacteristic.h"
+#include "GattProperty.h"
+#include "Logger.h"
+#include "Init.h"
+
+//
+// Constants
+//
+
+static const int kPeriodicTimerFrequencySeconds = 1;
+static const int kRetryDelaySeconds = 2;
+static const int kIdleFrequencyMS = 10;
+
+//
+// Retries
+//
+
+static time_t retryTimeStart = 0;
+
+//
+// Adapter configuration
+//
+
+GDBusConnection *pBusConnection = nullptr;
+static guint ownedNameId = 0;
+static guint periodicTimeoutId = 0;
+static std::vector<guint> registeredObjectIds;
+static std::atomic<GMainLoop *> pMainLoop(nullptr);
+static GDBusObjectManager *pBluezObjectManager = nullptr;
+static GDBusObject *pBluezAdapterObject = nullptr;
+static GDBusObject *pBluezDeviceObject = nullptr;
+static GDBusProxy *pBluezGattManagerProxy = nullptr;
+static GDBusProxy *pBluezAdapterInterfaceProxy = nullptr;
+static GDBusProxy *pBluezDeviceInterfaceProxy = nullptr;
+static GDBusProxy *pBluezAdapterPropertiesInterfaceProxy = nullptr;
+static bool bOwnedNameAcquired = false;
+static bool bAdapterConfigured = false;
+static bool bApplicationRegistered = false;
+static std::string bluezGattManagerInterfaceName = "";
+
+//
+// Externs
+//
+
+extern void setServerRunState(enum GGKServerRunState newState);
+extern void setServerHealth(enum GGKServerHealth newState);
+
+//
+// Forward declarations
+//
+
+static void initializationStateProcessor();
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ___ _ _ __ _ _ _
+// |_ _|__| | | ___ / / __| | __ _| |_ __ _ _ __ _ __ ___ ___ ___ ___ ___(_)_ __ __ _
+// | |/ _` | |/ _ \ / / / _` |/ _` | __/ _` | | '_ \| '__/ _ \ / __/ _ \/ __/ __| | '_ \ / _` |
+// | | (_| | | __/ / / | (_| | (_| | || (_| | | |_) | | | (_) | (_| __/\__ \__ \ | | | | (_| |
+// |___\__,_|_|\___| /_/ \__,_|\__,_|\__\__,_| | .__/|_| \___/ \___\___||___/___/_|_| |_|\__, |
+// |_| |___/
+//
+// Our idle funciton is what processes data updates. We handle this in a simple way. We update the data directly in our global
+// `TheServer` object, then call `ggkPushUpdateQueue` to trigger that data to be updated (in whatever way the service responsible
+// for that data() sees fit.
+//
+// This is done using the `ggkPushUpdateQueue` / `ggkPopUpdateQueue` methods to manage the queue of pending update messages. Each
+// entry represents an interface that needs to be updated. The idleFunc calls the interface's `onUpdatedValue` method for each
+// update.
+//
+// The idle processor will perform one update per idle tick, however, it will notify that there is more data so the idle ticks
+// do not lag behind.
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Our idle function
+//
+// This method is used to process data on the same thread as our main loop. This allows us to communicate with our service from
+// the outside.
+//
+// IMPORTANT: This method must return 'true' if any work was performed, otherwise it must return 'false'. Returning 'true' will
+// cause the idle loop to continue to call this method to process data at the maximum rate (which can peg the CPU at 100%.) By
+// returning false when there is no work to do, we are nicer to the system.
+bool idleFunc(void *pUserData)
+{
+ // Don't do anything unless we're running
+ if (ggkGetServerRunState() != ERunning)
+ {
+ return false;
+ }
+
+ // Try to get an update
+ const int kQueueEntryLen = 1024;
+ char queueEntry[kQueueEntryLen];
+ if (ggkPopUpdateQueue(queueEntry, kQueueEntryLen, 0) != 1)
+ {
+ return false;
+ }
+
+ std::string entryString = queueEntry;
+ auto token = entryString.find('|');
+ if (token == std::string::npos)
+ {
+ Logger::error("Queue entry was not formatted properly - could not find separating token");
+ return false;
+ }
+
+ DBusObjectPath objectPath = DBusObjectPath(entryString.substr(0, token));
+ std::string interfaceName = entryString.substr(token+1);
+
+ // We have an update - call the onUpdatedValue method on the interface
+ std::shared_ptr<const DBusInterface> pInterface = TheServer->findInterface(objectPath, interfaceName);
+ if (nullptr == pInterface)
+ {
+ Logger::warn(SSTR << "Unable to find interface for update: path[" << objectPath << "], name[" << interfaceName << "]");
+ }
+ else
+ {
+ // Is it a characteristic?
+ if (std::shared_ptr<const GattCharacteristic> pCharacteristic = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattCharacteristic))
+ {
+ Logger::info(SSTR << "Processing updated value for interface '" << interfaceName << "' at path '" << objectPath << "'");
+ pCharacteristic->callOnUpdatedValue(pBusConnection, pUserData);
+ }
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _ _ _ _ _ _
+// | _ \ ___(_)_ __ (_) |_(_) __ _| (_)______ _| |_(_) ___ _ ___
+// | | | |/ _ \ | '_ \| | __| |/ _` | | |_ / _` | __| |/ _ \| '_ |
+// | |_| | __/ | | | | | |_| | (_| | | |/ / (_| | |_| | (_) | | | |
+// |____/ \___|_|_| |_|_|\__|_|\__,_|_|_/___\__,_|\__|_|\___/|_| |_|
+//
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Perform final cleanup of various resources that were allocated while the server was initialized and/or running
+void uninit()
+{
+ // We've left our main loop - nullify its pointer so we know we're no longer running
+ pMainLoop = nullptr;
+
+ if (nullptr != pBluezAdapterObject)
+ {
+ g_object_unref(pBluezAdapterObject);
+ pBluezAdapterObject = nullptr;
+ }
+
+ if (nullptr != pBluezDeviceObject)
+ {
+ g_object_unref(pBluezDeviceObject);
+ pBluezDeviceObject = nullptr;
+ }
+
+ if (nullptr != pBluezAdapterInterfaceProxy)
+ {
+ g_object_unref(pBluezAdapterInterfaceProxy);
+ pBluezAdapterInterfaceProxy = nullptr;
+ }
+
+ if (nullptr != pBluezDeviceInterfaceProxy)
+ {
+ g_object_unref(pBluezDeviceInterfaceProxy);
+ pBluezDeviceInterfaceProxy = nullptr;
+ }
+
+ if (nullptr != pBluezAdapterPropertiesInterfaceProxy)
+ {
+ g_object_unref(pBluezAdapterPropertiesInterfaceProxy);
+ pBluezAdapterPropertiesInterfaceProxy = nullptr;
+ }
+
+ if (nullptr != pBluezGattManagerProxy)
+ {
+ g_object_unref(pBluezGattManagerProxy);
+ pBluezGattManagerProxy = nullptr;
+ }
+
+ if (nullptr != pBluezObjectManager)
+ {
+ g_object_unref(pBluezObjectManager);
+ pBluezObjectManager = nullptr;
+ }
+
+ if (!registeredObjectIds.empty())
+ {
+ for (guint id : registeredObjectIds)
+ {
+ g_dbus_connection_unregister_object(pBusConnection, id);
+ }
+ registeredObjectIds.clear();
+ }
+
+ if (0 != periodicTimeoutId)
+ {
+ g_source_remove(periodicTimeoutId);
+ periodicTimeoutId = 0;
+ }
+
+ if (ownedNameId > 0)
+ {
+ g_bus_unown_name(ownedNameId);
+ }
+
+ if (nullptr != pBusConnection)
+ {
+ g_object_unref(pBusConnection);
+ pBusConnection = nullptr;
+ }
+
+ if (nullptr != pMainLoop)
+ {
+ g_main_loop_unref(pMainLoop);
+ pMainLoop = nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _
+// / ___|| |__ _ _| |_ __| | _____ ___ ___
+// \___ \| '_ \| | | | __/ _` |/ _ \ \ /\ / / '_ |
+// ___) | | | | |_| | || (_| | (_) \ V V /| | | |
+// |____/|_| |_|\__,_|\__\__,_|\___/ \_/\_/ |_| |_|
+//
+// This is how we shutdown our server gracefully.
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Trigger a graceful, asynchronous shutdown of the server
+//
+// This method is non-blocking and as such, will only trigger the shutdown process but not wait for it
+void shutdown()
+{
+ if (ggkGetServerRunState() >= EStopping)
+ {
+ Logger::warn(SSTR << "shutdown() called while already shutting down");
+ }
+
+ // Our new state: shutting down
+ setServerRunState(EStopping);
+
+ // If we still have a main loop, ask it to quit
+ if (nullptr != pMainLoop)
+ {
+ g_main_loop_quit(pMainLoop);
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _ _ _
+// | _ \ ___ _ __(_) ___ __| (_) ___ | |_(_)_ __ ___ ___ _ __
+// | |_) / _ \ '__| |/ _ \ / _` | |/ __| | __| | '_ ` _ \ / _ \ '__|
+// | __/ __/ | | | (_) | (_| | | (__ | |_| | | | | | | __/ |
+// |_| \___|_| |_|\___/ \__,_|_|\___| \__|_|_| |_| |_|\___|_|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Periodic timer handler
+//
+// A periodic timer is a timer fires every so often (see kPeriodicTimerFrequencySeconds.) This is used for our initialization
+// failure retries, but custom code can also be added to a server description (see `onEvent()`)
+gboolean onPeriodicTimer(gpointer pUserData)
+{
+ // Deal with retry timers
+ if (0 != retryTimeStart)
+ {
+ Logger::debug(SSTR << "Ticking retry timer");
+
+ // Has the retry time expired?
+ int secondsRemaining = time(nullptr) - retryTimeStart - kRetryDelaySeconds;
+ if (secondsRemaining >= 0)
+ {
+ retryTimeStart = 0;
+ initializationStateProcessor();
+ }
+ }
+
+ // If we're registered, then go ahead and emit signals
+ if (bApplicationRegistered)
+ {
+ // Tick the object hierarchy
+ //
+ // The real goal here is to have the objects tick their interfaces (see `onEvent()` method when adding interfaces inside
+ // 'Server::Server()'
+ for (const DBusObject &object : TheServer->getObjects())
+ {
+ if (object.isPublished())
+ {
+ object.tickEvents(pBusConnection, pUserData);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _____ _
+// | ____|_ _____ _ __ | |_ ___
+// | _| \ \ / / _ \ '_ \| __/ __|
+// | |___ \ V / __/ | | | |_\__ )
+// |_____| \_/ \___|_| |_|\__|___/
+//
+// Our event handlers. These are generic, as they parcel out the work to the appropriate server objects (see 'Server::Server()' for
+// the code that manages event handlers.)
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Handle D-Bus method calls
+void onMethodCall
+(
+ GDBusConnection *pConnection,
+ const gchar *pSender,
+ const gchar *pObjectPath,
+ const gchar *pInterfaceName,
+ const gchar *pMethodName,
+ GVariant *pParameters,
+ GDBusMethodInvocation *pInvocation,
+ gpointer pUserData
+)
+{
+ // Convert our input path into our custom type for path management
+ DBusObjectPath objectPath(pObjectPath);
+
+ if (!TheServer->callMethod(objectPath, pInterfaceName, pMethodName, pConnection, pParameters, pInvocation, pUserData))
+ {
+ Logger::error(SSTR << " + Method not found: [" << pSender << "]:[" << objectPath << "]:[" << pInterfaceName << "]:[" << pMethodName << "]");
+ g_dbus_method_invocation_return_dbus_error(pInvocation, kErrorNotImplemented.c_str(), "This method is not implemented");
+ return;
+ }
+
+ return;
+}
+
+// Handle D-Bus requests to get a property
+GVariant *onGetProperty
+(
+ GDBusConnection *pConnection,
+ const gchar *pSender,
+ const gchar *pObjectPath,
+ const gchar *pInterfaceName,
+ const gchar *pPropertyName,
+ GError **ppError,
+ gpointer pUserData
+)
+{
+ // Convert our input path into our custom type for path management
+ DBusObjectPath objectPath(pObjectPath);
+
+ const GattProperty *pProperty = TheServer->findProperty(objectPath, pInterfaceName, pPropertyName);
+
+ std::string propertyPath = std::string("[") + pSender + "]:[" + objectPath.toString() + "]:[" + pInterfaceName + "]:[" + pPropertyName + "]";
+ if (!pProperty)
+ {
+ Logger::error(SSTR << "Property(get) not found: " << propertyPath);
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) not found: " + propertyPath).c_str(), pSender);
+ return nullptr;
+ }
+
+ if (!pProperty->getGetterFunc())
+ {
+ Logger::error(SSTR << "Property(get) func not found: " << propertyPath);
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) func not found: " + propertyPath).c_str(), pSender);
+ return nullptr;
+ }
+
+ Logger::info(SSTR << "Calling property getter: " << propertyPath);
+ GVariant *pResult = pProperty->getGetterFunc()(pConnection, pSender, objectPath.c_str(), pInterfaceName, pPropertyName, ppError, pUserData);
+
+ if (nullptr == pResult)
+ {
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) failed: " + propertyPath).c_str(), pSender);
+ return nullptr;
+ }
+
+ return pResult;
+}
+
+// Handle D-Bus requests to set a property
+gboolean onSetProperty
+(
+ GDBusConnection *pConnection,
+ const gchar *pSender,
+ const gchar *pObjectPath,
+ const gchar *pInterfaceName,
+ const gchar *pPropertyName,
+ GVariant *pValue,
+ GError **ppError,
+ gpointer pUserData
+)
+{
+ // Convert our input path into our custom type for path management
+ DBusObjectPath objectPath(pObjectPath);
+
+ const GattProperty *pProperty = TheServer->findProperty(objectPath, pInterfaceName, pPropertyName);
+
+ std::string propertyPath = std::string("[") + pSender + "]:[" + objectPath.toString() + "]:[" + pInterfaceName + "]:[" + pPropertyName + "]";
+ if (!pProperty)
+ {
+ Logger::error(SSTR << "Property(set) not found: " << propertyPath);
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) not found: " + propertyPath).c_str(), pSender);
+ return false;
+ }
+
+ if (!pProperty->getSetterFunc())
+ {
+ Logger::error(SSTR << "Property(set) func not found: " << propertyPath);
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) func not found: " + propertyPath).c_str(), pSender);
+ return false;
+ }
+
+ Logger::info(SSTR << "Calling property getter: " << propertyPath);
+ if (!pProperty->getSetterFunc()(pConnection, pSender, objectPath.c_str(), pInterfaceName, pPropertyName, pValue, ppError, pUserData))
+ {
+ g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) failed: " + propertyPath).c_str(), pSender);
+ return false;
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _____ _ _ _
+// | ___|_ _(_) |_ _ _ __ ___ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
+// | |_ / _` | | | | | | '__/ _ \ | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
+// | _| (_| | | | |_| | | | __/ | | | | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
+// |_| \__,_|_|_|\__,_|_| \___| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
+// |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Convenience method for setting a retry timer so that failures (related to initialization) can be continuously retried until we
+// eventually succeed.
+void setRetryFailure()
+{
+ retryTimeStart = time(nullptr);
+ Logger::info(SSTR << " + Will retry this failed operation in about " << kRetryDelaySeconds << " seconds");
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _____ _____ _ _ _ _
+// / ___| / \|_ _|_ _| _ __ ___ __ _(_)___| |_ _ __ __ _| |_(_) ___ _ ___
+// | | _ / _ \ | | | | | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_ |
+// | |_| |/ ___ \| | | | | | | __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | |
+// \____/_/ \_\_| |_| |_| \___|\__, |_|___/\__|_| \__,_|\__|_|\___/|_| |_|
+// |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Use the BlueZ GATT Manager proxy to register our GATT application with BlueZ
+void doRegisterApplication()
+{
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ GVariant *pParams = g_variant_new("(oa{sv})", "/", &builder);
+
+ g_dbus_proxy_call
+ (
+ pBluezGattManagerProxy, // GDBusProxy *proxy
+ "RegisterApplication", // const gchar *method_name (ex: "GetManagedObjects")
+ pParams, // GVariant *parameters
+ G_DBUS_CALL_FLAGS_NONE, // GDBusCallFlags flags
+ -1, // gint timeout_msec
+ nullptr, // GCancellable *cancellable
+
+ // GAsyncReadyCallback callback
+ [] (GObject * /*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
+ {
+ GError *pError = nullptr;
+ GVariant *pVariant = g_dbus_proxy_call_finish(pBluezGattManagerProxy, pAsyncResult, &pError);
+ if (nullptr == pVariant)
+ {
+ Logger::error(SSTR << "Failed to register application: " << (nullptr == pError ? "Unknown" : pError->message));
+ setRetryFailure();
+ }
+ else
+ {
+ g_variant_unref(pVariant);
+ Logger::info(SSTR << "Application registered");
+ bApplicationRegistered = true;
+ }
+
+ // Keep going...
+ initializationStateProcessor();
+ },
+
+ nullptr // gpointer user_data
+ );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ___ _ _ _ _ _ _ _
+// / _ \| |__ (_) ___ ___| |_ _ __ ___ __ _(_)___| |_ _ __ __ _| |_(_) ___ _ ___
+// | | | | '_ \| |/ _ \/ __| __| | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_ |
+// | |_| | |_) | | __/ (__| |_ | | | __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | |
+// \___/|_.__// |\___|\___|\__| |_| \___|\__, |_|___/\__|_| \__,_|\__|_|\___/|_| |_|
+// |__/ |___/
+//
+// Before we can register our service(s) with BlueZ, we must first register ourselves with D-Bus. The easiest way to do this is to
+// use an XML description of our D-Bus objects.
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+void registerNodeHierarchy(GDBusNodeInfo *pNode, const DBusObjectPath &basePath = DBusObjectPath(), int depth = 1)
+{
+ std::string prefix;
+ prefix.insert(0, depth * 2, ' ');
+
+ static GDBusInterfaceVTable interfaceVtable;
+ interfaceVtable.method_call = onMethodCall;
+ interfaceVtable.get_property = onGetProperty;
+ interfaceVtable.set_property = onSetProperty;
+
+ GDBusInterfaceInfo **ppInterface = pNode->interfaces;
+
+ Logger::debug(SSTR << prefix << "+ " << pNode->path);
+
+ while(nullptr != *ppInterface)
+ {
+ GError *pError = nullptr;
+ Logger::debug(SSTR << prefix << " (iface: " << (*ppInterface)->name << ")");
+ guint registeredObjectId = g_dbus_connection_register_object
+ (
+ pBusConnection, // GDBusConnection *connection
+ basePath.c_str(), // const gchar *object_path
+ *ppInterface, // GDBusInterfaceInfo *interface_info
+ &interfaceVtable, // const GDBusInterfaceVTable *vtable
+ nullptr, // gpointer user_data
+ nullptr, // GDestroyNotify user_data_free_func
+ &pError // GError **error
+ );
+
+ if (0 == registeredObjectId)
+ {
+ Logger::error(SSTR << "Failed to register object: " << (nullptr == pError ? "Unknown" : pError->message));
+
+ // Cleanup and pretend like we were never here
+ g_dbus_node_info_unref(pNode);
+ registeredObjectIds.clear();
+
+ // Try again later
+ setRetryFailure();
+ return;
+ }
+
+ // Save the registered object Id so we can clean it up later
+ registeredObjectIds.push_back(registeredObjectId);
+
+ ++ppInterface;
+ }
+
+ GDBusNodeInfo **ppChild = pNode->nodes;
+ while(nullptr != *ppChild)
+ {
+ registerNodeHierarchy(*ppChild, basePath + (*ppChild)->path, depth + 1);
+
+ ++ppChild;
+ }
+}
+
+void registerObjects()
+{
+ // Parse each object into an XML interface tree
+ for (const DBusObject &object : TheServer->getObjects())
+ {
+ GError *pError = nullptr;
+ std::string xmlString = object.generateIntrospectionXML();
+ GDBusNodeInfo *pNode = g_dbus_node_info_new_for_xml(xmlString.c_str(), &pError);
+ if (nullptr == pNode)
+ {
+ Logger::error(SSTR << "Failed to introspect XML: " << (nullptr == pError ? "Unknown" : pError->message));
+ setRetryFailure();
+ return;
+ }
+
+ Logger::debug(SSTR << "Registering object hierarchy with D-Bus hierarchy");
+
+ // Register the node hierarchy
+ registerNodeHierarchy(pNode, DBusObjectPath(pNode->path));
+
+ // Cleanup the node
+ g_dbus_node_info_unref(pNode);
+ }
+
+ // Keep going
+ initializationStateProcessor();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _ _ _ __ _ _ _
+// / \ __| | __ _ _ __ | |_ ___ _ __ ___ ___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ ___
+// / _ \ / _` |/ _` | '_ \| __/ _ \ '__| / __/ _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ |
+// / ___ \ (_| | (_| | |_) | || __/ | | (_| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | |
+// /_/ \_\__,_|\__,_| .__/ \__\___|_| \___\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_|
+// |_| |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Configure an adapter to ensure it is setup the way we need. We turn things on that we need and turn everything else off
+// (to maximize security.)
+//
+// Note that this only works for the first adapter (index 0). Search for kControllerIndex for information.
+//
+// See also: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt
+void configureAdapter()
+{
+ Mgmt mgmt;
+
+ // Find out what our current settings are
+ Logger::info(SSTR << "Getting device information");
+ Mgmt::ControllerInformation *pInfo = mgmt.getControllerInformation();
+ if (nullptr == pInfo)
+ {
+ setRetryFailure();
+ return;
+ }
+
+ // We need it off to start with
+ if ((pInfo->currentSettings & Mgmt::EHciPowered) != 0)
+ {
+ Logger::info(SSTR << "Powering off");
+ if (!mgmt.setPowered(false))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Change the Br/Edr state?
+ bool bredrCurrentState = (pInfo->currentSettings & Mgmt::EHciBasicRate_EnhancedDataRate) != 0 ? true:false;
+ if (TheServer->getEnableBREDR() != bredrCurrentState)
+ {
+ Logger::info(SSTR << (TheServer->getEnableBREDR() ? "Enabling":"Disabling") << " BR/EDR");
+ if (!mgmt.setBredr(TheServer->getEnableBREDR()))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Change the Secure Connectinos state?
+ bool scCurrentState = (pInfo->currentSettings & Mgmt::EHciSecureConnections) != 0 ? true:false;
+ if (TheServer->getEnableSecureConnection() != scCurrentState)
+ {
+ Logger::info(SSTR << (TheServer->getEnableSecureConnection() ? "Enabling":"Disabling") << " Secure Connections");
+ if (!mgmt.setSecureConnections(TheServer->getEnableSecureConnection() ? 1 : 0))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Change the Bondable state?
+ bool bondableCurrentState = (pInfo->currentSettings & Mgmt::EHciBondable) != 0 ? true:false;
+ if (TheServer->getEnableBondable() != bondableCurrentState)
+ {
+ Logger::info(SSTR << (TheServer->getEnableBondable() ? "Enabling":"Disabling") << " Bondable");
+ if (!mgmt.setBondable(TheServer->getEnableBondable()))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Change the Connectable state?
+ bool connectableCurrentState = (pInfo->currentSettings & Mgmt::EHciConnectable) != 0 ? true:false;
+ if (TheServer->getEnableConnectable() != connectableCurrentState)
+ {
+ Logger::info(SSTR << (TheServer->getEnableConnectable() ? "Enabling":"Disabling") << " Connectable");
+ if (!mgmt.setConnectable(TheServer->getEnableConnectable()))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Enable the LE state (we always set this state if it's not set)
+ if ((pInfo->currentSettings & Mgmt::EHciLowEnergy) == 0)
+ {
+ Logger::info(SSTR << "Enabling LE");
+ if (!mgmt.setLE(true))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Change the Advertising state?
+ bool advertisingCurrentState = (pInfo->currentSettings & Mgmt::EHciAdvertising) != 0 ? true:false;
+ if (TheServer->getEnableAdvertising() != advertisingCurrentState)
+ {
+ Logger::info(SSTR << (TheServer->getEnableAdvertising() ? "Enabling":"Disabling") << " Advertising");
+ if (!mgmt.setAdvertising(TheServer->getEnableAdvertising() ? 1 : 0))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+
+ // Set the name?
+ if (kCustomGlobalAdvertisingName.length() != 0 || kCustomGlobalAdvertisingShortName.length() != 0)
+ {
+ if (Mgmt::truncateName(kCustomGlobalAdvertisingName) != pInfo->name ||
+ Mgmt::truncateShortName(kCustomGlobalAdvertisingShortName) != pInfo->shortName)
+ {
+ Logger::info(SSTR << "Setting name to '" << kCustomGlobalAdvertisingName << "'");
+ if (!mgmt.setName(kCustomGlobalAdvertisingName.c_str(), kCustomGlobalAdvertisingName.c_str()))
+ {
+ setRetryFailure();
+ return;
+ }
+ }
+ }
+
+ // Turn it back on
+ Logger::info(SSTR << "Powering on");
+ if (!mgmt.setPowered(true))
+ {
+ setRetryFailure();
+ return;
+ }
+
+ // We can ignore errors on this - we're just letting it dump the output
+ mgmt.getControllerInformation();
+
+ bAdapterConfigured = true;
+ initializationStateProcessor();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// _ _ _
+// / \ __| | __ _ _ __ | |_ ___ _ __
+// / _ \ / _` |/ _` | '_ \| __/ _ \ '__|
+// / ___ \ (_| | (_| | |_) | || __/ |
+// /_/ \_\__,_|\__,_| .__/ \__\___|_|
+// |_|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Find the BlueZ's GATT Manager interface for the *first* Bluetooth adapter provided by BlueZ. We'll need this to register our
+// GATT server with BlueZ.
+void findAdapterInterface()
+{
+ // Get a list of the BlueZ's D-Bus objects
+ GList *pObjects = g_dbus_object_manager_get_objects(pBluezObjectManager);
+ if (nullptr == pObjects)
+ {
+ Logger::error(SSTR << "Unable to get ObjectManager objects");
+ setRetryFailure();
+ return;
+ }
+
+ // Scan the list of objects we find one with a GATT manager interface
+ //
+ // Note that if there are multiple interfaces, we will only find the first
+ for (guint i = 0; i < g_list_length(pObjects) && bluezGattManagerInterfaceName.empty(); ++i)
+ {
+ // Current object in question
+ pBluezAdapterObject = static_cast<GDBusObject *>(g_list_nth_data(pObjects, i));
+ if (nullptr == pBluezAdapterObject) { continue; }
+
+ // See if it has a GATT manager interface
+ pBluezGattManagerProxy = reinterpret_cast<GDBusProxy *>(g_dbus_object_get_interface(pBluezAdapterObject, "org.bluez.GattManager1"));
+ if (nullptr == pBluezGattManagerProxy) { continue; }
+
+ // Get the interface proxy for this adapter - this will come in handy later
+ pBluezAdapterInterfaceProxy = reinterpret_cast<GDBusProxy *>(g_dbus_object_get_interface(pBluezAdapterObject, "org.bluez.Adapter1"));
+ if (nullptr == pBluezAdapterInterfaceProxy)
+ {
+ Logger::warn(SSTR << "Failed to get adapter proxy for interface 'org.bluez.Adapter1'");
+ continue;
+ }
+
+ // Get the interface proxy for this adapter's properties - this will come in handy later
+ pBluezAdapterPropertiesInterfaceProxy = reinterpret_cast<GDBusProxy *>(g_dbus_object_get_interface(pBluezAdapterObject, "org.freedesktop.DBus.Properties"));
+ if (nullptr == pBluezAdapterPropertiesInterfaceProxy)
+ {
+ Logger::warn(SSTR << "Failed to get adapter properties proxy for interface 'org.freedesktop.DBus.Properties'");
+ continue;
+ }
+
+ // Finally, save off the interface name, we're done!
+ bluezGattManagerInterfaceName = g_dbus_proxy_get_object_path(pBluezGattManagerProxy);
+ break;
+ }
+
+ // Get a fresh copy of our objects so we can release the entire list
+ pBluezAdapterObject = g_dbus_object_manager_get_object(pBluezObjectManager, g_dbus_object_get_object_path(pBluezAdapterObject));
+
+ // We'll need access to the device object so we can set properties on it
+ pBluezDeviceObject = g_dbus_object_manager_get_object(pBluezObjectManager, g_dbus_object_get_object_path(pBluezAdapterObject));
+
+ // Cleanup the list
+ for (guint i = 0; i < g_list_length(pObjects) && bluezGattManagerInterfaceName.empty(); ++i)
+ {
+ g_object_unref(g_list_nth_data(pObjects, i));
+ }
+
+ g_list_free(pObjects);
+
+ // If we didn't find the adapter object, reset things and we'll try again later
+ if (nullptr == pBluezAdapterObject || nullptr == pBluezDeviceObject)
+ {
+ Logger::info(SSTR << "Unable to find BlueZ objects outside of object list");
+ bluezGattManagerInterfaceName.clear();
+ }
+
+ // If we never ended up with an interface name, bail now
+ if (bluezGattManagerInterfaceName.empty())
+ {
+ Logger::error(SSTR << "Unable to find the adapter");
+ setRetryFailure();
+ return;
+ }
+
+ // Keep going
+ initializationStateProcessor();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _____ ___ _ _ _ __ __
+// | __ )| |_ _ ___|__ / / _ \| |__ (_) ___ ___| |_| \/ | __ _ _ __ __ _ __ _ ___ _ __
+// | _ \| | | | |/ _ \ / / | | | | '_ \| |/ _ \/ __| __| |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
+// | |_) | | |_| | __// /_ | |_| | |_) | | __/ (__| |_| | | | (_| | | | | (_| | (_| | __/ |
+// |____/|_|\__,_|\___/____| \___/|_.__// |\___|\___|\__|_| |_|\__,_|_| |_|\__,_|\__, |\___|_|
+// |__/ |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Get the D-Bus Object Manager client to the BlueZ ObjectManager object
+//
+// An ObjectManager allows us to find out what objects (and from those, interfaces, etc.) are available from an owned name. We'll
+// use this to interrogate BlueZ's objects to find an adapter we can use, among other things.
+void getBluezObjectManager()
+{
+ g_dbus_object_manager_client_new
+ (
+ pBusConnection, // GDBusConnection
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, // GDBusObjectManagerClientFlags
+ "org.bluez", // Owner name (or well-known name)
+ "/", // Object path
+ nullptr, // GDBusProxyTypeFunc get_proxy_type_func
+ nullptr, // gpointer get_proxy_type_user_data
+ nullptr, // GDestroyNotify get_proxy_type_destroy_notify
+ nullptr, // GCancellable *cancellable
+
+ // GAsyncReadyCallback callback
+ [] (GObject * /*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
+ {
+ // Store BlueZ's ObjectManager
+ GError *pError = nullptr;
+ pBluezObjectManager = g_dbus_object_manager_client_new_finish(pAsyncResult, &pError);
+
+ if (nullptr == pBluezObjectManager)
+ {
+ Logger::error(SSTR << "Failed to get an ObjectManager client: " << (nullptr == pError ? "Unknown" : pError->message));
+ setRetryFailure();
+ return;
+ }
+
+ // Keep going
+ initializationStateProcessor();
+ },
+
+ nullptr // gpointer user_data
+ );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ___ _
+// / _ \__ ___ __ ___ __| | _ __ __ _ _ __ ___ ___
+// | | | \ \ /\ / / '_ \ / _ \/ _` | | '_ \ / _` | '_ ` _ \ / _ )
+// | |_| |\ V V /| | | | __/ (_| | | | | | (_| | | | | | | __/
+// \___/ \_/\_/ |_| |_|\___|\__,_| |_| |_|\__,_|_| |_| |_|\___|
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Acquire an "owned name" with D-Bus. This name represents our server as a whole, identifying us on D-Bus and allowing others
+// (BlueZ) to communicate back to us. All of the D-Bus objects (which represent our BlueZ services, characteristics, etc.) will all
+// reside under this owned name.
+//
+// Note about error management: We don't yet hwave a timeout callback running for retries; errors are considered fatal
+void doOwnedNameAcquire()
+{
+ // Our name is not presently lost
+ bOwnedNameAcquired = false;
+
+ ownedNameId = g_bus_own_name_on_connection
+ (
+ pBusConnection, // GDBusConnection *connection
+ kServerOwnedName.c_str(), // const gchar *name
+ G_BUS_NAME_OWNER_FLAGS_NONE, // GBusNameOwnerFlags flags
+
+ // GBusNameAcquiredCallback name_acquired_handler
+ [](GDBusConnection *, const gchar *, gpointer)
+ {
+ // Handy way to get periodic activity
+ periodicTimeoutId = g_timeout_add_seconds(kPeriodicTimerFrequencySeconds, onPeriodicTimer, pBusConnection);
+ if (periodicTimeoutId <= 0)
+ {
+ Logger::fatal(SSTR << "Failed to add a periodic timer");
+ setServerHealth(EFailedInit);
+ shutdown();
+ }
+
+ // Bus name acquired
+ bOwnedNameAcquired = true;
+
+ // Keep going...
+ initializationStateProcessor();
+ },
+
+ // GBusNameLostCallback name_lost_handler
+ [](GDBusConnection *, const gchar *, gpointer)
+ {
+ // Bus name lost
+ bOwnedNameAcquired = false;
+
+ // If we don't have a periodicTimeout (which we use for error recovery) then we're done
+ if (0 == periodicTimeoutId)
+ {
+ Logger::fatal(SSTR << "Unable to acquire an owned name ('" << kServerOwnedName << "') on the bus");
+ setServerHealth(EFailedInit);
+ shutdown();
+ }
+ else
+ {
+ Logger::warn(SSTR << "Owned name ('" << kServerOwnedName << "') lost");
+ setRetryFailure();
+ return;
+ }
+
+ // Keep going...
+ initializationStateProcessor();
+ },
+
+ nullptr, // gpointer user_data
+ nullptr // GDestroyNotify user_data_free_func
+ );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____
+// | __ ) _ _ ___
+// | _ \| | | / __|
+// | |_) | |_| \__ )
+// |____/ \__,_|___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Acquire a connection to the SYSTEM bus so we can communicate with BlueZ.
+//
+// Note about error management: We don't yet hwave a timeout callback running for retries; errors are considered fatal
+void doBusAcquire()
+{
+ // Acquire a connection to the SYSTEM bus
+ g_bus_get
+ (
+ G_BUS_TYPE_SYSTEM, // GBusType bus_type
+ nullptr, // GCancellable *cancellable
+
+ // GAsyncReadyCallback callback
+ [] (GObject */*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
+ {
+ GError *pError = nullptr;
+ pBusConnection = g_bus_get_finish(pAsyncResult, &pError);
+
+ if (nullptr == pBusConnection)
+ {
+ Logger::fatal(SSTR << "Failed to get bus connection: " << (nullptr == pError ? "Unknown" : pError->message));
+ setServerHealth(EFailedInit);
+ shutdown();
+ }
+
+ // Continue
+ initializationStateProcessor();
+ },
+
+ nullptr // gpointer user_data
+ );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _ _ _
+// / ___|| |_ __ _| |_ ___ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
+// \___ \| __/ _` | __/ _ \ | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
+// ___) | || (_| | || __/ | | | | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
+// |____/ \__\__,_|\__\___| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
+// |___/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Poor-man's state machine, which effectively ensures everything is initialized in order by verifying actual initialization state
+// rather than stepping through a set of numeric states. This way, if something fails in an out-of-order sort of way, we can still
+// handle it and recover nicely.
+void initializationStateProcessor()
+{
+ // If we're in our end-of-life or waiting for a retry, don't process states
+ if (ggkGetServerRunState() > ERunning || 0 != retryTimeStart) { return; }
+
+ //
+ // Get a bus connection
+ //
+ if (nullptr == pBusConnection)
+ {
+ Logger::info(SSTR << "Acquiring bus connection");
+ doBusAcquire();
+ return;
+ }
+
+ //
+ // Acquire an owned name on the bus
+ //
+ if (!bOwnedNameAcquired)
+ {
+ Logger::info(SSTR << "Acquiring owned name: '" << kServerOwnedName << "'");
+ doOwnedNameAcquire();
+ return;
+ }
+
+ //
+ // Get BlueZ's ObjectManager
+ //
+ if (nullptr == pBluezObjectManager)
+ {
+ Logger::debug(SSTR << "Getting BlueZ ObjectManager");
+ getBluezObjectManager();
+ return;
+ }
+
+ //
+ // Find the adapter interface
+ //
+ if (bluezGattManagerInterfaceName.empty())
+ {
+ Logger::debug(SSTR << "Finding BlueZ GattManager1 interface");
+ findAdapterInterface();
+ return;
+ }
+
+ //
+ // Find the adapter interface
+ //
+ if (!bAdapterConfigured)
+ {
+ Logger::info(SSTR << "Configuring BlueZ adapter '" << bluezGattManagerInterfaceName << "'");
+ configureAdapter();
+ return;
+ }
+
+ //
+ // Register our object with D-bus
+ //
+ if (registeredObjectIds.empty())
+ {
+ Logger::info(SSTR << "Registering with D-Bus");
+ registerObjects();
+ return;
+ }
+
+ // Register our appliation with the BlueZ GATT manager
+ if (!bApplicationRegistered)
+ {
+ Logger::info(SSTR << "Registering application with BlueZ GATT manager");
+
+ doRegisterApplication();
+ return;
+ }
+
+ // At this point, we should be fully initialized
+ //
+ // It shouldn't ever happen, but just in case, let's double-check that we're healthy and if not, shutdown immediately
+ if (ggkGetServerHealth() != EOk)
+ {
+ shutdown();
+ return;
+ }
+
+ // Successful initialization - switch to running state
+ setServerRunState(ERunning);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// ____ _
+// | _ \ _ _ _ __ ___ ___ _ ____ _____ _ __ _ __ _ _ _ __ | |
+// | |_) | | | | '_ \ / __|/ _ \ '__\ \ / / _ \ '__| | '__| | | | '_ \| |
+// | _ <| |_| | | | | \__ \ __/ | \ V / __/ | _ | | | |_| | | | |_|
+// |_| \_\\__,_|_| |_| |___/\___|_| \_/ \___|_|( ) |_| \__,_|_| |_(_)
+// |/
+//
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Entry point for the asynchronous server thread
+//
+// This method should not be called directly, instead, direct your attention over to `ggkStart()`
+void runServerThread()
+{
+ // Set the initialization state
+ setServerRunState(EInitializing);
+
+ // Start our state processor, which is really just a simplified state machine that steps us through an asynchronous
+ // initialization process.
+ //
+ // In case you're wondering if these really need to be async, the answer is yes. For one, it's the right way to do it. But
+ // the more practical response is that the main loop must be running (see below) in order for us to receive and respond to
+ // events from BlueZ. Well, one of the calls we need to make during initialization is 'RegisterApplication'. This method will
+ // will require that we respond the 'GetNamedObjects' method before it returns. If we were to call the synchronous version of
+ // 'RegisterApplication', then we've effectively created a deadlock.
+ //
+ // There are alternatives, but using async methods is the recommended way.
+ initializationStateProcessor();
+
+ Logger::info(SSTR << "Starting main loop");
+ pMainLoop = g_main_loop_new(NULL, FALSE);
+
+ // Add the idle function
+ //
+ // Note that we actually run the idle function from a lambda. This allows us to manage the inter-idle sleep so we don't
+ // soak up 100% of our CPU.
+ guint res = g_idle_add
+ (
+ [](gpointer pUserData) -> gboolean
+ {
+ // Try to process some data and if no data is processed, sleep for the requested frequency
+ if (!idleFunc(pUserData))
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(kIdleFrequencyMS));
+ }
+
+ // Always return TRUE so our idle remains in tact
+ return TRUE;
+ },
+ nullptr
+ );
+
+ if (res == 0)
+ {
+ Logger::error(SSTR << "Unable to add idle to main loop");
+ }
+ else
+ {
+ Logger::info("Idle function added successfully");
+ }
+
+ g_main_loop_run(pMainLoop);
+
+ // We have stopped
+ setServerRunState(EStopped);
+
+ // Cleanup
+ uninit();
+}
diff --git a/src/Init.h b/src/Init.h
new file mode 100644
index 0000000..cad7785
--- /dev/null
+++ b/src/Init.h
@@ -0,0 +1,43 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// Herein lies the code that manages the full initialization (including the running) of the server
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of Init.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+// Trigger a graceful, asynchronous shutdown of the server
+//
+// This method is non-blocking and as such, will only trigger the shutdown process but not wait for it
+void shutdown();
+
+// Entry point for the asynchronous server thread
+//
+// This method should not be called directly, instead, direct your attention over to `ggkStart()`
+void runServerThread();
diff --git a/src/Logger.cpp b/src/Logger.cpp
new file mode 100644
index 0000000..7607ac8
--- /dev/null
+++ b/src/Logger.cpp
@@ -0,0 +1,200 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our Logger, which allows for applications to use their own logging mechanisms by registering log receivers for each of
+// the logging categories.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// In order for logging to work, the application must register logging receivers with the `ggkLogRegister*` methods. At that point,
+// it is up to the application to decide how to handle the incoming log requests. For example, it may filter Error and Fatal logs
+// to stderr.
+//
+// The following is a breakdown of the intended use of each of the available categories. Application receivers can filter then
+// (enable/ disable/redirect/etc.) in whatever way works best (via bitmasks, log levels, etc.)
+//
+// Debug - Lowest logging level; may contain log-spam
+// Info - The logging equivalent to the junk drawer
+// Status - Current health/service availability info (ex: "Server starting", "Initialization complete", "Stop requested")
+// Warn - Problems that may or may not affect functionality
+// Error - Problems that will affect functionality, but do not result in a termination of the process
+// Fatal - Issues that so severe as to require termination
+//
+// The following log categories should be considered very important (and possibly never filtered):
+//
+// Always - Use when something absolutely, positively has to show up in the log output
+// Trace - Useful for tracing through code (ex: "Before call to foo()", "Entering function bar") and other development use
+// only and should be removed before a release.
+//
+// Using the logger is simple:
+//
+// Logger::error("Unable to locate configuration file (this is probably bad)");
+//
+// There is an additional macro (SSTR) which can simplify sending dynamic data to the logger via a string stream:
+//
+// Logger::info(SSTR << "There were " << count << " entries in the list");
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "Logger.h"
+
+//
+// Log receiver delegates
+//
+
+// The registered log receiver for DEBUG logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverDebug = nullptr;
+
+// The registered log receiver for INFO logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverInfo = nullptr;
+
+// The registered log receiver for STATUS logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverStatus = nullptr;
+
+// The registered log receiver for WARN logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverWarn = nullptr;
+
+// The registered log receiver for ERROR logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverError = nullptr;
+
+// The registered log receiver for FATAL logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverFatal = nullptr;
+
+// The registered log receiver for ALWAYS logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverAlways = nullptr;
+
+// The registered log receiver for TRACE logs - a nullptr will cause the logging for that receiver to be ignored
+GGKLogReceiver Logger::logReceiverTrace = nullptr;
+
+//
+// Registration
+//
+
+// Register logging receiver for DEBUG logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerDebugReceiver(GGKLogReceiver receiver) { logReceiverDebug = receiver; }
+
+// Register logging receiver for INFO logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerInfoReceiver(GGKLogReceiver receiver) { logReceiverInfo = receiver; }
+
+// Register logging receiver for STATUS logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerStatusReceiver(GGKLogReceiver receiver) { logReceiverStatus = receiver; }
+
+// Register logging receiver for WARN logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerWarnReceiver(GGKLogReceiver receiver) { logReceiverWarn = receiver; }
+
+// Register logging receiver for ERROR logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerErrorReceiver(GGKLogReceiver receiver) { logReceiverError = receiver; }
+
+// Register logging receiver for FATAL logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerFatalReceiver(GGKLogReceiver receiver) { logReceiverFatal = receiver; }
+
+// Register logging receiver for ALWAYS logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerAlwaysReceiver(GGKLogReceiver receiver) { logReceiverAlways = receiver; }
+
+// Register logging receiver for TRACE logging. To register a logging level, simply call with a delegate that performs the
+// appropriate logging action. To unregister, call with `nullptr`
+void Logger::registerTraceReceiver(GGKLogReceiver receiver) { logReceiverTrace = receiver; }
+
+//
+// Logging actions
+//
+
+// Log a DEBUG entry with a C string
+void Logger::debug(const char *pText) { if (nullptr != logReceiverDebug) { logReceiverDebug(pText); } }
+
+// Log a DEBUG entry with a string
+void Logger::debug(const std::string &text) { if (nullptr != logReceiverDebug) { debug(text.c_str()); } }
+
+// Log a DEBUG entry using a stream
+void Logger::debug(const std::ostream &text) { if (nullptr != logReceiverDebug) { debug(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a INFO entry with a C string
+void Logger::info(const char *pText) { if (nullptr != logReceiverInfo) { logReceiverInfo(pText); } }
+
+// Log a INFO entry with a string
+void Logger::info(const std::string &text) { if (nullptr != logReceiverInfo) { info(text.c_str()); } }
+
+// Log a INFO entry using a stream
+void Logger::info(const std::ostream &text) { if (nullptr != logReceiverInfo) { info(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a STATUS entry with a C string
+void Logger::status(const char *pText) { if (nullptr != logReceiverStatus) { logReceiverStatus(pText); } }
+
+// Log a STATUS entry with a string
+void Logger::status(const std::string &text) { if (nullptr != logReceiverStatus) { status(text.c_str()); } }
+
+// Log a STATUS entry using a stream
+void Logger::status(const std::ostream &text) { if (nullptr != logReceiverStatus) { status(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a WARN entry with a C string
+void Logger::warn(const char *pText) { if (nullptr != logReceiverWarn) { logReceiverWarn(pText); } }
+
+// Log a WARN entry with a string
+void Logger::warn(const std::string &text) { if (nullptr != logReceiverWarn) { warn(text.c_str()); } }
+
+// Log a WARN entry using a stream
+void Logger::warn(const std::ostream &text) { if (nullptr != logReceiverWarn) { warn(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a ERROR entry with a C string
+void Logger::error(const char *pText) { if (nullptr != logReceiverError) { logReceiverError(pText); } }
+
+// Log a ERROR entry with a string
+void Logger::error(const std::string &text) { if (nullptr != logReceiverError) { error(text.c_str()); } }
+
+// Log a ERROR entry using a stream
+void Logger::error(const std::ostream &text) { if (nullptr != logReceiverError) { error(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a FATAL entry with a C string
+void Logger::fatal(const char *pText) { if (nullptr != logReceiverFatal) { logReceiverFatal(pText); } }
+
+// Log a FATAL entry with a string
+void Logger::fatal(const std::string &text) { if (nullptr != logReceiverFatal) { fatal(text.c_str()); } }
+
+// Log a FATAL entry using a stream
+void Logger::fatal(const std::ostream &text) { if (nullptr != logReceiverFatal) { fatal(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a ALWAYS entry with a C string
+void Logger::always(const char *pText) { if (nullptr != logReceiverAlways) { logReceiverAlways(pText); } }
+
+// Log a ALWAYS entry with a string
+void Logger::always(const std::string &text) { if (nullptr != logReceiverAlways) { always(text.c_str()); } }
+
+// Log a ALWAYS entry using a stream
+void Logger::always(const std::ostream &text) { if (nullptr != logReceiverAlways) { always(static_cast<const std::ostringstream &>(text).str().c_str()); } }
+
+// Log a TRACE entry with a C string
+void Logger::trace(const char *pText) { if (nullptr != logReceiverTrace) { logReceiverTrace(pText); } }
+
+// Log a TRACE entry with a string
+void Logger::trace(const std::string &text) { if (nullptr != logReceiverTrace) { trace(text.c_str()); } }
+
+// Log a TRACE entry using a stream
+void Logger::trace(const std::ostream &text) { if (nullptr != logReceiverTrace) { trace(static_cast<const std::ostringstream &>(text).str().c_str()); } }
diff --git a/src/Logger.h b/src/Logger.h
new file mode 100644
index 0000000..08c1221
--- /dev/null
+++ b/src/Logger.h
@@ -0,0 +1,185 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is our Logger, which allows for applications to use their own logging mechanisms by registering log receivers for each of
+// the logging categories.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of Logger.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <sstream>
+
+#include "../include/Gobbledegook.h"
+
+// Our handy stringstream macro
+#define SSTR std::ostringstream().flush()
+
+class Logger
+{
+public:
+
+ //
+ // Registration
+ //
+
+ // Register logging receiver for DEBUG logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerDebugReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for INFO logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerInfoReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for STATUS logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerStatusReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for WARN logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerWarnReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for ERROR logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerErrorReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for FATAL logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerFatalReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for ALWAYS logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerAlwaysReceiver(GGKLogReceiver receiver);
+
+ // Register logging receiver for TRACE logging. To register a logging level, simply call with a delegate that performs the
+ // appropriate logging action. To unregister, call with `nullptr`
+ static void registerTraceReceiver(GGKLogReceiver receiver);
+
+
+ //
+ // Logging actions
+ //
+
+ // Log a DEBUG entry with a C string
+ static void debug(const char *pText);
+
+ // Log a DEBUG entry with a string
+ static void debug(const std::string &text);
+
+ // Log a DEBUG entry using a stream
+ static void debug(const std::ostream &text);
+
+ // Log a INFO entry with a C string
+ static void info(const char *pText);
+
+ // Log a INFO entry with a string
+ static void info(const std::string &text);
+
+ // Log a INFO entry using a stream
+ static void info(const std::ostream &text);
+
+ // Log a STATUS entry with a C string
+ static void status(const char *pText);
+
+ // Log a STATUS entry with a string
+ static void status(const std::string &text);
+
+ // Log a STATUS entry using a stream
+ static void status(const std::ostream &text);
+
+ // Log a WARN entry with a C string
+ static void warn(const char *pText);
+
+ // Log a WARN entry with a string
+ static void warn(const std::string &text);
+
+ // Log a WARN entry using a stream
+ static void warn(const std::ostream &text);
+
+ // Log a ERROR entry with a C string
+ static void error(const char *pText);
+
+ // Log a ERROR entry with a string
+ static void error(const std::string &text);
+
+ // Log a ERROR entry using a stream
+ static void error(const std::ostream &text);
+
+ // Log a FATAL entry with a C string
+ static void fatal(const char *pText);
+
+ // Log a FATAL entry with a string
+ static void fatal(const std::string &text);
+
+ // Log a FATAL entry using a stream
+ static void fatal(const std::ostream &text);
+
+ // Log a ALWAYS entry with a C string
+ static void always(const char *pText);
+
+ // Log a ALWAYS entry with a string
+ static void always(const std::string &text);
+
+ // Log a ALWAYS entry using a stream
+ static void always(const std::ostream &text);
+
+ // Log a TRACE entry with a C string
+ static void trace(const char *pText);
+
+ // Log a TRACE entry with a string
+ static void trace(const std::string &text);
+
+ // Log a TRACE entry using a stream
+ static void trace(const std::ostream &text);
+
+private:
+
+ // The registered log receiver for DEBUG logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverDebug;
+
+ // The registered log receiver for INFO logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverInfo;
+
+ // The registered log receiver for STATUS logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverStatus;
+
+ // The registered log receiver for WARN logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverWarn;
+
+ // The registered log receiver for ERROR logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverError;
+
+ // The registered log receiver for FATAL logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverFatal;
+
+ // The registered log receiver for ALWAYS logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverAlways;
+
+ // The registered log receiver for TRACE logs - a nullptr will cause the logging for that receiver to be ignored
+ static GGKLogReceiver logReceiverTrace;
+};
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..9a8aea4
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,48 @@
+# Build a static library (libggk.a)
+noinst_LIBRARIES = libggk.a
+libggk_a_CXXFLAGS = -fPIC -Wall -Wextra -std=c++11 $(GLIB_CFLAGS) $(GIO_CFLAGS) $(GOBJECT_CFLAGS)
+libggk_a_SOURCES = DBusInterface.cpp \
+ DBusInterface.h \
+ DBusMethod.cpp \
+ DBusMethod.h \
+ DBusObject.cpp \
+ DBusObject.h \
+ DBusObjectPath.h \
+ GattCharacteristic.cpp \
+ GattCharacteristic.h \
+ GattDescriptor.cpp \
+ GattDescriptor.h \
+ GattInterface.cpp \
+ GattInterface.h \
+ GattProperty.cpp \
+ GattProperty.h \
+ GattService.cpp \
+ GattService.h \
+ GattUuid.h \
+ Globals.h \
+ Gobbledegook.cpp \
+ ../include/Gobbledegook.h \
+ HciAdapter.cpp \
+ HciAdapter.h \
+ HciSocket.cpp \
+ HciSocket.h \
+ Init.cpp \
+ Init.h \
+ Logger.cpp \
+ Logger.h \
+ Mgmt.cpp \
+ Mgmt.h \
+ Server.cpp \
+ Server.h \
+ ServerUtils.cpp \
+ ServerUtils.h \
+ standalone.cpp \
+ TickEvent.h \
+ Utils.cpp \
+ Utils.h
+# Build our standalone server (linking statically with libggk.a, linking dynamically with GLib)
+standalone_CXXFLAGS = -fPIC -Wall -Wextra -std=c++11
+noinst_PROGRAMS = standalone
+standalone_SOURCES = standalone.cpp
+standalone_LDADD = libggk.a
+standalone_LDLIBS = $(GLIB_LIBS) $(GIO_LIBS) $(GOBJECT_LIBS)
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..9a1903f
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,909 @@
+# Makefile.in generated by automake 1.15 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = standalone$(EXEEXT)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = \
+ $(top_srcdir)/build-aux/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libggk_a_AR = $(AR) $(ARFLAGS)
+libggk_a_LIBADD =
+am_libggk_a_OBJECTS = libggk_a-DBusInterface.$(OBJEXT) \
+ libggk_a-DBusMethod.$(OBJEXT) libggk_a-DBusObject.$(OBJEXT) \
+ libggk_a-GattCharacteristic.$(OBJEXT) \
+ libggk_a-GattDescriptor.$(OBJEXT) \
+ libggk_a-GattInterface.$(OBJEXT) \
+ libggk_a-GattProperty.$(OBJEXT) libggk_a-GattService.$(OBJEXT) \
+ libggk_a-Gobbledegook.$(OBJEXT) libggk_a-HciAdapter.$(OBJEXT) \
+ libggk_a-HciSocket.$(OBJEXT) libggk_a-Init.$(OBJEXT) \
+ libggk_a-Logger.$(OBJEXT) libggk_a-Mgmt.$(OBJEXT) \
+ libggk_a-Server.$(OBJEXT) libggk_a-ServerUtils.$(OBJEXT) \
+ libggk_a-standalone.$(OBJEXT) libggk_a-Utils.$(OBJEXT)
+libggk_a_OBJECTS = $(am_libggk_a_OBJECTS)
+PROGRAMS = $(noinst_PROGRAMS)
+am_standalone_OBJECTS = standalone-standalone.$(OBJEXT)
+standalone_OBJECTS = $(am_standalone_OBJECTS)
+standalone_DEPENDENCIES = libggk.a
+standalone_LINK = $(CXXLD) $(standalone_CXXFLAGS) $(CXXFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \
+ -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libggk_a_SOURCES) $(standalone_SOURCES)
+DIST_SOURCES = $(libggk_a_SOURCES) $(standalone_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EXEEXT = @EXEEXT@
+GIO_CFLAGS = @GIO_CFLAGS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GOBJECT_CFLAGS = @GOBJECT_CFLAGS@
+HAVE_CXX11 = @HAVE_CXX11@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# Build a static library (libggk.a)
+noinst_LIBRARIES = libggk.a
+libggk_a_CXXFLAGS = -fPIC -Wall -Wextra -std=c++11 $(GLIB_CFLAGS) $(GIO_CFLAGS) $(GOBJECT_CFLAGS)
+libggk_a_SOURCES = DBusInterface.cpp \
+ DBusInterface.h \
+ DBusMethod.cpp \
+ DBusMethod.h \
+ DBusObject.cpp \
+ DBusObject.h \
+ DBusObjectPath.h \
+ GattCharacteristic.cpp \
+ GattCharacteristic.h \
+ GattDescriptor.cpp \
+ GattDescriptor.h \
+ GattInterface.cpp \
+ GattInterface.h \
+ GattProperty.cpp \
+ GattProperty.h \
+ GattService.cpp \
+ GattService.h \
+ GattUuid.h \
+ Globals.h \
+ Gobbledegook.cpp \
+ ../include/Gobbledegook.h \
+ HciAdapter.cpp \
+ HciAdapter.h \
+ HciSocket.cpp \
+ HciSocket.h \
+ Init.cpp \
+ Init.h \
+ Logger.cpp \
+ Logger.h \
+ Mgmt.cpp \
+ Mgmt.h \
+ Server.cpp \
+ Server.h \
+ ServerUtils.cpp \
+ ServerUtils.h \
+ standalone.cpp \
+ TickEvent.h \
+ Utils.cpp \
+ Utils.h
+
+# Build our standalone server (linking statically with libggk.a, linking dynamically with GLib)
+standalone_CXXFLAGS = -fPIC -Wall -Wextra -std=c++11
+standalone_SOURCES = standalone.cpp
+standalone_LDADD = libggk.a
+standalone_LDLIBS = $(GLIB_LIBS) $(GIO_LIBS) $(GOBJECT_LIBS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cpp .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libggk.a: $(libggk_a_OBJECTS) $(libggk_a_DEPENDENCIES) $(EXTRA_libggk_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libggk.a
+ $(AM_V_AR)$(libggk_a_AR) libggk.a $(libggk_a_OBJECTS) $(libggk_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libggk.a
+
+clean-noinstPROGRAMS:
+ -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+
+standalone$(EXEEXT): $(standalone_OBJECTS) $(standalone_DEPENDENCIES) $(EXTRA_standalone_DEPENDENCIES)
+ @rm -f standalone$(EXEEXT)
+ $(AM_V_CXXLD)$(standalone_LINK) $(standalone_OBJECTS) $(standalone_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-DBusInterface.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-DBusMethod.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-DBusObject.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-GattCharacteristic.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-GattDescriptor.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-GattInterface.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-GattProperty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-GattService.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Gobbledegook.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-HciAdapter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-HciSocket.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Logger.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Mgmt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-ServerUtils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-Utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libggk_a-standalone.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/standalone-standalone.Po@am__quote@
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+libggk_a-DBusInterface.o: DBusInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusInterface.o -MD -MP -MF $(DEPDIR)/libggk_a-DBusInterface.Tpo -c -o libggk_a-DBusInterface.o `test -f 'DBusInterface.cpp' || echo '$(srcdir)/'`DBusInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusInterface.Tpo $(DEPDIR)/libggk_a-DBusInterface.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusInterface.cpp' object='libggk_a-DBusInterface.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusInterface.o `test -f 'DBusInterface.cpp' || echo '$(srcdir)/'`DBusInterface.cpp
+
+libggk_a-DBusInterface.obj: DBusInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusInterface.obj -MD -MP -MF $(DEPDIR)/libggk_a-DBusInterface.Tpo -c -o libggk_a-DBusInterface.obj `if test -f 'DBusInterface.cpp'; then $(CYGPATH_W) 'DBusInterface.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusInterface.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusInterface.Tpo $(DEPDIR)/libggk_a-DBusInterface.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusInterface.cpp' object='libggk_a-DBusInterface.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusInterface.obj `if test -f 'DBusInterface.cpp'; then $(CYGPATH_W) 'DBusInterface.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusInterface.cpp'; fi`
+
+libggk_a-DBusMethod.o: DBusMethod.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusMethod.o -MD -MP -MF $(DEPDIR)/libggk_a-DBusMethod.Tpo -c -o libggk_a-DBusMethod.o `test -f 'DBusMethod.cpp' || echo '$(srcdir)/'`DBusMethod.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusMethod.Tpo $(DEPDIR)/libggk_a-DBusMethod.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusMethod.cpp' object='libggk_a-DBusMethod.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusMethod.o `test -f 'DBusMethod.cpp' || echo '$(srcdir)/'`DBusMethod.cpp
+
+libggk_a-DBusMethod.obj: DBusMethod.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusMethod.obj -MD -MP -MF $(DEPDIR)/libggk_a-DBusMethod.Tpo -c -o libggk_a-DBusMethod.obj `if test -f 'DBusMethod.cpp'; then $(CYGPATH_W) 'DBusMethod.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusMethod.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusMethod.Tpo $(DEPDIR)/libggk_a-DBusMethod.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusMethod.cpp' object='libggk_a-DBusMethod.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusMethod.obj `if test -f 'DBusMethod.cpp'; then $(CYGPATH_W) 'DBusMethod.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusMethod.cpp'; fi`
+
+libggk_a-DBusObject.o: DBusObject.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusObject.o -MD -MP -MF $(DEPDIR)/libggk_a-DBusObject.Tpo -c -o libggk_a-DBusObject.o `test -f 'DBusObject.cpp' || echo '$(srcdir)/'`DBusObject.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusObject.Tpo $(DEPDIR)/libggk_a-DBusObject.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusObject.cpp' object='libggk_a-DBusObject.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusObject.o `test -f 'DBusObject.cpp' || echo '$(srcdir)/'`DBusObject.cpp
+
+libggk_a-DBusObject.obj: DBusObject.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-DBusObject.obj -MD -MP -MF $(DEPDIR)/libggk_a-DBusObject.Tpo -c -o libggk_a-DBusObject.obj `if test -f 'DBusObject.cpp'; then $(CYGPATH_W) 'DBusObject.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusObject.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-DBusObject.Tpo $(DEPDIR)/libggk_a-DBusObject.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='DBusObject.cpp' object='libggk_a-DBusObject.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-DBusObject.obj `if test -f 'DBusObject.cpp'; then $(CYGPATH_W) 'DBusObject.cpp'; else $(CYGPATH_W) '$(srcdir)/DBusObject.cpp'; fi`
+
+libggk_a-GattCharacteristic.o: GattCharacteristic.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattCharacteristic.o -MD -MP -MF $(DEPDIR)/libggk_a-GattCharacteristic.Tpo -c -o libggk_a-GattCharacteristic.o `test -f 'GattCharacteristic.cpp' || echo '$(srcdir)/'`GattCharacteristic.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattCharacteristic.Tpo $(DEPDIR)/libggk_a-GattCharacteristic.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattCharacteristic.cpp' object='libggk_a-GattCharacteristic.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattCharacteristic.o `test -f 'GattCharacteristic.cpp' || echo '$(srcdir)/'`GattCharacteristic.cpp
+
+libggk_a-GattCharacteristic.obj: GattCharacteristic.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattCharacteristic.obj -MD -MP -MF $(DEPDIR)/libggk_a-GattCharacteristic.Tpo -c -o libggk_a-GattCharacteristic.obj `if test -f 'GattCharacteristic.cpp'; then $(CYGPATH_W) 'GattCharacteristic.cpp'; else $(CYGPATH_W) '$(srcdir)/GattCharacteristic.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattCharacteristic.Tpo $(DEPDIR)/libggk_a-GattCharacteristic.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattCharacteristic.cpp' object='libggk_a-GattCharacteristic.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattCharacteristic.obj `if test -f 'GattCharacteristic.cpp'; then $(CYGPATH_W) 'GattCharacteristic.cpp'; else $(CYGPATH_W) '$(srcdir)/GattCharacteristic.cpp'; fi`
+
+libggk_a-GattDescriptor.o: GattDescriptor.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattDescriptor.o -MD -MP -MF $(DEPDIR)/libggk_a-GattDescriptor.Tpo -c -o libggk_a-GattDescriptor.o `test -f 'GattDescriptor.cpp' || echo '$(srcdir)/'`GattDescriptor.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattDescriptor.Tpo $(DEPDIR)/libggk_a-GattDescriptor.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattDescriptor.cpp' object='libggk_a-GattDescriptor.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattDescriptor.o `test -f 'GattDescriptor.cpp' || echo '$(srcdir)/'`GattDescriptor.cpp
+
+libggk_a-GattDescriptor.obj: GattDescriptor.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattDescriptor.obj -MD -MP -MF $(DEPDIR)/libggk_a-GattDescriptor.Tpo -c -o libggk_a-GattDescriptor.obj `if test -f 'GattDescriptor.cpp'; then $(CYGPATH_W) 'GattDescriptor.cpp'; else $(CYGPATH_W) '$(srcdir)/GattDescriptor.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattDescriptor.Tpo $(DEPDIR)/libggk_a-GattDescriptor.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattDescriptor.cpp' object='libggk_a-GattDescriptor.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattDescriptor.obj `if test -f 'GattDescriptor.cpp'; then $(CYGPATH_W) 'GattDescriptor.cpp'; else $(CYGPATH_W) '$(srcdir)/GattDescriptor.cpp'; fi`
+
+libggk_a-GattInterface.o: GattInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattInterface.o -MD -MP -MF $(DEPDIR)/libggk_a-GattInterface.Tpo -c -o libggk_a-GattInterface.o `test -f 'GattInterface.cpp' || echo '$(srcdir)/'`GattInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattInterface.Tpo $(DEPDIR)/libggk_a-GattInterface.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattInterface.cpp' object='libggk_a-GattInterface.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattInterface.o `test -f 'GattInterface.cpp' || echo '$(srcdir)/'`GattInterface.cpp
+
+libggk_a-GattInterface.obj: GattInterface.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattInterface.obj -MD -MP -MF $(DEPDIR)/libggk_a-GattInterface.Tpo -c -o libggk_a-GattInterface.obj `if test -f 'GattInterface.cpp'; then $(CYGPATH_W) 'GattInterface.cpp'; else $(CYGPATH_W) '$(srcdir)/GattInterface.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattInterface.Tpo $(DEPDIR)/libggk_a-GattInterface.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattInterface.cpp' object='libggk_a-GattInterface.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattInterface.obj `if test -f 'GattInterface.cpp'; then $(CYGPATH_W) 'GattInterface.cpp'; else $(CYGPATH_W) '$(srcdir)/GattInterface.cpp'; fi`
+
+libggk_a-GattProperty.o: GattProperty.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattProperty.o -MD -MP -MF $(DEPDIR)/libggk_a-GattProperty.Tpo -c -o libggk_a-GattProperty.o `test -f 'GattProperty.cpp' || echo '$(srcdir)/'`GattProperty.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattProperty.Tpo $(DEPDIR)/libggk_a-GattProperty.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattProperty.cpp' object='libggk_a-GattProperty.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattProperty.o `test -f 'GattProperty.cpp' || echo '$(srcdir)/'`GattProperty.cpp
+
+libggk_a-GattProperty.obj: GattProperty.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattProperty.obj -MD -MP -MF $(DEPDIR)/libggk_a-GattProperty.Tpo -c -o libggk_a-GattProperty.obj `if test -f 'GattProperty.cpp'; then $(CYGPATH_W) 'GattProperty.cpp'; else $(CYGPATH_W) '$(srcdir)/GattProperty.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattProperty.Tpo $(DEPDIR)/libggk_a-GattProperty.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattProperty.cpp' object='libggk_a-GattProperty.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattProperty.obj `if test -f 'GattProperty.cpp'; then $(CYGPATH_W) 'GattProperty.cpp'; else $(CYGPATH_W) '$(srcdir)/GattProperty.cpp'; fi`
+
+libggk_a-GattService.o: GattService.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattService.o -MD -MP -MF $(DEPDIR)/libggk_a-GattService.Tpo -c -o libggk_a-GattService.o `test -f 'GattService.cpp' || echo '$(srcdir)/'`GattService.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattService.Tpo $(DEPDIR)/libggk_a-GattService.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattService.cpp' object='libggk_a-GattService.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattService.o `test -f 'GattService.cpp' || echo '$(srcdir)/'`GattService.cpp
+
+libggk_a-GattService.obj: GattService.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-GattService.obj -MD -MP -MF $(DEPDIR)/libggk_a-GattService.Tpo -c -o libggk_a-GattService.obj `if test -f 'GattService.cpp'; then $(CYGPATH_W) 'GattService.cpp'; else $(CYGPATH_W) '$(srcdir)/GattService.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-GattService.Tpo $(DEPDIR)/libggk_a-GattService.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='GattService.cpp' object='libggk_a-GattService.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-GattService.obj `if test -f 'GattService.cpp'; then $(CYGPATH_W) 'GattService.cpp'; else $(CYGPATH_W) '$(srcdir)/GattService.cpp'; fi`
+
+libggk_a-Gobbledegook.o: Gobbledegook.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Gobbledegook.o -MD -MP -MF $(DEPDIR)/libggk_a-Gobbledegook.Tpo -c -o libggk_a-Gobbledegook.o `test -f 'Gobbledegook.cpp' || echo '$(srcdir)/'`Gobbledegook.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Gobbledegook.Tpo $(DEPDIR)/libggk_a-Gobbledegook.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Gobbledegook.cpp' object='libggk_a-Gobbledegook.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Gobbledegook.o `test -f 'Gobbledegook.cpp' || echo '$(srcdir)/'`Gobbledegook.cpp
+
+libggk_a-Gobbledegook.obj: Gobbledegook.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Gobbledegook.obj -MD -MP -MF $(DEPDIR)/libggk_a-Gobbledegook.Tpo -c -o libggk_a-Gobbledegook.obj `if test -f 'Gobbledegook.cpp'; then $(CYGPATH_W) 'Gobbledegook.cpp'; else $(CYGPATH_W) '$(srcdir)/Gobbledegook.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Gobbledegook.Tpo $(DEPDIR)/libggk_a-Gobbledegook.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Gobbledegook.cpp' object='libggk_a-Gobbledegook.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Gobbledegook.obj `if test -f 'Gobbledegook.cpp'; then $(CYGPATH_W) 'Gobbledegook.cpp'; else $(CYGPATH_W) '$(srcdir)/Gobbledegook.cpp'; fi`
+
+libggk_a-HciAdapter.o: HciAdapter.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-HciAdapter.o -MD -MP -MF $(DEPDIR)/libggk_a-HciAdapter.Tpo -c -o libggk_a-HciAdapter.o `test -f 'HciAdapter.cpp' || echo '$(srcdir)/'`HciAdapter.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-HciAdapter.Tpo $(DEPDIR)/libggk_a-HciAdapter.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='HciAdapter.cpp' object='libggk_a-HciAdapter.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-HciAdapter.o `test -f 'HciAdapter.cpp' || echo '$(srcdir)/'`HciAdapter.cpp
+
+libggk_a-HciAdapter.obj: HciAdapter.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-HciAdapter.obj -MD -MP -MF $(DEPDIR)/libggk_a-HciAdapter.Tpo -c -o libggk_a-HciAdapter.obj `if test -f 'HciAdapter.cpp'; then $(CYGPATH_W) 'HciAdapter.cpp'; else $(CYGPATH_W) '$(srcdir)/HciAdapter.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-HciAdapter.Tpo $(DEPDIR)/libggk_a-HciAdapter.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='HciAdapter.cpp' object='libggk_a-HciAdapter.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-HciAdapter.obj `if test -f 'HciAdapter.cpp'; then $(CYGPATH_W) 'HciAdapter.cpp'; else $(CYGPATH_W) '$(srcdir)/HciAdapter.cpp'; fi`
+
+libggk_a-HciSocket.o: HciSocket.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-HciSocket.o -MD -MP -MF $(DEPDIR)/libggk_a-HciSocket.Tpo -c -o libggk_a-HciSocket.o `test -f 'HciSocket.cpp' || echo '$(srcdir)/'`HciSocket.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-HciSocket.Tpo $(DEPDIR)/libggk_a-HciSocket.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='HciSocket.cpp' object='libggk_a-HciSocket.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-HciSocket.o `test -f 'HciSocket.cpp' || echo '$(srcdir)/'`HciSocket.cpp
+
+libggk_a-HciSocket.obj: HciSocket.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-HciSocket.obj -MD -MP -MF $(DEPDIR)/libggk_a-HciSocket.Tpo -c -o libggk_a-HciSocket.obj `if test -f 'HciSocket.cpp'; then $(CYGPATH_W) 'HciSocket.cpp'; else $(CYGPATH_W) '$(srcdir)/HciSocket.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-HciSocket.Tpo $(DEPDIR)/libggk_a-HciSocket.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='HciSocket.cpp' object='libggk_a-HciSocket.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-HciSocket.obj `if test -f 'HciSocket.cpp'; then $(CYGPATH_W) 'HciSocket.cpp'; else $(CYGPATH_W) '$(srcdir)/HciSocket.cpp'; fi`
+
+libggk_a-Init.o: Init.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Init.o -MD -MP -MF $(DEPDIR)/libggk_a-Init.Tpo -c -o libggk_a-Init.o `test -f 'Init.cpp' || echo '$(srcdir)/'`Init.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Init.Tpo $(DEPDIR)/libggk_a-Init.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Init.cpp' object='libggk_a-Init.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Init.o `test -f 'Init.cpp' || echo '$(srcdir)/'`Init.cpp
+
+libggk_a-Init.obj: Init.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Init.obj -MD -MP -MF $(DEPDIR)/libggk_a-Init.Tpo -c -o libggk_a-Init.obj `if test -f 'Init.cpp'; then $(CYGPATH_W) 'Init.cpp'; else $(CYGPATH_W) '$(srcdir)/Init.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Init.Tpo $(DEPDIR)/libggk_a-Init.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Init.cpp' object='libggk_a-Init.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Init.obj `if test -f 'Init.cpp'; then $(CYGPATH_W) 'Init.cpp'; else $(CYGPATH_W) '$(srcdir)/Init.cpp'; fi`
+
+libggk_a-Logger.o: Logger.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Logger.o -MD -MP -MF $(DEPDIR)/libggk_a-Logger.Tpo -c -o libggk_a-Logger.o `test -f 'Logger.cpp' || echo '$(srcdir)/'`Logger.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Logger.Tpo $(DEPDIR)/libggk_a-Logger.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Logger.cpp' object='libggk_a-Logger.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Logger.o `test -f 'Logger.cpp' || echo '$(srcdir)/'`Logger.cpp
+
+libggk_a-Logger.obj: Logger.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Logger.obj -MD -MP -MF $(DEPDIR)/libggk_a-Logger.Tpo -c -o libggk_a-Logger.obj `if test -f 'Logger.cpp'; then $(CYGPATH_W) 'Logger.cpp'; else $(CYGPATH_W) '$(srcdir)/Logger.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Logger.Tpo $(DEPDIR)/libggk_a-Logger.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Logger.cpp' object='libggk_a-Logger.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Logger.obj `if test -f 'Logger.cpp'; then $(CYGPATH_W) 'Logger.cpp'; else $(CYGPATH_W) '$(srcdir)/Logger.cpp'; fi`
+
+libggk_a-Mgmt.o: Mgmt.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Mgmt.o -MD -MP -MF $(DEPDIR)/libggk_a-Mgmt.Tpo -c -o libggk_a-Mgmt.o `test -f 'Mgmt.cpp' || echo '$(srcdir)/'`Mgmt.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Mgmt.Tpo $(DEPDIR)/libggk_a-Mgmt.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Mgmt.cpp' object='libggk_a-Mgmt.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Mgmt.o `test -f 'Mgmt.cpp' || echo '$(srcdir)/'`Mgmt.cpp
+
+libggk_a-Mgmt.obj: Mgmt.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Mgmt.obj -MD -MP -MF $(DEPDIR)/libggk_a-Mgmt.Tpo -c -o libggk_a-Mgmt.obj `if test -f 'Mgmt.cpp'; then $(CYGPATH_W) 'Mgmt.cpp'; else $(CYGPATH_W) '$(srcdir)/Mgmt.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Mgmt.Tpo $(DEPDIR)/libggk_a-Mgmt.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Mgmt.cpp' object='libggk_a-Mgmt.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Mgmt.obj `if test -f 'Mgmt.cpp'; then $(CYGPATH_W) 'Mgmt.cpp'; else $(CYGPATH_W) '$(srcdir)/Mgmt.cpp'; fi`
+
+libggk_a-Server.o: Server.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Server.o -MD -MP -MF $(DEPDIR)/libggk_a-Server.Tpo -c -o libggk_a-Server.o `test -f 'Server.cpp' || echo '$(srcdir)/'`Server.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Server.Tpo $(DEPDIR)/libggk_a-Server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Server.cpp' object='libggk_a-Server.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Server.o `test -f 'Server.cpp' || echo '$(srcdir)/'`Server.cpp
+
+libggk_a-Server.obj: Server.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Server.obj -MD -MP -MF $(DEPDIR)/libggk_a-Server.Tpo -c -o libggk_a-Server.obj `if test -f 'Server.cpp'; then $(CYGPATH_W) 'Server.cpp'; else $(CYGPATH_W) '$(srcdir)/Server.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Server.Tpo $(DEPDIR)/libggk_a-Server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Server.cpp' object='libggk_a-Server.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Server.obj `if test -f 'Server.cpp'; then $(CYGPATH_W) 'Server.cpp'; else $(CYGPATH_W) '$(srcdir)/Server.cpp'; fi`
+
+libggk_a-ServerUtils.o: ServerUtils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-ServerUtils.o -MD -MP -MF $(DEPDIR)/libggk_a-ServerUtils.Tpo -c -o libggk_a-ServerUtils.o `test -f 'ServerUtils.cpp' || echo '$(srcdir)/'`ServerUtils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-ServerUtils.Tpo $(DEPDIR)/libggk_a-ServerUtils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ServerUtils.cpp' object='libggk_a-ServerUtils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-ServerUtils.o `test -f 'ServerUtils.cpp' || echo '$(srcdir)/'`ServerUtils.cpp
+
+libggk_a-ServerUtils.obj: ServerUtils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-ServerUtils.obj -MD -MP -MF $(DEPDIR)/libggk_a-ServerUtils.Tpo -c -o libggk_a-ServerUtils.obj `if test -f 'ServerUtils.cpp'; then $(CYGPATH_W) 'ServerUtils.cpp'; else $(CYGPATH_W) '$(srcdir)/ServerUtils.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-ServerUtils.Tpo $(DEPDIR)/libggk_a-ServerUtils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ServerUtils.cpp' object='libggk_a-ServerUtils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-ServerUtils.obj `if test -f 'ServerUtils.cpp'; then $(CYGPATH_W) 'ServerUtils.cpp'; else $(CYGPATH_W) '$(srcdir)/ServerUtils.cpp'; fi`
+
+libggk_a-standalone.o: standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-standalone.o -MD -MP -MF $(DEPDIR)/libggk_a-standalone.Tpo -c -o libggk_a-standalone.o `test -f 'standalone.cpp' || echo '$(srcdir)/'`standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-standalone.Tpo $(DEPDIR)/libggk_a-standalone.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='standalone.cpp' object='libggk_a-standalone.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-standalone.o `test -f 'standalone.cpp' || echo '$(srcdir)/'`standalone.cpp
+
+libggk_a-standalone.obj: standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-standalone.obj -MD -MP -MF $(DEPDIR)/libggk_a-standalone.Tpo -c -o libggk_a-standalone.obj `if test -f 'standalone.cpp'; then $(CYGPATH_W) 'standalone.cpp'; else $(CYGPATH_W) '$(srcdir)/standalone.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-standalone.Tpo $(DEPDIR)/libggk_a-standalone.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='standalone.cpp' object='libggk_a-standalone.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-standalone.obj `if test -f 'standalone.cpp'; then $(CYGPATH_W) 'standalone.cpp'; else $(CYGPATH_W) '$(srcdir)/standalone.cpp'; fi`
+
+libggk_a-Utils.o: Utils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Utils.o -MD -MP -MF $(DEPDIR)/libggk_a-Utils.Tpo -c -o libggk_a-Utils.o `test -f 'Utils.cpp' || echo '$(srcdir)/'`Utils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Utils.Tpo $(DEPDIR)/libggk_a-Utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Utils.cpp' object='libggk_a-Utils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Utils.o `test -f 'Utils.cpp' || echo '$(srcdir)/'`Utils.cpp
+
+libggk_a-Utils.obj: Utils.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -MT libggk_a-Utils.obj -MD -MP -MF $(DEPDIR)/libggk_a-Utils.Tpo -c -o libggk_a-Utils.obj `if test -f 'Utils.cpp'; then $(CYGPATH_W) 'Utils.cpp'; else $(CYGPATH_W) '$(srcdir)/Utils.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libggk_a-Utils.Tpo $(DEPDIR)/libggk_a-Utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='Utils.cpp' object='libggk_a-Utils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libggk_a_CXXFLAGS) $(CXXFLAGS) -c -o libggk_a-Utils.obj `if test -f 'Utils.cpp'; then $(CYGPATH_W) 'Utils.cpp'; else $(CYGPATH_W) '$(srcdir)/Utils.cpp'; fi`
+
+standalone-standalone.o: standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(standalone_CXXFLAGS) $(CXXFLAGS) -MT standalone-standalone.o -MD -MP -MF $(DEPDIR)/standalone-standalone.Tpo -c -o standalone-standalone.o `test -f 'standalone.cpp' || echo '$(srcdir)/'`standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/standalone-standalone.Tpo $(DEPDIR)/standalone-standalone.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='standalone.cpp' object='standalone-standalone.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(standalone_CXXFLAGS) $(CXXFLAGS) -c -o standalone-standalone.o `test -f 'standalone.cpp' || echo '$(srcdir)/'`standalone.cpp
+
+standalone-standalone.obj: standalone.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(standalone_CXXFLAGS) $(CXXFLAGS) -MT standalone-standalone.obj -MD -MP -MF $(DEPDIR)/standalone-standalone.Tpo -c -o standalone-standalone.obj `if test -f 'standalone.cpp'; then $(CYGPATH_W) 'standalone.cpp'; else $(CYGPATH_W) '$(srcdir)/standalone.cpp'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/standalone-standalone.Tpo $(DEPDIR)/standalone-standalone.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='standalone.cpp' object='standalone-standalone.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(standalone_CXXFLAGS) $(CXXFLAGS) -c -o standalone-standalone.obj `if test -f 'standalone.cpp'; then $(CYGPATH_W) 'standalone.cpp'; else $(CYGPATH_W) '$(srcdir)/standalone.cpp'; fi`
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES) $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+ clean-noinstLIBRARIES clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/Mgmt.cpp b/src/Mgmt.cpp
new file mode 100644
index 0000000..d2ced63
--- /dev/null
+++ b/src/Mgmt.cpp
@@ -0,0 +1,339 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains various functions for interacting with Bluetooth Management interface, which provides adapter configuration.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// We only cover the basics here. If there are configuration features you need that aren't supported (such as configuring BR/EDR),
+// then this would be a good place for them.
+//
+// Note that this class relies on the `HciAdapter`, which is a very primitive implementation. Use with caution.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <string.h>
+
+#include "Mgmt.h"
+#include "Logger.h"
+#include "Utils.h"
+
+// Construct the Mgmt device
+//
+// Set `controllerIndex` to the zero-based index of the device as recognized by the OS. If this parameter is omitted, the index
+// of the first device (0) will be used.
+Mgmt::Mgmt(uint16_t controllerIndex)
+: controllerIndex(controllerIndex)
+{
+}
+
+// Returns the version information:
+//
+// bits 0-15 = revision
+// bits 16-23 = version
+//
+// ... or -1 on error
+int Mgmt::getVersion()
+{
+ struct SResponse : HciAdapter::ResponseEvent
+ {
+ uint8_t version;
+ uint16_t revision;
+
+ void toHost()
+ {
+ revision = Utils::endianToHost(revision);
+ }
+ } __attribute__((packed));
+
+ HciAdapter::Header request;
+ request.code = 1;
+ request.controllerId = kNonController;
+ request.dataSize = 0;
+
+ SResponse response;
+ if (!hciAdapter.sendCommand(request, response, sizeof(response)))
+ {
+ Logger::warn(SSTR << " + Failed to get version information");
+ return -1;
+ }
+
+ response.toHost();
+
+ Logger::info(SSTR << " + Version response has version=" << Utils::hex(response.version) << " and revision=" << Utils::hex(response.revision));
+ return (response.version << 16) | response.revision;
+}
+
+// Returns information about the controller (address, name, current settings, etc.)
+//
+// See the definition for MgmtControllerInformation for details.
+//
+// ... or nullptr on error
+Mgmt::ControllerInformation *Mgmt::getControllerInformation()
+{
+ struct SResponse : HciAdapter::ResponseEvent
+ {
+ ControllerInformation info;
+
+ void toHost()
+ {
+ info.toHost();
+ }
+ } __attribute__((packed));
+
+ HciAdapter::Header request;
+ request.code = 4; // Controller Information Command
+ request.controllerId = controllerIndex;
+ request.dataSize = 0;
+
+ Logger::debug("Dumping device information after configuration...");
+
+ SResponse response;
+ if (!hciAdapter.sendCommand(request, response, sizeof(response)))
+ {
+ Logger::warn(SSTR << " + Failed to get current settings");
+ return nullptr;
+ }
+
+ response.toHost();
+
+ // Copy it to our local
+ controllerInfo = response.info;
+
+ Logger::debug(" + Controller information");
+ Logger::debug(SSTR << " + Current settings : " << Utils::hex(controllerInfo.currentSettings));
+ Logger::debug(SSTR << " + Address : " << Utils::bluetoothAddressString(controllerInfo.address));
+ Logger::debug(SSTR << " + BT Version : " << controllerInfo.bluetoothVersion);
+ Logger::debug(SSTR << " + Manufacturer : " << Utils::hex(controllerInfo.manufacturer));
+ Logger::debug(SSTR << " + Supported settings : " << controllerSettingsString(controllerInfo.supportedSettings));
+ Logger::debug(SSTR << " + Current settings : " << controllerSettingsString(controllerInfo.currentSettings));
+ Logger::debug(SSTR << " + Name : " << controllerInfo.name);
+ Logger::debug(SSTR << " + Short name : " << controllerInfo.shortName);
+
+ return &controllerInfo;
+}
+
+// Set the adapter name and short name
+//
+// The inputs `name` and `shortName` may be truncated prior to setting them on the adapter. To ensure that `name` and
+// `shortName` conform to length specifications prior to calling this method, see the constants `kMaxNameLength` and
+// `kMaxShortNameLength`. In addition, the static methods `truncateName()` and `truncateShortName()` may be helpful.
+//
+// Returns true on success, otherwise false
+bool Mgmt::setName(std::string name, std::string shortName)
+{
+ // Ensure their lengths are okay
+ name = truncateName(name);
+ shortName = truncateShortName(shortName);
+
+ struct SRequest : HciAdapter::Header
+ {
+ char name[249];
+ char shortName[11];
+ } __attribute__((packed));
+
+ struct SResponse : HciAdapter::ResponseEvent
+ {
+ char name[249];
+ char shortName[11];
+ } __attribute__((packed));
+
+ SRequest request;
+ request.code = 0x000F;
+ request.controllerId = controllerIndex;
+ request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::Header);
+
+ memset(request.name, 0, sizeof(request.name));
+ snprintf(request.name, sizeof(request.name), "%s", name.c_str());
+
+ memset(request.shortName, 0, sizeof(request.shortName));
+ snprintf(request.shortName, sizeof(request.shortName), "%s", shortName.c_str());
+
+ SResponse response;
+ if (!hciAdapter.sendCommand(request, response, sizeof(response)))
+ {
+ Logger::warn(SSTR << " + Failed to set name");
+ return false;
+ }
+
+ Logger::info(SSTR << " + Name set to '" << request.name << "', short name set to '" << request.shortName << "'");
+ return true;
+}
+
+// Set a setting state to 'newState'
+//
+// Many settings are set the same way, this is just a convenience routine to handle them all
+//
+// Returns true on success, otherwise false
+bool Mgmt::setState(const char *pSettingName, uint16_t commandCode, uint16_t controllerId, uint8_t newState)
+{
+ struct SRequest : HciAdapter::Header
+ {
+ uint8_t state;
+ } __attribute__((packed));
+
+ struct SResponse : HciAdapter::ResponseEvent
+ {
+ uint32_t currentSettings;
+
+ void toHost()
+ {
+ currentSettings = Utils::endianToHost(currentSettings);
+ }
+ } __attribute__((packed));
+
+ SRequest request;
+ request.code = commandCode;
+ request.controllerId = controllerId;
+ request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::Header);
+ request.state = newState;
+
+ SResponse response;
+ if (!hciAdapter.sendCommand(request, response, sizeof(response)))
+ {
+ Logger::warn(SSTR << " + Failed to set " << pSettingName << " state to: " << static_cast<int>(newState));
+ return false;
+ }
+
+ response.toHost();
+
+ Logger::info(SSTR << " + " << pSettingName << " set to " << static_cast<int>(newState) << ": " << controllerSettingsString(response.currentSettings));
+ return true;
+}
+
+// Set the powered state to `newState` (true = powered on, false = powered off)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setPowered(bool newState)
+{
+ return setState("Powered", 0x0005, controllerIndex, newState ? 1 : 0);
+}
+
+// Set the BR/EDR state to `newState` (true = enabled, false = disabled)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setBredr(bool newState)
+{
+ return setState("BR/EDR", 0x002A, controllerIndex, newState ? 1 : 0);
+}
+
+// Set the Secure Connection state (0 = disabled, 1 = enabled, 2 = secure connections only mode)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setSecureConnections(uint8_t newState)
+{
+ return setState("SecureConnections", 0x002D, controllerIndex, newState);
+}
+
+// Set the bondable state to `newState` (true = enabled, false = disabled)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setBondable(bool newState)
+{
+ return setState("SecureConnections", 0x0009, controllerIndex, newState ? 1 : 0);
+}
+
+// Set the connectable state to `newState` (true = enabled, false = disabled)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setConnectable(bool newState)
+{
+ return setState("Connectable", 0x0007, controllerIndex, newState ? 1 : 0);
+}
+
+// Set the LE state to `newState` (true = enabled, false = disabled)
+//
+// Returns true on success, otherwise false
+bool Mgmt::setLE(bool newState)
+{
+ return setState("LowEnergy", 0x000D, controllerIndex, newState ? 1 : 0);
+}
+
+// Set the advertising state to `newState` (0 = disabled, 1 = enabled (with consideration towards the connectable setting),
+// 2 = enabled in connectable mode).
+//
+// Returns true on success, otherwise false
+bool Mgmt::setAdvertising(uint8_t newState)
+{
+ return setState("Advertising", 0x0029, controllerIndex, newState);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Utilitarian
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Transforms a "Current_Settings" value (4 octets as defined by the Bluetooth Management API specification) into a human-readable
+// string of flags and settings.
+std::string Mgmt::controllerSettingsString(uint32_t bits)
+{
+ std::string result = "";
+
+ if ((bits & EHciPowered) != 0) { result += "Powered, "; }
+ if ((bits & EHciConnectable) != 0) { result += "Connectable, "; }
+ if ((bits & EHciFastConnectable) != 0) { result += "FC, "; }
+ if ((bits & EHciDiscoverable) != 0) { result += "Discov, "; }
+ if ((bits & EHciBondable) != 0) { result += "Bondable, "; }
+ if ((bits & EHciLinkLevelSecurity) != 0) { result += "LLS, "; }
+ if ((bits & EHciSecureSimplePairing) != 0) { result += "SSP, "; }
+ if ((bits & EHciBasicRate_EnhancedDataRate) != 0) { result += "BR/EDR, "; }
+ if ((bits & EHciHighSpeed) != 0) { result += "HS, "; }
+ if ((bits & EHciLowEnergy) != 0) { result += "LE, "; }
+ if ((bits & EHciAdvertising) != 0) { result += "Adv, "; }
+ if ((bits & EHciSecureConnections) != 0) { result += "SC, "; }
+ if ((bits & EHciDebugKeys) != 0) { result += "DebugKeys, "; }
+ if ((bits & EHciPrivacy) != 0) { result += "Privacy, "; }
+ if ((bits & EHciControllerConfiguration) != 0) { result += "ControllerConfig, "; }
+ if ((bits & EHciStaticAddress) != 0) { result += "StaticAddr, "; }
+
+ if (result.length() != 0)
+ {
+ result = result.substr(0, result.length() - 2);
+ }
+
+ return result;
+}
+
+// Truncates the string `name` to the maximum allowed length for an adapter name. If `name` needs no truncation, a copy of
+// `name` is returned.
+std::string Mgmt::truncateName(const std::string &name)
+{
+ if (name.length() <= kMaxNameLength)
+ {
+ return name;
+ }
+
+ return name.substr(0, kMaxNameLength);
+}
+
+// Truncates the string `name` to the maximum allowed length for an adapter short-name. If `name` needs no truncation, a copy
+// of `name` is returned.
+std::string Mgmt::truncateShortName(const std::string &name)
+{
+ if (name.length() <= kMaxShortNameLength)
+ {
+ return name;
+ }
+
+ return name.substr(0, kMaxShortNameLength);
+}
diff --git a/src/Mgmt.h b/src/Mgmt.h
new file mode 100644
index 0000000..c58c9bc
--- /dev/null
+++ b/src/Mgmt.h
@@ -0,0 +1,213 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains various functions for interacting with Bluetooth Management interface, which provides adapter configuration.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// We only cover the basics here. If there are configuration features you need that aren't supported (such as configuring BR/EDR),
+// then this would be a good place for them.
+//
+// Note that this class relies on the `HciAdapter`, which is a very primitive implementation. Use with caution.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+#include "HciAdapter.h"
+#include "Utils.h"
+
+struct Mgmt
+{
+ //
+ // Constants
+ //
+
+ // The length of the controller's name (not including null terminator)
+ static const int kMaxNameLength = 248;
+
+ // The length of the controller's short name (not including null terminator)
+ static const int kMaxShortNameLength = 10;
+
+ //
+ // Types
+ //
+
+ // HCI Controller Settings
+ enum EHciControllerSettings
+ {
+ EHciPowered = (1<<0),
+ EHciConnectable = (1<<1),
+ EHciFastConnectable = (1<<2),
+ EHciDiscoverable = (1<<3),
+ EHciBondable = (1<<4),
+ EHciLinkLevelSecurity = (1<<5),
+ EHciSecureSimplePairing = (1<<6),
+ EHciBasicRate_EnhancedDataRate = (1<<7),
+ EHciHighSpeed = (1<<8),
+ EHciLowEnergy = (1<<9),
+ EHciAdvertising = (1<<10),
+ EHciSecureConnections = (1<<11),
+ EHciDebugKeys = (1<<12),
+ EHciPrivacy = (1<<13),
+ EHciControllerConfiguration = (1<<14),
+ EHciStaticAddress = (1<<15)
+ };
+
+ // The comments documenting these fields are very high level. There is a lot of detailed information not present, for example
+ // some values are not available at all times. This is fully documented in:
+ //
+ // https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt
+ struct ControllerInformation
+ {
+ uint8_t address[6]; // The Bluetooth address
+ uint8_t bluetoothVersion; // Bluetooth version
+ uint16_t manufacturer; // The manufacturer
+ uint32_t supportedSettings; // Bits for various supported settings (see EHciControllerSettings)
+ uint32_t currentSettings; // Bits for various currently configured settings (see EHciControllerSettings)
+ uint8_t classOfDevice[3]; // Um, yeah. That.
+ char name[249]; // Null terminated name
+ char shortName[11]; // Null terminated short name
+
+ void toHost()
+ {
+ manufacturer = Utils::endianToHost(manufacturer);
+ supportedSettings = Utils::endianToHost(supportedSettings);
+ currentSettings = Utils::endianToHost(currentSettings);
+ }
+ } __attribute__((packed));
+
+ // Construct the Mgmt device
+ //
+ // Set `controllerIndex` to the zero-based index of the device as recognized by the OS. If this parameter is omitted, the index
+ // of the first device (0) will be used.
+ Mgmt(uint16_t controllerIndex = kDefaultControllerIndex);
+
+ // Returns the version information:
+ //
+ // bits 0-15 = revision
+ // bits 16-23 = version
+ //
+ // ... or -1 on error
+ int getVersion();
+
+ // Returns information about the controller (address, name, current settings, etc.)
+ //
+ // See the definition for MgmtControllerInformation for details.
+ //
+ // ... or nullptr on error
+ ControllerInformation *getControllerInformation();
+
+ // Set the adapter name and short name
+ //
+ // The inputs `name` and `shortName` may be truncated prior to setting them on the adapter. To ensure that `name` and
+ // `shortName` conform to length specifications prior to calling this method, see the constants `kMaxNameLength` and
+ // `kMaxShortNameLength`. In addition, the static methods `truncateName()` and `truncateShortName()` may be helpful.
+ //
+ // Returns true on success, otherwise false
+ bool setName(std::string name, std::string shortName);
+
+ // Set a setting state to 'newState'
+ //
+ // Many settings are set the same way, this is just a convenience routine to handle them all
+ //
+ // Returns true on success, otherwise false
+ bool setState(const char *pSettingName, uint16_t commandCode, uint16_t controllerId, uint8_t newState);
+
+ // Set the powered state to `newState` (true = powered on, false = powered off)
+ //
+ // Returns true on success, otherwise false
+ bool setPowered(bool newState);
+
+ // Set the BR/EDR state to `newState` (true = enabled, false = disabled)
+ //
+ // Returns true on success, otherwise false
+ bool setBredr(bool newState);
+
+ // Set the Secure Connection state (0 = disabled, 1 = enabled, 2 = secure connections only mode)
+ //
+ // Returns true on success, otherwise false
+ bool setSecureConnections(uint8_t newState);
+
+ // Set the bondable state to `newState` (true = enabled, false = disabled)
+ //
+ // Returns true on success, otherwise false
+ bool setBondable(bool newState);
+
+ // Set the connectable state to `newState` (true = enabled, false = disabled)
+ //
+ // Returns true on success, otherwise false
+ bool setConnectable(bool newState);
+
+ // Set the LE state to `newState` (true = enabled, false = disabled)
+ //
+ // Returns true on success, otherwise false
+ bool setLE(bool newState);
+
+ // Set the advertising state to `newState` (0 = disabled, 1 = enabled (with consideration towards the connectable setting),
+ // 2 = enabled in connectable mode).
+ //
+ // Returns true on success, otherwise false
+ bool setAdvertising(uint8_t newState);
+
+ //
+ // Utilitarian
+ //
+
+ // Transforms a "Current_Settings" value (4 octets as defined by the Bluetooth Management API specification) into a human-
+ // readable string of flags and settings.
+ static std::string controllerSettingsString(uint32_t bits);
+
+ // Truncates the string `name` to the maximum allowed length for an adapter name. If `name` needs no truncation, a copy of
+ // `name` is returned.
+ static std::string truncateName(const std::string &name);
+
+ // Truncates the string `name` to the maximum allowed length for an adapter short-name. If `name` needs no truncation, a copy
+ // of `name` is returned.
+ static std::string truncateShortName(const std::string &name);
+
+private:
+
+ //
+ // Data members
+ //
+
+ // Our adapter - we use this to connect to the HCI socket to talk to the adapter
+ HciAdapter hciAdapter;
+
+ // Our controller information, updated each time the controller information is received
+ ControllerInformation controllerInfo;
+
+ // The default controller index (the first device)
+ uint16_t controllerIndex;
+
+ // Default controller index
+ static const uint16_t kDefaultControllerIndex = 0;
+
+ // A constant referring to a 'non-controller' (for commands that do not require a controller index)
+ static const uint16_t kNonController = 0xffff;
+};
diff --git a/src/Server.cpp b/src/Server.cpp
new file mode 100644
index 0000000..3ab15bf
--- /dev/null
+++ b/src/Server.cpp
@@ -0,0 +1,618 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is the money file. This is your server description and complete implementation. If you want to add or remove a Bluetooth
+// service, alter its behavior, add or remove characteristics or descriptors (and more), then this is your new home.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// The use of the term 'server', as it is used here, refers a collection of BlueZ services, characteristics & Descripors (plus
+// a little more.)
+//
+// Our server needs to be described in two ways. Why two? Well, think about it like this: We're communicating with Bluetooth
+// clients through BlueZ, and we're communicating with BlueZ through D-Bus. In essence, BlueZ and D-Bus are acting as tunnels, one
+// inside the other.
+//
+// Here are those two descriptions in a bit more detail:
+//
+// 1. We need to describe ourselves as a citizen on D-Bus: The objects we implement, interfaces we provide, methods we handle, etc.
+//
+// To accomplish this, we need to build an XML description (called an 'Introspection' for the curious readers) of our DBus
+// object hierarchy. The code for the XML generation starts in DBusObject.cpp (see `generateIntrospectionXML`) and carries on
+// throughout the other DBus* files (and even a few Gatt* files).
+//
+// 2. We also need to describe ourselves as a Bluetooth citizen: The services we provide, our characteristics and descriptors.
+//
+// To accomplish this, BlueZ requires us to implement a standard D-Bus interface ('org.freedesktop.DBus.ObjectManager'). This
+// interface includes a D-Bus method 'GetManagedObjects', which is just a standardized way for somebody (say... BlueZ) to ask a
+// D-Bus entity (say... this server) to enumerate itself. This is how BlueZ figures out what services we offer. BlueZ will
+// essentially forward this information to Bluetooth clients.
+//
+// Although these two descriptions work at different levels, the two need to be kept in sync. In addition, we will also need to act
+// on the messages we receive from our Bluetooth clients (through BlueZ, through D-Bus.) This means that we'll have yet another
+// synchronization issue to resolve, which is to ensure that whatever has been asked of us, makes its way to the correct code in
+// our description so we do the right thing.
+//
+// I don't know about you, but when dealing with data and the concepts "multiple" and "kept in sync" come into play, my spidey
+// sense starts to tingle. The best way to ensure sychronization is to remove the need to keep things sychronized.
+//
+// The large code block below defines a description that includes all the information about our server in a way that can be easily
+// used to generate both: (1) the D-Bus object hierarchy and (2) the BlueZ services that occupy that hierarchy. In addition, we'll
+// take that a step further by including the implementation right inside the description. Everything in one place.
+//
+// Well, almost one place (hint: Take a quick look at Globals.h - that's where your server name is.)
+//
+// >>
+// >>> MANAGING SERVER DATA
+// >>
+//
+// The purpose of the server is to serve data. Your application is responsible for providing that data to the server via two data
+// accessors (a getter and a setter) that implemented in the form of delegates that are passed into the `ggkStart()` method.
+//
+// While the server is running, if data is updated via a write operation from the client the setter delegate will be called. If your
+// application also generates or updates data periodically, it can push those updates to the server via call to
+// `ggkNofifyUpdatedCharacteristic()` or `ggkNofifyUpdatedDescriptor()`.
+//
+// >>
+// >>> UNDERSTANDING THE UNDERLYING FRAMEWORKS
+// >>
+//
+// The server description below attempts to provide a GATT-based interface in terms of GATT services, characteristics and
+// descriptors. Consider the following sample:
+//
+// .gattServiceBegin("text", "00000001-1E3C-FAD4-74E2-97A033F1BFAA")
+// .gattCharacteristicBegin("string", "00000002-1E3C-FAD4-74E2-97A033F1BFAA", {"read", "write", "notify"})
+//
+// .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+// {
+// // Abbreviated for simplicity
+// self.methodReturnValue(pInvocation, myTextString, true);
+// })
+//
+// .onWriteValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+// {
+// // Abbreviated for simplicity
+// myTextString = ...
+// })
+//
+// .gattDescriptorBegin("description", "2901", {"read"})
+// .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
+// {
+// self.methodReturnValue(pInvocation, "Returns a test string", true);
+// })
+//
+// .gattDescriptorEnd()
+// .gattCharacteristicEnd()
+// .gattServiceEnd()
+//
+// The first thing you may notice abpout the sample is that all of the lines begin with a dot. This is because we're chaining
+// methods together. Each method returns the appropriate type to provide context. For example, The `gattServiceBegin` method returns
+// a reference to a `GattService` object which provides the proper context to create a characteristic within that service.
+// Similarly, the `gattCharacteristicBegin` method returns a reference to a `GattCharacteristic` object which provides the proper
+// context for responding to requests to read the characterisic value or add descriptors to the characteristic.
+//
+// For every `*Begin` method, there is a corresponding `*End` method, which returns us to the previous context. Indentation helps us
+// keep track of where we are.
+//
+// Also note the use of the lambda macros, `CHARACTERISTIC_METHOD_CALLBACK_LAMBDA` and `DESCRIPTOR_METHOD_CALLBACK_LAMBDA`. These
+// macros simplify the process of including our implementation directly in the description.
+//
+// The first parameter to each of the `*Begin` methods is a path node name. As we build our hierarchy, we give each node a name,
+// which gets appended to it's parent's node (which in turns gets appended to its parent's node, etc.) If our root path was
+// "/com/gobbledegook", then our service would have the path "/com/gobbledegook/text" and the characteristic would have the path
+// "/com/gobbledegook/text/string", and the descriptor would have the path "/com/gobbledegook/text/string/description". These paths
+// are important as they act like an addressing mechanism similar to paths on a filesystem or in a URL.
+//
+// The second parameter to each of the `*Begin` methods is a UUID as defined by the Bluetooth standard. These UUIDs effectively
+// refer to an interface. You will see two different kinds of UUIDs: a short UUID ("2901") and a long UUID
+// ("00000002-1E3C-FAD4-74E2-97A033F1BFAA").
+//
+// For more information on UUDSs, see GattUuid.cpp.
+//
+// In the example above, our non-standard UUIDs ("00000001-1E3C-FAD4-74E2-97A033F1BFAA") are something we generate ourselves. In the
+// case above, we have created a custom service that simply stores a mutable text string. When the client enumerates our services
+// they'll see this UUID and, assuming we've documented our interface behind this UUID for client authors, they can use our service
+// to read and write a text string maintained on our server.
+//
+// The third parameter (which only applies to dharacteristics and descriptors) are a set of flags. You will find the current set of
+// flags for characteristics and descriptors in the "BlueZ D-Bus GATT API description" at:
+//
+// https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt
+//
+// In addition to these structural methods, there are a small handful of helper methods for performing common operations. These
+// helper methods are available within a method (such as `onReadValue`) through the use of a `self` reference. The `self` reference
+// refers to the object at which the method is invoked (either a `GattCharacteristic` object or a `GattDescriptor` object.)
+//
+// methodReturnValue and methodReturnVariant
+// These methods provide a means for returning values from Characteristics and Descriptors. The `-Value` form accept a set
+// of common types (int, string, etc.) If you need to provide a custom return type, you can do so by building your own
+// GVariant (which is a GLib construct) and using the `-Variant` form of the method.
+//
+// sendChangeNotificationValue and sendChangeNotificationVariant
+// These methods provide a means for notifying changes for Characteristics. The `-Value` form accept a set of common types
+// (int, string, etc.) If you need to notify a custom return type, you can do so by building your own GVariant (which is a
+// GLib construct) and using the `-Variant` form of the method.
+//
+// For information about GVariants (what they are and how to work with them), see the GLib documentation at:
+//
+// https://www.freedesktop.org/software/gstreamer-sdk/data/docs/latest/glib/glib-GVariantType.html
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include "Server.h"
+#include "ServerUtils.h"
+#include "Utils.h"
+#include "Globals.h"
+#include "DBusObject.h"
+#include "DBusInterface.h"
+#include "GattProperty.h"
+#include "GattService.h"
+#include "GattUuid.h"
+#include "GattCharacteristic.h"
+#include "GattDescriptor.h"
+
+// There's a good chance there will be a bunch of unused parameters from the lambda macros
+#if defined(__GNUC__) && defined(__clang__)
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunused-parameter"
+#endif
+#if defined(__GNUC__) && !defined(__clang__)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Globals
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Our one and only server. It's global.
+std::shared_ptr<Server> TheServer = nullptr;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Object implementation
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Create (and completely describe) a server
+//
+// For details, head up to the top of this file.
+Server::Server(GGKServerDataGetter getter, GGKServerDataSetter setter)
+{
+ // Register getter & setter for server data
+ dataGetter = getter;
+ dataSetter = setter;
+
+ // Adapter configuration flags - set these flags based on how you want the adapter configured
+ enableBREDR = false;
+ enableSecureConnection = false;
+ enableConnectable = true;
+ enableAdvertising = true;
+ enableBondable = false;
+
+ //
+ // Define the server
+ //
+
+ // Create the root D-Bus object and push it into the list
+ objects.push_back(DBusObject(DBusObjectPath() + "com" + kServerName));
+
+ // We're going to build off of this object, so we need to get a reference to the instance of the object as it resides in the
+ // list (and not the object that would be added to the list.)
+ objects.back()
+
+ // Service: Device Information (0x180A)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml
+ .gattServiceBegin("device", "180A")
+
+ // Characteristic: Manufacturer Name String (0x2A29)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml
+ .gattCharacteristicBegin("mfgr_name", "2A29", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ self.methodReturnValue(pInvocation, "Acme Inc.", true);
+ })
+
+ .gattCharacteristicEnd()
+
+ // Characteristic: Model Number String (0x2A24)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.model_number_string.xml
+ .gattCharacteristicBegin("model_num", "2A24", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ self.methodReturnValue(pInvocation, "Marvin-PA", true);
+ })
+
+ .gattCharacteristicEnd()
+
+ .gattServiceEnd()
+
+ // Battery Service (0x180F)
+ //
+ // This is a fake battery service that conforms to org.bluetooth.service.battery_service. For details, see:
+ //
+ // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xml
+ //
+ // We also handle updates to the battery level from inside the server (see onUpdatedValue). There is an external method
+ // (see main.cpp) that updates our battery level and posts an update using ggkPushUpdateQueue. Those updates are used
+ // to notify us that our value has changed, which translates into a call to `onUpdatedValue` from the idleFunc (see
+ // Init.cpp).
+ .gattServiceBegin("battery", "180F")
+
+ // Characteristic: Battery Level (0x2A19)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml
+ .gattCharacteristicBegin("level", "2A19", {"read", "notify"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ uint8_t batteryLevel = self.getDataValue<uint8_t>("battery/level", 0);
+ self.methodReturnValue(pInvocation, batteryLevel, true);
+ })
+
+ // Handle updates to the battery level
+ //
+ // Here we use the onUpdatedValue to set a callback that isn't exposed to BlueZ, but rather allows us to manage
+ // updates to our value. These updates may have come from our own server or some other source.
+ //
+ // We can handle updates in any way we wish, but the most common use is to send a change notification.
+ .onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
+ {
+ uint8_t batteryLevel = self.getDataValue<uint8_t>("battery/level", 0);
+ self.sendChangeNotificationValue(pConnection, batteryLevel);
+ return true;
+ })
+
+ .gattCharacteristicEnd()
+ .gattServiceEnd()
+
+ // Current Time Service (0x1805)
+ //
+ // This is a time service that conforms to org.bluetooth.service.current_time. For details, see:
+ //
+ // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.current_time.xml
+ //
+ // Like the battery service, this also makes use of events. This one updates the time every tick.
+ //
+ // This showcases the use of events (see the call to .onEvent() below) for periodic actions. In this case, the action
+ // taken is to update time every tick. This probably isn't a good idea for a production service, but it has been quite
+ // useful for testing to ensure we're connected and updating.
+ .gattServiceBegin("time", "1805")
+
+ // Characteristic: Current Time (0x2A2B)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.current_time.xml
+ .gattCharacteristicBegin("current", "2A2B", {"read", "notify"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ self.methodReturnVariant(pInvocation, ServerUtils::gvariantCurrentTime(), true);
+ })
+
+ // Update the time every tick of the periodic timer
+ //
+ // We'll send an change notification to any subscribed clients with the latest value
+ .onEvent(1, nullptr, CHARACTERISTIC_EVENT_CALLBACK_LAMBDA
+ {
+ self.sendChangeNotificationVariant(pConnection, ServerUtils::gvariantCurrentTime());
+ })
+
+ .gattCharacteristicEnd()
+
+ // Characteristic: Local Time Information (0x2A0F)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.local_time_information.xml
+ .gattCharacteristicBegin("local", "2A0F", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ self.methodReturnVariant(pInvocation, ServerUtils::gvariantLocalTime(), true);
+ })
+
+ .gattCharacteristicEnd()
+ .gattServiceEnd()
+
+ // Custom read/write text string service (00000001-1E3C-FAD4-74E2-97A033F1BFAA)
+ //
+ // This service will return a text string value (default: 'Hello, world!'). If the text value is updated, it will notify
+ // that the value has been updated and provide the new text from that point forward.
+ .gattServiceBegin("text", "00000001-1E3C-FAD4-74E2-97A033F1BFAA")
+
+ // Characteristic: String value (custom: 00000002-1E3C-FAD4-74E2-97A033F1BFAA)
+ .gattCharacteristicBegin("string", "00000002-1E3C-FAD4-74E2-97A033F1BFAA", {"read", "write", "notify"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ const char *pTextString = self.getDataPointer<const char *>("text/string", "");
+ self.methodReturnValue(pInvocation, pTextString, true);
+ })
+
+ // Standard characteristic "WriteValue" method call
+ .onWriteValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ // Update the text string value
+ GVariant *pAyBuffer = g_variant_get_child_value(pParameters, 0);
+ self.setDataPointer("text/string", Utils::stringFromGVariantByteArray(pAyBuffer).c_str());
+
+ // Since all of these methods (onReadValue, onWriteValue, onUpdateValue) are all part of the same
+ // Characteristic interface (which just so happens to be the same interface passed into our self
+ // parameter) we can that parameter to call our own onUpdatedValue method
+ self.callOnUpdatedValue(pConnection, pUserData);
+ })
+
+ // Here we use the onUpdatedValue to set a callback that isn't exposed to BlueZ, but rather allows us to manage
+ // updates to our value. These updates may have come from our own server or some other source.
+ //
+ // We can handle updates in any way we wish, but the most common use is to send a change notification.
+ .onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
+ {
+ const char *pTextString = self.getDataPointer<const char *>("text/string", "");
+ self.sendChangeNotificationValue(pConnection, pTextString);
+ return true;
+ })
+
+ // GATT Descriptor: Characteristic User Description (0x2901)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml
+ .gattDescriptorBegin("description", "2901", {"read"})
+
+ // Standard descriptor "ReadValue" method call
+ .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
+ {
+ const char *pDescription = "A mutable text string used for testing. Read and write to me, it tickles!";
+ self.methodReturnValue(pInvocation, pDescription, true);
+ })
+
+ .gattDescriptorEnd()
+
+ .gattCharacteristicEnd()
+ .gattServiceEnd()
+
+ // Custom ASCII time string service
+ //
+ // This service will simply return the result of asctime() of the current local time. It's a nice test service to provide
+ // a new value each time it is read.
+
+ // Service: ASCII Time (custom: 00000001-1E3D-FAD4-74E2-97A033F1BFEE)
+ .gattServiceBegin("ascii_time", "00000001-1E3D-FAD4-74E2-97A033F1BFEE")
+
+ // Characteristic: ASCII Time String (custom: 00000002-1E3D-FAD4-74E2-97A033F1BFEE)
+ .gattCharacteristicBegin("string", "00000002-1E3D-FAD4-74E2-97A033F1BFEE", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ // Get our local time string using asctime()
+ time_t timeVal = time(nullptr);
+ struct tm *pTimeStruct = localtime(&timeVal);
+ std::string timeString = Utils::trim(asctime(pTimeStruct));
+
+ self.methodReturnValue(pInvocation, timeString, true);
+ })
+
+ // GATT Descriptor: Characteristic User Description (0x2901)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml
+ .gattDescriptorBegin("description", "2901", {"read"})
+
+ // Standard descriptor "ReadValue" method call
+ .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
+ {
+ const char *pDescription = "Returns the local time (as reported by POSIX asctime()) each time it is read";
+ self.methodReturnValue(pInvocation, pDescription, true);
+ })
+
+ .gattDescriptorEnd()
+
+ .gattCharacteristicEnd()
+ .gattServiceEnd()
+
+ // Custom CPU information service (custom: 0000B001-1E3D-FAD4-74E2-97A033F1BFEE)
+ //
+ // This is a cheezy little service that reads the CPU info from /proc/cpuinfo and returns the count and model of the
+ // CPU. It may not work on all platforms, but it does provide yet another example of how to do things.
+
+ // Service: CPU Information (custom: 0000B001-1E3D-FAD4-74E2-97A033F1BFEE)
+ .gattServiceBegin("cpu", "0000B001-1E3D-FAD4-74E2-97A033F1BFEE")
+
+ // Characteristic: CPU Count (custom: 0000B002-1E3D-FAD4-74E2-97A033F1BFEE)
+ .gattCharacteristicBegin("count", "0000B002-1E3D-FAD4-74E2-97A033F1BFEE", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ int16_t cpuCount = 0;
+ ServerUtils::getCpuInfo(cpuCount);
+ self.methodReturnValue(pInvocation, cpuCount, true);
+ })
+
+ // GATT Descriptor: Characteristic User Description (0x2901)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml
+ .gattDescriptorBegin("description", "2901", {"read"})
+
+ // Standard descriptor "ReadValue" method call
+ .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
+ {
+ const char *pDescription = "This might represent the number of CPUs in the system";
+ self.methodReturnValue(pInvocation, pDescription, true);
+ })
+
+ .gattDescriptorEnd()
+
+ .gattCharacteristicEnd()
+
+ // Characteristic: CPU Model (custom: 0000B003-1E3D-FAD4-74E2-97A033F1BFEE)
+ .gattCharacteristicBegin("model", "0000B003-1E3D-FAD4-74E2-97A033F1BFEE", {"read"})
+
+ // Standard characteristic "ReadValue" method call
+ .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
+ {
+ int16_t cpuCount = 0;
+ self.methodReturnValue(pInvocation, ServerUtils::getCpuInfo(cpuCount), true);
+ })
+
+ // GATT Descriptor: Characteristic User Description (0x2901)
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml
+ .gattDescriptorBegin("description", "2901", {"read"})
+
+ // Standard descriptor "ReadValue" method call
+ .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
+ {
+ const char *pDescription = "Possibly the model of the CPU in the system";
+ self.methodReturnValue(pInvocation, pDescription, true);
+ })
+
+ .gattDescriptorEnd()
+
+ .gattCharacteristicEnd()
+ .gattServiceEnd(); // << -- NOTE THE SEMICOLON
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // ____ _____ ___ _____
+ // / ___|_ _/ _ \| _ |
+ // \___ \ | || | | | |_) |
+ // ___) || || |_| | __/
+ // |____/ |_| \___/|_|
+ //
+ // You probably shouldn't mess with stuff beyond this point. It is required to meet BlueZ's requirements for a GATT Service.
+ //
+ // >>
+ // >> WHAT IT IS
+ // >>
+ //
+ // From the BlueZ D-Bus GATT API description (https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt):
+ //
+ // "To make service registration simple, BlueZ requires that all objects that belong to a GATT service be grouped under a
+ // D-Bus Object Manager that solely manages the objects of that service. Hence, the standard DBus.ObjectManager interface
+ // must be available on the root service path."
+ //
+ // The code below does exactly that. Notice that we're doing much of the same work that our Server description does except that
+ // instead of defining our own interfaces, we're following a pre-defined standard.
+ //
+ // The object types and method names used in the code below may look unfamiliar compared to what you're used to seeing in the
+ // Server desecription. That's because the server description uses higher level types that define a more GATT-oriented framework
+ // to build your GATT services. That higher level functionality was built using a set of lower-level D-Bus-oriented framework,
+ // which is used in the code below.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ // Create the root object and push it into the list. We're going to build off of this object, so we need to get a reference
+ // to the instance of the object as it resides in the list (and not the object that would be added to the list.)
+ //
+ // This is a non-published object (as specified by the 'false' parameter in the DBusObject constructor.) This way, we can
+ // include this within our server hieararchy (i.e., within the `objects` list) but it won't be exposed by BlueZ as a Bluetooth
+ // service to clietns.
+ objects.push_back(DBusObject(DBusObjectPath(), false));
+
+ // Get a reference to the new object as it resides in the list
+ DBusObject &objectManager = objects.back();
+
+ // Create an interface of the standard type 'org.freedesktop.DBus.ObjectManager'
+ //
+ // See: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
+ auto omInterface = std::make_shared<DBusInterface>(objectManager, "org.freedesktop.DBus.ObjectManager");
+
+ // Add the interface to the object manager
+ objectManager.addInterface(omInterface);
+
+ // Finally, we setup the interface. We do this by adding the `GetManagedObjects` method as specified by D-Bus for the
+ // 'org.freedesktop.DBus.ObjectManager' interface.
+ const char *pInArgs[] = { nullptr };
+ const char *pOutArgs = "a{oa{sa{sv}}}";
+ omInterface->addMethod("GetManagedObjects", pInArgs, pOutArgs, INTERFACE_METHOD_CALLBACK_LAMBDA
+ {
+ ServerUtils::getManagedObjects(pInvocation);
+ });
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Utilitarian
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Find a D-Bus interface within the given D-Bus object
+//
+// If the interface was found, it is returned, otherwise nullptr is returned
+std::shared_ptr<const DBusInterface> Server::findInterface(const DBusObjectPath &objectPath, const std::string &interfaceName) const
+{
+ for (const DBusObject &object : objects)
+ {
+ std::shared_ptr<const DBusInterface> pInterface = object.findInterface(objectPath, interfaceName);
+ if (pInterface != nullptr)
+ {
+ return pInterface;
+ }
+ }
+
+ return nullptr;
+}
+
+// Find and call a D-Bus method within the given D-Bus object on the given D-Bus interface
+//
+// If the method was called, this method returns true, otherwise false. There is no result from the method call itself.
+bool Server::callMethod(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const
+{
+ for (const DBusObject &object : objects)
+ {
+ if (object.callMethod(objectPath, interfaceName, methodName, pConnection, pParameters, pInvocation, pUserData))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Find a GATT Property within the given D-Bus object on the given D-Bus interface
+//
+// If the property was found, it is returned, otherwise nullptr is returned
+const GattProperty *Server::findProperty(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &propertyName) const
+{
+ std::shared_ptr<const DBusInterface> pInterface = findInterface(objectPath, interfaceName);
+
+ // Try each of the GattInterface types that support properties?
+ if (std::shared_ptr<const GattInterface> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattInterface))
+ {
+ return pGattInterface->findProperty(propertyName);
+ }
+ else if (std::shared_ptr<const GattService> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattService))
+ {
+ return pGattInterface->findProperty(propertyName);
+ }
+ else if (std::shared_ptr<const GattCharacteristic> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattCharacteristic))
+ {
+ return pGattInterface->findProperty(propertyName);
+ }
+
+ return nullptr;
+}
diff --git a/src/Server.h b/src/Server.h
new file mode 100644
index 0000000..8880f22
--- /dev/null
+++ b/src/Server.h
@@ -0,0 +1,146 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is the top-level interface for the server. There is only one of these stored in the global `TheServer`. Use this object
+// to configure your server's settings (there are surprisingly few of them.) It also contains the full server description and
+// implementation.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See the discussion at the top of Server.cpp
+//
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <string>
+#include <list>
+#include <memory>
+
+#include "DBusInterface.h"
+#include "DBusObject.h"
+
+//
+// Forward declarations
+//
+
+struct GattProperty;
+
+//
+// Implementation
+//
+
+struct Server
+{
+ //
+ // Types
+ //
+
+ // Our server is a collection of D-Bus objects
+ typedef std::list<DBusObject> Objects;
+
+ //
+ // Accessors
+ //
+
+ // Returns the set of objects that each represent the root of an object tree describing a group of services we are providing
+ const Objects &getObjects() const { return objects; }
+
+ // Returns the requested setting for BR/EDR (true = enabled, false = disabled)
+ bool getEnableBREDR() const { return enableBREDR; }
+
+ // Returns the requested setting for secure connections (true = enabled, false = disabled)
+ bool getEnableSecureConnection() const { return enableSecureConnection; }
+
+ // Returns the requested setting the connectable state (true = enabled, false = disabled)
+ bool getEnableConnectable() const { return enableConnectable; }
+
+ // Returns the requested setting the LE advertising state (true = enabled, false = disabled)
+ bool getEnableAdvertising() const { return enableAdvertising; }
+
+ // Returns the requested setting the bondable state (true = enabled, false = disabled)
+ bool getEnableBondable() const { return enableBondable; }
+
+ // Returns our registered data getter
+ GGKServerDataGetter getDataGetter() const { return dataGetter; }
+
+ // Returns our registered data setter
+ GGKServerDataSetter getDataSetter() const { return dataSetter; }
+
+ //
+ // Initialization
+ //
+
+ // Our constructor builds our entire server description
+ Server(GGKServerDataGetter getter, GGKServerDataSetter setter);
+
+ //
+ // Utilitarian
+ //
+
+ // Find and call a D-Bus method within the given D-Bus object on the given D-Bus interface
+ //
+ // If the method was called, this method returns true, otherwise false. There is no result from the method call itself.
+ std::shared_ptr<const DBusInterface> findInterface(const DBusObjectPath &objectPath, const std::string &interfaceName) const;
+
+ // Find a D-Bus method within the given D-Bus object on the given D-Bus interface
+ //
+ // If the method was found, it is returned, otherwise nullptr is returned
+ bool callMethod(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const;
+
+ // Find a GATT Property within the given D-Bus object on the given D-Bus interface
+ //
+ // If the property was found, it is returned, otherwise nullptr is returned
+ const GattProperty *findProperty(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &propertyName) const;
+
+private:
+
+ // Our server's objects
+ Objects objects;
+
+ // BR/EDR requested state
+ bool enableBREDR;
+
+ // Secure connection requested state
+ bool enableSecureConnection;
+
+ // Connectable requested state
+ bool enableConnectable;
+
+ // LE advertising requested state
+ bool enableAdvertising;
+
+ // Bondable requested state
+ bool enableBondable;
+
+ // The getter callback that is responsible for returning current server data that is shared over Bluetooth
+ GGKServerDataGetter dataGetter;
+
+ // The setter callback that is responsible for storing current server data that is shared over Bluetooth
+ GGKServerDataSetter dataSetter;
+};
+
+// Our one and only server. It's a global.
+extern std::shared_ptr<Server> TheServer;
diff --git a/src/ServerUtils.cpp b/src/ServerUtils.cpp
new file mode 100644
index 0000000..bee20cf
--- /dev/null
+++ b/src/ServerUtils.cpp
@@ -0,0 +1,314 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains helper functions for our server's implementation. That is, methods that are used by the server itself (when
+// responding to D-Bus or BlueZ requests.)
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Generally speaking, these are blocks of code that are too big to comfortably fit as lambdas within the `Server::Server()`
+// constructor are here.
+//
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <glib.h>
+#include <string>
+#include <fstream>
+#include <regex>
+
+#include "ServerUtils.h"
+#include "DBusObject.h"
+#include "DBusInterface.h"
+#include "GattProperty.h"
+#include "GattService.h"
+#include "GattCharacteristic.h"
+#include "GattDescriptor.h"
+#include "Server.h"
+#include "Logger.h"
+#include "Utils.h"
+
+// Adds an object to the tree of managed objects as returned from the `GetManagedObjects` method call from the D-Bus interface
+// `org.freedesktop.DBus.ObjectManager`.
+//
+// According to the spec:
+//
+// The return value of this method is a dict whose keys are object paths.
+// All returned object paths are children of the object path implementing this interface,
+// i.e. their object paths start with the ObjectManager's object path plus '/'.
+//
+// Each value is a dict whose keys are interfaces names. Each value in this inner dict
+// is the same dict that would be returned by the org.freedesktop.DBus.Properties.GetAll()
+// method for that combination of object path and interface. If an interface has no properties,
+// the empty dict is returned.
+//
+// (a{oa{sa{sv}}})
+static void addManagedObjectsNode(const DBusObject &object, const DBusObjectPath &basePath, GVariantBuilder *pObjectArray)
+{
+ if (!object.isPublished())
+ {
+ return;
+ }
+
+ if (!object.getInterfaces().empty())
+ {
+ DBusObjectPath path = basePath + object.getPathNode();
+ Logger::debug(SSTR << " Object: " << path);
+
+ GVariantBuilder *pInterfaceArray = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ for (std::shared_ptr<const DBusInterface> pInterface : object.getInterfaces())
+ {
+ Logger::debug(SSTR << " + Interface (type: " << pInterface->getInterfaceType() << ")");
+
+ if (std::shared_ptr<const GattService> pService = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattService))
+ {
+ if (!pService->getProperties().empty())
+ {
+ Logger::debug(SSTR << " GATT Service interface: " << pService->getName());
+
+ GVariantBuilder *pPropertyArray = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ for (const GattProperty &property : pService->getProperties())
+ {
+ Logger::debug(SSTR << " Property " << property.getName());
+ g_variant_builder_add
+ (
+ pPropertyArray,
+ "{sv}",
+ property.getName().c_str(),
+ property.getValue()
+ );
+ }
+
+ g_variant_builder_add
+ (
+ pInterfaceArray,
+ "{sa{sv}}",
+ pService->getName().c_str(),
+ pPropertyArray
+ );
+ }
+ }
+ else if (std::shared_ptr<const GattCharacteristic> pCharacteristic = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattCharacteristic))
+ {
+ if (!pCharacteristic->getProperties().empty())
+ {
+ Logger::debug(SSTR << " GATT Characteristic interface: " << pCharacteristic->getName());
+
+ GVariantBuilder *pPropertyArray = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ for (const GattProperty &property : pCharacteristic->getProperties())
+ {
+ Logger::debug(SSTR << " Property " << property.getName());
+ g_variant_builder_add
+ (
+ pPropertyArray,
+ "{sv}",
+ property.getName().c_str(),
+ property.getValue()
+ );
+ }
+
+ g_variant_builder_add
+ (
+ pInterfaceArray,
+ "{sa{sv}}",
+ pCharacteristic->getName().c_str(),
+ pPropertyArray
+ );
+ }
+ }
+ else if (std::shared_ptr<const GattDescriptor> pDescriptor = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattDescriptor))
+ {
+ if (!pDescriptor->getProperties().empty())
+ {
+ Logger::debug(SSTR << " GATT Descriptor interface: " << pDescriptor->getName());
+
+ GVariantBuilder *pPropertyArray = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ for (const GattProperty &property : pDescriptor->getProperties())
+ {
+ Logger::debug(SSTR << " Property " << property.getName());
+ g_variant_builder_add
+ (
+ pPropertyArray,
+ "{sv}",
+ property.getName().c_str(),
+ property.getValue()
+ );
+ }
+
+ g_variant_builder_add
+ (
+ pInterfaceArray,
+ "{sa{sv}}",
+ pDescriptor->getName().c_str(),
+ pPropertyArray
+ );
+ }
+ }
+ else
+ {
+ Logger::error(SSTR << " Unknown interface type");
+ return;
+ }
+ }
+
+ g_variant_builder_add
+ (
+ pObjectArray,
+ "{oa{sa{sv}}}",
+ path.c_str(),
+ pInterfaceArray
+ );
+ }
+
+ for (const DBusObject &child : object.getChildren())
+ {
+ addManagedObjectsNode(child, basePath + object.getPathNode(), pObjectArray);
+ }
+}
+
+// Builds the response to the method call `GetManagedObjects` from the D-Bus interface `org.freedesktop.DBus.ObjectManager`
+void ServerUtils::getManagedObjects(GDBusMethodInvocation *pInvocation)
+{
+ Logger::debug(SSTR << "Reporting managed objects");
+
+ GVariantBuilder *pObjectArray = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ for (const DBusObject &object : TheServer->getObjects())
+ {
+ addManagedObjectsNode(object, DBusObjectPath(""), pObjectArray);
+ }
+
+ GVariant *pParams = g_variant_new("(a{oa{sa{sv}}})", pObjectArray);
+ g_dbus_method_invocation_return_value(pInvocation, pParams);
+}
+
+// WARNING: Hacky code - don't count on this working properly on all systems
+//
+// This routine will attempt to parse /proc/cpuinfo to return the CPU count/model. Results are cached on the first call, with
+// cached results returned on successive calls.
+//
+// If this routine fails, it will respond with something reasonable, if not _entirely_ accurate.
+std::string ServerUtils::getCpuInfo(int16_t &cpuCount)
+{
+ static int16_t cachedCount = -1;
+ static std::string cachedModel;
+ static const std::string kCpuInfoFile = "/proc/cpuinfo";
+
+ // If we haven't cached a result, let's go get one
+ if (cachedCount == -1)
+ {
+ // Reset our counter
+ cachedCount = 0;
+
+ // Open the cpuinfo file
+ std::ifstream cpuInfo(kCpuInfoFile);
+ if (cpuInfo.is_open())
+ {
+ std::string line;
+ while(getline(cpuInfo, line))
+ {
+ // Count the processors
+ std::regex processorRegex("^processor.*: [0-9].*$", std::regex::ECMAScript);
+ std::smatch processorMatch;
+
+ if (std::regex_search(line, processorMatch, processorRegex))
+ {
+ cachedCount++;
+ }
+
+ // Extract the first model name we find
+ if (cachedModel.empty())
+ {
+ std::regex modelRegex("^model name.*: (.*)$", std::regex::ECMAScript);
+ std::smatch modelMatch;
+ if (std::regex_search(line, modelMatch, modelRegex))
+ {
+ if (modelMatch.size() == 2)
+ {
+ cachedModel = Utils::trim(modelMatch[1].str());
+ }
+ }
+ }
+ }
+
+ cpuInfo.close();
+ }
+
+ // If we never found one, provide a reasonable default
+ if (cachedModel.empty()) { cachedModel = "Gooberfest Cyclemaster 3000 (v8)"; }
+ if (cachedCount == 0) { cachedCount = 42; }
+ }
+
+ cpuCount = cachedCount;
+ return cachedModel;
+}
+
+// Build a variant that meets the standard for the Current Time (0x2A2B) Bluetooth Characteristic standard
+//
+// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.current_time.xml
+GVariant *ServerUtils::gvariantCurrentTime()
+{
+ time_t timeValue = time(nullptr);
+ struct tm *pTimeStruct = localtime(&timeValue);
+ guint16 year = pTimeStruct->tm_year + 1900;
+ guint8 wday = guint8(pTimeStruct->tm_wday == 0 ? 7 : pTimeStruct->tm_wday);
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ g_variant_builder_add(&builder, "y", (year >> 0) & 0xff);
+ g_variant_builder_add(&builder, "y", (year >> 8) & 0xff);
+ g_variant_builder_add(&builder, "y", guint8(pTimeStruct->tm_mon+1)); // month (1-12)
+ g_variant_builder_add(&builder, "y", guint8(pTimeStruct->tm_mday)); // day (1-31)
+ g_variant_builder_add(&builder, "y", guint8(pTimeStruct->tm_hour)); // hour (0-23)
+ g_variant_builder_add(&builder, "y", guint8(pTimeStruct->tm_min)); // minute (0-59)
+ g_variant_builder_add(&builder, "y", guint8(pTimeStruct->tm_sec)); // seconds (0-59)
+ g_variant_builder_add(&builder, "y", wday); // weekday (1-7 where 1=Monday)
+ g_variant_builder_add(&builder, "y", guint8(0)); // Fractions (1/256th of second)
+ g_variant_builder_add(&builder, "y", guint8(0)); // Adjust reason bitmask (0 for testing)
+
+ GVariant * const pVariant = g_variant_builder_end(&builder);
+ return pVariant;
+}
+
+// Build a variant that meets the standard for the Local Time Information (0x2A0F) Bluetooth Characteristic standard
+//
+// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.local_time_information.xml
+GVariant *ServerUtils::gvariantLocalTime()
+{
+ tzset();
+ time_t timeValue = time(nullptr);
+ struct tm *pTimeStruct = localtime(&timeValue);
+
+ gint8 utcOffset = -gint8(timezone / 60 / 15); // UTC time (uses 15-minute increments, 0 = UTC time)
+ guint8 dstOffset = pTimeStruct->tm_isdst == 0 ? 0 : 4; // 0 = no DST offset, 4 = +1 hour for DST
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ g_variant_builder_add(&builder, "y", utcOffset);
+ g_variant_builder_add(&builder, "y", dstOffset);
+
+ GVariant * const pVariant = g_variant_builder_end(&builder);
+ return pVariant;
+}
diff --git a/src/ServerUtils.h b/src/ServerUtils.h
new file mode 100644
index 0000000..3ab9511
--- /dev/null
+++ b/src/ServerUtils.h
@@ -0,0 +1,62 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains helper function declarations for our server's implementation. That is, methods that are used by the server
+// itself (when responding to D-Bus or BlueZ requests.)
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// See ServerUtils.cpp for implementations
+//
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+
+struct ServerUtils
+{
+ // Builds the response to the method call `GetManagedObjects` from the D-Bus interface `org.freedesktop.DBus.ObjectManager`
+ static void getManagedObjects(GDBusMethodInvocation *pInvocation);
+
+ // WARNING: Hacky code - don't count on this working properly on all systems
+ //
+ // This routine will attempt to parse /proc/cpuinfo to return the CPU count/model. Results are cached on the first call, with
+ // cached results returned on successive calls.
+ //
+ // If this routine fails, it will respond with something reasonable, if not _entirely_ accurate.
+ static std::string getCpuInfo(int16_t &cpuCount);
+
+ // Build a variant that meets the standard for the Current Time (0x2A2B) Bluetooth Characteristic standard
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.current_time.xml
+ static GVariant *gvariantCurrentTime();
+
+ // Build a variant that meets the standard for the Local Time Information (0x2A0F) Bluetooth Characteristic standard
+ //
+ // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.local_time_information.xml
+ static GVariant *gvariantLocalTime();
+};
diff --git a/src/TickEvent.h b/src/TickEvent.h
new file mode 100644
index 0000000..c519a0b
--- /dev/null
+++ b/src/TickEvent.h
@@ -0,0 +1,149 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// A TIckEvent is an event that is triggered on a regular timer interval.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Tick events are used within the Server's description in Server.cpp. As you might expect, they are useful for updating data on a
+// regular basis or performing other periodic tasks. One example usage might be checking the battery level every 60 seconds and if
+// it has changed since the last update, send out a notification to subscribers.
+//
+// The frequency at which events fire is dependent upon two values: the driving timer's tick frequency multiplied by the tick
+// freuency of the TickEvent itself.
+//
+// The tick event's frequency is set when a tick event is added via the `onEvent()` method to the server description.
+//
+// The driving timer's frequency is a one-second-resolution low-frequency timer with a default of one second. To modify this, see
+// `kPeriodicTimerFrequencySeconds` at the top of Init.cpp. Note that the periodic timer (which drives tick events) is intentionally
+// a low-frequency timer. Higher frequency timers would lend themselves to using more battery on both, the server and client.
+//
+// When using a TickEvent, be careful not to demand too much of your client. Notifiations that are too frequent may place undue
+// stress on their battery to receive and process the updates.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <gio/gio.h>
+#include <string>
+
+#include "DBusObjectPath.h"
+#include "Logger.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Forward declarations
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct DBusInterface;
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Implementation
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+struct TickEvent
+{
+ //
+ // Types
+ //
+
+ // A tick event callback, which is called whenever the TickEvent fires
+ typedef void (*Callback)(const DBusInterface &self, const TickEvent &event, GDBusConnection *pConnection, void *pUserData);
+
+ // Construct a TickEvent that will fire after a specified 'tickFrequency' number of ticks of the periodic timer.
+ //
+ // Note that the actual time between a callback's execution is the event's 'tickFrequency' multiplied by the time between each
+ // periodic timer tick.
+ TickEvent(const DBusInterface *pOwner, int tickFrequency, Callback callback, void *pUserData)
+ : pOwner(pOwner), elapsedTicks(0), tickFrequency(tickFrequency), callback(callback), pUserData(pUserData)
+ {
+ }
+
+ //
+ // Accessors
+ //
+
+ // Returns the elapsed ticks since the last event firing
+ int getElapsedTicks() const { return elapsedTicks; }
+
+ // Sets the elapsed ticks since the last event firing
+ void setElapsedTicks(int elapsed) { elapsedTicks = elapsed; }
+
+ // Returns the tick frequency between schedule tick events
+ int getTickFrequency() const { return tickFrequency; }
+
+ // Sets the tick frequency between schedule tick events
+ void setTickFrequency(int frequency) { tickFrequency = frequency; }
+
+ // Returns the user data pointer associated to this TickEvent
+ void *getUserData() { return pUserData; }
+
+ // Sets the user data pointer associated to this TickEvent
+ void setUserData(void *pUserData) { this->pUserData = pUserData; }
+
+ // Gets the callback for the TickEvent
+ Callback getCallback() const { return callback; }
+
+ // Sets the callback for the TickEvent
+ void setCallback(Callback callback) { this->callback = callback; }
+
+ //
+ // Tick management
+ //
+
+ // Perform a single tick of a TickEvent
+ //
+ // A TickEvent is ticked each time the periodic timer fires. The TickEvent only fires after `tickFrequency` ticks. As a result,
+ // the `callback` is only called after a period of time equal to the time between firings of the periodic timer, multiplied by
+ // `tickFrequency`.
+ //
+ // Returns true if event fires, false otherwise
+ template<typename T>
+ void tick(const DBusObjectPath &path, GDBusConnection *pConnection, void *pUserData) const
+ {
+ elapsedTicks += 1;
+ if (elapsedTicks >= tickFrequency)
+ {
+ if (nullptr != callback)
+ {
+ Logger::info(SSTR << "Ticking at path '" << path << "'");
+ callback(*static_cast<const T *>(pOwner), *this, pConnection, pUserData);
+ }
+
+ elapsedTicks = 0;
+ }
+ }
+
+private:
+
+ //
+ // Data members
+ //
+
+ const DBusInterface *pOwner;
+ mutable int elapsedTicks;
+ int tickFrequency;
+ Callback callback;
+ void *pUserData;
+};
diff --git a/src/Utils.cpp b/src/Utils.cpp
new file mode 100644
index 0000000..655bd84
--- /dev/null
+++ b/src/Utils.cpp
@@ -0,0 +1,431 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains various general utilitarian functions used throught. It is in some ways, the 'junk drawer' of the appliation,
+// though better organized than most physical junk drawers.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This file contains:
+//
+// - String helper functions (trimming methods)
+// - Hexidecimal helper functions for
+// + Producing hex values of various types (8-bit, 16-bit, 32-bit)
+// + Standardied Hex/ASCII dumps to the log file of chunks of binary data
+// + Properly formatted Bluetooth addresses)
+// - GVariant helper funcions of various forms to convert values to/from GVariants
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <algorithm>
+#include <string.h>
+
+#include "Utils.h"
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Handy string functions
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Trim from start (in place)
+void Utils::trimBeginInPlace(std::string &str)
+{
+ str.erase(str.begin(), std::find_if(str.begin(), str.end(),
+ [](int ch)
+ {
+ return !std::isspace(ch);
+ }));
+}
+
+// Trim from end (in place)
+void Utils::trimEndInPlace(std::string &str)
+{
+ str.erase(std::find_if(str.rbegin(), str.rend(),
+ [](int ch)
+ {
+ return !std::isspace(ch);
+ }).base(), str.end());
+}
+
+// Trim from both ends (in place)
+void Utils::trimInPlace(std::string &str)
+{
+ trimBeginInPlace(str);
+ trimEndInPlace(str);
+}
+
+// Trim from start (copying)
+std::string Utils::trimBegin(const std::string &str)
+{
+ std::string out = str;
+ trimBeginInPlace(out);
+ return out;
+}
+
+// Trim from end (copying)
+std::string Utils::trimEnd(const std::string &str)
+{
+ std::string out = str;
+ trimEndInPlace(out);
+ return out;
+}
+
+// Trim from both ends (copying)
+std::string Utils::trim(const std::string &str)
+{
+ std::string out = str;
+ trimInPlace(out);
+ return out;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// Hex output functions
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Returns a zero-padded 8-bit hex value in the format: 0xA
+std::string Utils::hex(uint8_t value)
+{
+ char hex[16];
+ sprintf(hex, "0x%02X", value);
+ return hex;
+}
+
+// Returns a zero-padded 8-bit hex value in the format: 0xAB
+std::string Utils::hex(uint16_t value)
+{
+ char hex[16];
+ sprintf(hex, "0x%04X", value);
+ return hex;
+}
+
+// Returns a zero-padded 8-bit hex value in the format: 0xABCD
+std::string Utils::hex(uint32_t value)
+{
+ char hex[16];
+ sprintf(hex, "0x%08X", value);
+ return hex;
+}
+
+// A full hex-dump of binary data (with accompanying ASCII output)
+std::string Utils::hex(const uint8_t *pData, int count)
+{
+ char hex[16];
+
+ // Hex data output
+ std::string line;
+ std::vector<std::string> hexData;
+ for (int i = 0; i < count; ++i)
+ {
+ sprintf(hex, "%02X ", pData[i]);
+ line += hex;
+
+ if (line.length() >= 16 * 3)
+ {
+ hexData.push_back(line);
+ line = "";
+ }
+ }
+
+ if (!line.empty())
+ {
+ hexData.push_back(line);
+ line = "";
+ }
+
+ // ASCII data output
+ std::vector<std::string> asciiData;
+ for (int i = 0; i < count; ++i)
+ {
+ // Unprintable?
+ if (pData[i] < 0x20 || pData[i] > 0x7e)
+ {
+ line += ".";
+ }
+ else
+ {
+ line += pData[i];
+ }
+
+ if (line.length() >= 16)
+ {
+ asciiData.push_back(line);
+ line = "";
+ }
+ }
+
+ if (!line.empty())
+ {
+ asciiData.push_back(line);
+ }
+
+ std::string result = "\n";
+ size_t dataSize = hexData.size();
+ for (size_t i = 0; i < dataSize; ++i)
+ {
+ std::string hexPart = hexData[i];
+ hexPart.insert(hexPart.length(), 48-hexPart.length(), ' ');
+
+ std::string asciiPart = asciiData[i];
+ asciiPart.insert(asciiPart.length(), 16-asciiPart.length(), ' ');
+
+ result += std::string(" > ") + hexPart + " [" + asciiPart + "]";
+
+ if (i < dataSize - 1) { result += "\n"; }
+ }
+
+ return result;
+}
+
+// Returns a peoperly formatted Bluetooth address from a set of six octets stored at `pAddress`
+//
+// USE WITH CAUTION: It is expected that pAddress point to an array of 6 bytes. The length of the array cannot be validated and
+// incorrect lengths will produce undefined, likely unwanted and potentially fatal results. Or it will return the address of the
+// train at platform 9 3/4. You decide.
+//
+// This method returns a set of six zero-padded 8-bit hex values 8-bit in the format: 12:34:56:78:9A:BC
+std::string Utils::bluetoothAddressString(uint8_t *pAddress)
+{
+ char hex[32];
+ snprintf(hex, sizeof(hex), "%02X:%02X:%02X:%02X:%02X:%02X",
+ pAddress[0], pAddress[1], pAddress[2], pAddress[3], pAddress[4], pAddress[5]);
+ return hex;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------------------
+// GVariant helper functions
+// ---------------------------------------------------------------------------------------------------------------------------------
+
+// Returns a GVariant containing a floating reference to a utf8 string
+GVariant *Utils::gvariantFromString(const char *pStr)
+{
+ return g_variant_new_string(pStr);
+}
+
+// Returns a GVariant containing a floating reference to a utf8 string
+GVariant *Utils::gvariantFromString(const std::string &str)
+{
+ return g_variant_new_string(str.c_str());
+}
+
+// Returns an array of strings ("as") with one string per variable argument.
+//
+// The array must be terminated with a nullptr.
+//
+// This is an extension method to the vararg version, which accepts pass-through variable arguments from other mthods.
+GVariant *Utils::gvariantFromStringArray(const char *pStr, va_list args)
+{
+ // Deal with empty arrays
+ if (pStr == 0)
+ {
+ return g_variant_new("as", nullptr);
+ }
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ while(nullptr != pStr)
+ {
+ g_variant_builder_add(&builder, "s", pStr);
+ pStr = va_arg(args, const char *);
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+// Returns an array of strings ("as") with one string per variable argument.
+//
+// The array must be terminated with a nullptr.
+GVariant *Utils::gvariantFromStringArray(const char *pStr, ...)
+{
+ // Deal with empty arrays
+ if (pStr == 0)
+ {
+ return g_variant_new("as", nullptr);
+ }
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ va_list args;
+ va_start(args, pStr);
+
+ GVariant *pResult = gvariantFromStringArray(pStr, args);
+
+ va_end(args);
+
+ return pResult;
+}
+
+// Returns an array of strings ("as") from an array of strings
+GVariant *Utils::gvariantFromStringArray(const std::vector<std::string> &arr)
+{
+ // Deal with empty arrays
+ if (arr.empty())
+ {
+ return g_variant_new("as", nullptr);
+ }
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ for (std::string str : arr)
+ {
+ g_variant_builder_add(&builder, "s", str.c_str());
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+// Returns an array of strings ("as") from an array of C strings
+GVariant *Utils::gvariantFromStringArray(const std::vector<const char *> &arr)
+{
+ // Deal with empty arrays
+ if (arr.empty())
+ {
+ return g_variant_new("as", nullptr);
+ }
+
+ g_auto(GVariantBuilder) builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ for (const char *pStr : arr)
+ {
+ g_variant_builder_add(&builder, "s", pStr);
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+// Returns an GVariant* containing an object path ("o") from an DBusObjectPath
+GVariant *Utils::gvariantFromObject(const DBusObjectPath &path)
+{
+ return g_variant_new_object_path(path.c_str());
+}
+
+// Returns an GVariant* containing a boolean
+GVariant *Utils::gvariantFromBoolean(bool b)
+{
+ return g_variant_new_boolean(b);
+}
+
+// Returns an GVariant* containing a 16-bit integer
+GVariant *Utils::gvariantFromInt(gint16 value)
+{
+ return g_variant_new_int16(value);
+}
+
+// Returns an GVariant* containing a 32-bit integer
+GVariant *Utils::gvariantFromInt(gint32 value)
+{
+ return g_variant_new_int32(value);
+}
+
+// Returns an array of bytes ("ay") with the contents of the input C string
+GVariant *Utils::gvariantFromByteArray(const char *pStr)
+{
+ // Deal with empty arrays
+ if (*pStr == 0)
+ {
+ return g_variant_new("ay", nullptr);
+ }
+
+ return g_variant_new_bytestring(pStr);
+}
+
+// Returns an array of bytes ("ay") with the contents of the input string
+GVariant *Utils::gvariantFromByteArray(const std::string &str)
+{
+ return gvariantFromByteArray(str.c_str());
+}
+
+// Returns an array of bytes ("ay") with the contents of the input array of unsigned 8-bit values
+GVariant *Utils::gvariantFromByteArray(const guint8 *pBytes, int count)
+{
+ GBytes *pGbytes = g_bytes_new(pBytes, count);
+ return g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, pGbytes, count);
+}
+
+// Returns an array of bytes ("ay") with the contents of the input array of unsigned 8-bit values
+GVariant *Utils::gvariantFromByteArray(const std::vector<guint8> bytes)
+{
+ GBytes *pGbytes = g_bytes_new(bytes.data(), bytes.size());
+ return g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, pGbytes, bytes.size());
+}
+
+// Returns an array of bytes ("ay") containing a single unsigned 8-bit value
+GVariant *Utils::gvariantFromByteArray(const guint8 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single signed 8-bit value
+GVariant *Utils::gvariantFromByteArray(const gint8 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single unsigned 16-bit value
+GVariant *Utils::gvariantFromByteArray(const guint16 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single signed 16-bit value
+GVariant *Utils::gvariantFromByteArray(const gint16 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single unsigned 32-bit value
+GVariant *Utils::gvariantFromByteArray(const guint32 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single signed 32-bit value
+GVariant *Utils::gvariantFromByteArray(const gint32 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single unsigned 64-bit value
+GVariant *Utils::gvariantFromByteArray(const guint64 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Returns an array of bytes ("ay") containing a single signed 64-bit value
+GVariant *Utils::gvariantFromByteArray(const gint64 data)
+{
+ return gvariantFromByteArray((const guint8 *) &data, sizeof(data));
+}
+
+// Extracts a string from an array of bytes ("ay")
+std::string Utils::stringFromGVariantByteArray(const GVariant *pVariant)
+{
+ gsize size;
+ gconstpointer pPtr = g_variant_get_fixed_array(const_cast<GVariant *>(pVariant), &size, 1);
+ std::vector<gchar> array(size + 1, 0);
+ memcpy(array.data(), pPtr, size);
+ return array.data();
+}
diff --git a/src/Utils.h b/src/Utils.h
new file mode 100644
index 0000000..2eee95a
--- /dev/null
+++ b/src/Utils.h
@@ -0,0 +1,212 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This file contains various general utilitarian functions used throught. It is in some ways, the 'junk drawer' of the appliation,
+// though better organized than most physical junk drawers.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// This file contains:
+//
+// - String helper functions (trimming methods)
+// - Hexidecimal helper functions for
+// + Producing hex values of various types (8-bit, 16-bit, 32-bit)
+// + Standardied Hex/ASCII dumps to the log file of chunks of binary data
+// + Properly formatted Bluetooth addresses)
+// - GVariant helper funcions of various forms to convert values to/from GVariants
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#pragma once
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <vector>
+#include <string>
+#include <endian.h>
+
+#include "DBusObjectPath.h"
+
+struct Utils
+{
+ // -----------------------------------------------------------------------------------------------------------------------------
+ // Handy string functions
+ // -----------------------------------------------------------------------------------------------------------------------------
+
+ // Trim from start (in place)
+ static void trimBeginInPlace(std::string &str);
+
+ // Trim from end (in place)
+ static void trimEndInPlace(std::string &str);
+
+ // Trim from both ends (in place)
+ static void trimInPlace(std::string &str);
+
+ // Trim from start (copying)
+ static std::string trimBegin(const std::string &str);
+
+ // Trim from end (copying)
+ static std::string trimEnd(const std::string &str);
+
+ // Trim from both ends (copying)
+ static std::string trim(const std::string &str);
+
+ // -----------------------------------------------------------------------------------------------------------------------------
+ // Hex output functions
+ // -----------------------------------------------------------------------------------------------------------------------------
+
+ // Returns a zero-padded 8-bit hex value in the format: 0xA
+ static std::string hex(uint8_t value);
+
+ // Returns a zero-padded 8-bit hex value in the format: 0xAB
+ static std::string hex(uint16_t value);
+
+ // Returns a zero-padded 8-bit hex value in the format: 0xABCD
+ static std::string hex(uint32_t value);
+
+ // A full hex-dump of binary data (with accompanying ASCII output)
+ static std::string hex(const uint8_t *pData, int count);
+
+ // Returns a peoperly formatted Bluetooth address from a set of six octets stored at `pAddress`
+ //
+ // USE WITH CAUTION: It is expected that pAddress point to an array of 6 bytes. The length of the array cannot be validated and
+ // incorrect lengths will produce undefined, likely unwanted and potentially fatal results. Or it will return the address of the
+ // train at platform 9 3/4. You decide.
+ //
+ // This method returns a set of six zero-padded 8-bit hex values 8-bit in the format: 12:34:56:78:9A:BC
+ static std::string bluetoothAddressString(uint8_t *pAddress);
+
+ // -----------------------------------------------------------------------------------------------------------------------------
+ // A small collection of helper functions for generating various types of GVariants, which are needed when responding to BlueZ
+ // method/property messages. Real services will likley need more of these to support various types of data passed to/from BlueZ,
+ // or feel free to do away with them and use GLib directly.
+ // -----------------------------------------------------------------------------------------------------------------------------
+
+ // Returns a GVariant containing a floating reference to a utf8 string
+ static GVariant *gvariantFromString(const char *pStr);
+
+ // Returns a GVariant containing a floating reference to a utf8 string
+ static GVariant *gvariantFromString(const std::string &str);
+
+ // Returns an array of strings ("as") with one string per variable argument.
+ //
+ // The array must be terminated with a nullptr.
+ //
+ // This is an extension method to the vararg version, which accepts pass-through variable arguments from other mthods.
+ static GVariant *gvariantFromStringArray(const char *pStr, va_list args);
+
+ // Returns an array of strings ("as") with one string per variable argument.
+ //
+ // The array must be terminated with a nullptr.
+ static GVariant *gvariantFromStringArray(const char *pStr, ...);
+
+ // Returns an array of strings ("as") from an array of strings
+ static GVariant *gvariantFromStringArray(const std::vector<std::string> &arr);
+
+ // Returns an array of strings ("as") from an array of C strings
+ static GVariant *gvariantFromStringArray(const std::vector<const char *> &arr);
+
+ // Returns an GVariant* containing an object path ("o") from an DBusObjectPath
+ static GVariant *gvariantFromObject(const DBusObjectPath &path);
+
+ // Returns an GVariant* containing a boolean
+ static GVariant *gvariantFromBoolean(bool b);
+
+ // Returns an GVariant* containing a 16-bit integer
+ static GVariant *gvariantFromInt(gint16 value);
+
+ // Returns an GVariant* containing a 32-bit integer
+ static GVariant *gvariantFromInt(gint32 value);
+
+ // Returns an array of bytes ("ay") with the contents of the input C string
+ static GVariant *gvariantFromByteArray(const char *pStr);
+
+ // Returns an array of bytes ("ay") with the contents of the input string
+ static GVariant *gvariantFromByteArray(const std::string &str);
+
+ // Returns an array of bytes ("ay") with the contents of the input array of unsigned 8-bit values
+ static GVariant *gvariantFromByteArray(const guint8 *pBytes, int count);
+
+ // Returns an array of bytes ("ay") with the contents of the input array of unsigned 8-bit values
+ static GVariant *gvariantFromByteArray(const std::vector<guint8> bytes);
+
+ // Returns an array of bytes ("ay") containing a single unsigned 8-bit value
+ static GVariant *gvariantFromByteArray(const guint8 data);
+
+ // Returns an array of bytes ("ay") containing a single signed 8-bit value
+ static GVariant *gvariantFromByteArray(const gint8 data);
+
+ // Returns an array of bytes ("ay") containing a single unsigned 16-bit value
+ static GVariant *gvariantFromByteArray(const guint16 data);
+
+ // Returns an array of bytes ("ay") containing a single signed 16-bit value
+ static GVariant *gvariantFromByteArray(const gint16 data);
+
+ // Returns an array of bytes ("ay") containing a single unsigned 32-bit value
+ static GVariant *gvariantFromByteArray(const guint32 data);
+
+ // Returns an array of bytes ("ay") containing a single signed 32-bit value
+ static GVariant *gvariantFromByteArray(const gint32 data);
+
+ // Returns an array of bytes ("ay") containing a single unsigned 64-bit value
+ static GVariant *gvariantFromByteArray(const guint64 data);
+
+ // Returns an array of bytes ("ay") containing a single signed 64-bit value
+ static GVariant *gvariantFromByteArray(const gint64 data);
+
+ // Extracts a string from an array of bytes ("ay")
+ static std::string stringFromGVariantByteArray(const GVariant *pVariant);
+
+ // -----------------------------------------------------------------------------------------------------------------------------
+ // Endian conversion
+ //
+ // The Bluetooth Management API defines itself has using little-endian byte order. In the methods below, 'Hci' refers to this
+ // format, while 'Host' refers to the endianness of the hardware we are running on.
+ //
+ // The `Utils::endianToHost()` method overloads perform endian byte-ordering conversions from the HCI to our endian format
+ // The `Utils::endianToHci()` method overloads perform endian byte-ordering conversions from our endian format to that of the HCI
+ // -----------------------------------------------------------------------------------------------------------------------------
+
+ // Convert a byte from HCI format to host format
+ //
+ // Since bytes are endian agnostic, this function simply returns the input value
+ static uint8_t endianToHost(uint8_t value) {return value;}
+
+ // Convert a byte from host format to HCI format
+ //
+ // Since bytes are endian agnostic, this function simply returns the input value
+ static uint8_t endianToHci(uint8_t value) {return value;}
+
+ // Convert a 16-bit value from HCI format to host format
+ static uint16_t endianToHost(uint16_t value) {return le16toh(value);}
+
+ // Convert a 16-bit value from host format to HCI format
+ static uint16_t endianToHci(uint16_t value) {return htole16(value);}
+
+ // Convert a 32-bit value from HCI format to host format
+ static uint32_t endianToHost(uint32_t value) {return le32toh(value);}
+
+ // Convert a 32-bit value from host format to HCI format
+ static uint32_t endianToHci(uint32_t value) {return htole32(value);}
+}; \ No newline at end of file
diff --git a/src/standalone.cpp b/src/standalone.cpp
new file mode 100644
index 0000000..c8af5d6
--- /dev/null
+++ b/src/standalone.cpp
@@ -0,0 +1,320 @@
+// Copyright 2017 Paul Nettle.
+//
+// This file is part of Gobbledegook.
+//
+// Gobbledegook 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.
+//
+// Gobbledegook 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 Gobbledegook. If not, see <http://www.gnu.org/licenses/>.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// >>
+// >>> INSIDE THIS FILE
+// >>
+//
+// This is an example single-file stand-alone application that runs a Gobbledegook server.
+//
+// >>
+// >>> DISCUSSION
+// >>
+//
+// Very little is required ("MUST") by a stand-alone application to instantiate a valid Gobbledegook server. There are also some
+// things that are reocommended ("SHOULD").
+//
+// * A stand-alone application MUST:
+//
+// * Start the server via a call to `ggkStart()`.
+//
+// Once started the server will run on its own thread.
+//
+// Two of the parameters to `ggkStart()` are delegates responsible for providing data accessors for the server, a
+// `GGKServerDataGetter` delegate and a 'GGKServerDataSetter' delegate. The getter method simply receives a string name (for
+// example, "battery/level") and returns a void pointer to that data (for example: `(void *)&batteryLevel`). The setter does
+// the same only in reverse.
+//
+// While the server is running, you will likely need to update the data being served. This is done by calling
+// `ggkNofifyUpdatedCharacteristic()` or `ggkNofifyUpdatedDescriptor()` with the full path to the characteristic or delegate
+// whose data has been updated. This will trigger your server's `onUpdatedValue()` method, which can perform whatever
+// actions are needed such as sending out a change notification (or in BlueZ parlance, a "PropertiesChanged" signal.)
+//
+// * A stand-alone application SHOULD:
+//
+// * Shutdown the server before termination
+//
+// Triggering the server to begin shutting down is done via a call to `ggkTriggerShutdown()`. This is a non-blocking method
+// that begins the asynchronous shutdown process.
+//
+// Before your application terminates, it should wait for the server to be completely stopped. This is done via a call to
+// `ggkWait()`. If the server has not yet reached the `EStopped` state when `ggkWait()` is called, it will block until the
+// server has done so.
+//
+// To avoid the blocking behavior of `ggkWait()`, ensure that the server has stopped before calling it. This can be done
+// by ensuring `ggkGetServerRunState() == EStopped`. Even if the server has stopped, it is recommended to call `ggkWait()`
+// to ensure the server has cleaned up all threads and other internals.
+//
+// If you want to keep things simple, there is a method `ggkShutdownAndWait()` which will trigger the shutdown and then
+// block until the server has stopped.
+//
+// * Implement signal handling to provide a clean shut-down
+//
+// This is done by calling `ggkTriggerShutdown()` from any signal received that can terminate your application. For an
+// example of this, search for all occurrences of the string "signalHandler" in the code below.
+//
+// * Register a custom logging mechanism with the server
+//
+// This is done by calling each of the log registeration methods:
+//
+// `ggkLogRegisterDebug()`
+// `ggkLogRegisterInfo()`
+// `ggkLogRegisterStatus()`
+// `ggkLogRegisterWarn()`
+// `ggkLogRegisterError()`
+// `ggkLogRegisterFatal()`
+// `ggkLogRegisterAlways()`
+// `ggkLogRegisterTrace()`
+//
+// Each registration method manages a different log level. For a full description of these levels, see the header comment
+// in Logger.cpp.
+//
+// The code below includes a simple logging mechanism that logs to stdout and filters logs based on a few command-line
+// options to specify the level of verbosity.
+//
+// >>
+// >>> Building with GOBBLEDEGOOK
+// >>
+//
+// The Gobbledegook distribution includes this file as part of the Gobbledegook files with everything compiling to a single, stand-
+// alone binary. It is built this way because Gobbledegook is not intended to be a generic library. You will need to make your
+// custom modifications to it. Don't worry, a lot of work went into Gobbledegook to make it almost trivial to customize
+// (see Server.cpp).
+//
+// If it is important to you or your build process that Gobbledegook exist as a library, you are welcome to do so. Just configure
+// your build process to build the Gobbledegook files (minus this file) as a library and link against that instead. All that is
+// required by applications linking to a Gobbledegook library is to include `include/Gobbledegook.h`.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#include <signal.h>
+#include <iostream>
+#include <thread>
+#include <sstream>
+
+#include "../include/Gobbledegook.h"
+
+//
+// Constants
+//
+
+// Maximum time to wait for any single async process to timeout during initialization
+static const int kMaxAsyncInitTimeoutMS = 30 * 1000;
+
+//
+// Server data values
+//
+
+// The battery level ("battery/level") reported by the server (see Server.cpp)
+static uint8_t serverDataBatteryLevel = 78;
+
+// The text string ("text/string") used by our custom text string service (see Server.cpp)
+static std::string serverDataTextString = "Hello, world!";
+
+//
+// Logging
+//
+
+enum LogLevel
+{
+ Debug,
+ Verbose,
+ Normal,
+ ErrorsOnly
+};
+
+// Our log level - defaulted to 'Normal' but can be modified via command-line options
+LogLevel logLevel = Normal;
+
+// Our full set of logging methods (we just log to stdout)
+//
+// NOTE: Some methods will only log if the appropriate `logLevel` is set
+void LogDebug(const char *pText) { if (logLevel <= Debug) { std::cout << " DEBUG: " << pText << std::endl; } }
+void LogInfo(const char *pText) { if (logLevel <= Verbose) { std::cout << " INFO: " << pText << std::endl; } }
+void LogStatus(const char *pText) { if (logLevel <= Normal) { std::cout << " STATUS: " << pText << std::endl; } }
+void LogWarn(const char *pText) { std::cout << "WARNING: " << pText << std::endl; }
+void LogError(const char *pText) { std::cout << "!!ERROR: " << pText << std::endl; }
+void LogFatal(const char *pText) { std::cout << "**FATAL: " << pText << std::endl; }
+void LogAlways(const char *pText) { std::cout << "..Log..: " << pText << std::endl; }
+void LogTrace(const char *pText) { std::cout << "-Trace-: " << pText << std::endl; }
+
+//
+// Signal handling
+//
+
+// We setup a couple Unix signals to perform graceful shutdown in the case of SIGTERM or get an SIGING (CTRL-C)
+void signalHandler(int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ LogStatus("SIGINT recieved, shutting down");
+ ggkTriggerShutdown();
+ break;
+ case SIGTERM:
+ LogStatus("SIGTERM recieved, shutting down");
+ ggkTriggerShutdown();
+ break;
+ }
+}
+
+//
+// Server data management
+//
+
+// Called by the server when it wants to retrieve a named value
+//
+// This method conforms to `GGKServerDataGetter` and is passed to the server via our call to `ggkStart()`.
+//
+// The server calls this method from its own thread, so we must ensure our implementation is thread-safe. In our case, we're simply
+// sending over stored values, so we don't need to take any additional steps to ensure thread-safety.
+const void *dataGetter(const char *pName)
+{
+ if (nullptr == pName)
+ {
+ LogError("NULL name sent to server data getter");
+ return nullptr;
+ }
+
+ std::string strName = pName;
+
+ if (strName == "battery/level")
+ {
+ return &serverDataBatteryLevel;
+ }
+ else if (strName == "text/string")
+ {
+ return serverDataTextString.c_str();
+ }
+
+ LogWarn((std::string("Unknown name for server data getter request: '") + pName + "'").c_str());
+ return nullptr;
+}
+
+// Called by the server when it wants to update a named value
+//
+// This method conforms to `GGKServerDataSetter` and is passed to the server via our call to `ggkStart()`.
+//
+// The server calls this method from its own thread, so we must ensure our implementation is thread-safe. In our case, we're simply
+// sending over stored values, so we don't need to take any additional steps to ensure thread-safety.
+int dataSetter(const char *pName, const void *pData)
+{
+ if (nullptr == pName)
+ {
+ LogError("NULL name sent to server data setter");
+ return 0;
+ }
+ if (nullptr == pData)
+ {
+ LogError("NULL pData sent to server data setter");
+ return 0;
+ }
+
+ std::string strName = pName;
+
+ if (strName == "battery/level")
+ {
+ serverDataBatteryLevel = *static_cast<const uint8_t *>(pData);
+ LogDebug((std::string("Server data: battery level set to ") + std::to_string(serverDataBatteryLevel)).c_str());
+ return 1;
+ }
+ else if (strName == "text/string")
+ {
+ serverDataTextString = static_cast<const char *>(pData);
+ LogDebug((std::string("Server data: text string set to '") + serverDataTextString + "'").c_str());
+ return 1;
+ }
+
+ LogWarn((std::string("Unknown name for server data setter request: '") + pName + "'").c_str());
+
+ return 0;
+}
+
+//
+// Entry point
+//
+
+int main(int argc, char **ppArgv)
+{
+ // A basic command-line parser
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string arg = ppArgv[i];
+ if (arg == "-q")
+ {
+ logLevel = ErrorsOnly;
+ }
+ else if (arg == "-v")
+ {
+ logLevel = Verbose;
+ }
+ else if (arg == "-d")
+ {
+ logLevel = Debug;
+ }
+ else
+ {
+ LogFatal((std::string("Unknown parameter: '") + arg + "'").c_str());
+ LogFatal("");
+ LogFatal("Usage: standalone [-q | -v | -d]");
+ return -1;
+ }
+ }
+
+ // Setup our signal handlers
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+
+ // Register our loggers
+ ggkLogRegisterDebug(LogDebug);
+ ggkLogRegisterInfo(LogInfo);
+ ggkLogRegisterStatus(LogStatus);
+ ggkLogRegisterWarn(LogWarn);
+ ggkLogRegisterError(LogError);
+ ggkLogRegisterFatal(LogFatal);
+ ggkLogRegisterAlways(LogAlways);
+ ggkLogRegisterTrace(LogTrace);
+
+ // Start the server's ascync processing
+ //
+ // This starts the server on a thread and begins the initialization process
+ if (!ggkStart(dataGetter, dataSetter, kMaxAsyncInitTimeoutMS))
+ {
+ return -1;
+ }
+
+ // Wait for the server to start the shutdown process
+ //
+ // While we wait, every 15 ticks, drop the battery level by one percent until we reach 0
+ while (ggkGetServerRunState() < EStopping)
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(15));
+
+ serverDataBatteryLevel = std::max(serverDataBatteryLevel - 1, 0);
+ ggkNofifyUpdatedCharacteristic("/com/gobbledegook/battery/level");
+ }
+
+ // Wait for the server to come to a complete stop (CTRL-C from the command line)
+ if (!ggkWait())
+ {
+ return -1;
+ }
+
+ // Return the final server health status as a success (0) or error (-1)
+ return ggkGetServerHealth() == EOk ? 0 : 1;
+}