From 06f646aec4dbce64d28bae1be6111bd833f8e79e Mon Sep 17 00:00:00 2001 From: Paul Nettle Date: Fri, 25 Aug 2017 09:30:39 -0500 Subject: Initial version 1.0 --- src/DBusInterface.cpp | 202 ++++++++ src/DBusInterface.h | 140 +++++ src/DBusMethod.cpp | 88 ++++ src/DBusMethod.h | 118 +++++ src/DBusObject.cpp | 286 +++++++++++ src/DBusObject.h | 135 +++++ src/DBusObjectPath.h | 172 +++++++ src/GattCharacteristic.cpp | 217 ++++++++ src/GattCharacteristic.h | 219 ++++++++ src/GattDescriptor.cpp | 176 +++++++ src/GattDescriptor.h | 183 +++++++ src/GattInterface.cpp | 123 +++++ src/GattInterface.h | 231 +++++++++ src/GattProperty.cpp | 185 +++++++ src/GattProperty.h | 106 ++++ src/GattService.cpp | 93 ++++ src/GattService.h | 99 ++++ src/GattUuid.h | 282 +++++++++++ src/Globals.h | 71 +++ src/Gobbledegook.cpp | 433 ++++++++++++++++ src/HciAdapter.cpp | 384 ++++++++++++++ src/HciAdapter.h | 138 +++++ src/HciSocket.cpp | 198 ++++++++ src/HciSocket.h | 90 ++++ src/Init.cpp | 1207 ++++++++++++++++++++++++++++++++++++++++++++ src/Init.h | 43 ++ src/Logger.cpp | 200 ++++++++ src/Logger.h | 185 +++++++ src/Makefile.am | 48 ++ src/Makefile.in | 909 +++++++++++++++++++++++++++++++++ src/Mgmt.cpp | 339 +++++++++++++ src/Mgmt.h | 213 ++++++++ src/Server.cpp | 618 +++++++++++++++++++++++ src/Server.h | 146 ++++++ src/ServerUtils.cpp | 314 ++++++++++++ src/ServerUtils.h | 62 +++ src/TickEvent.h | 149 ++++++ src/Utils.cpp | 431 ++++++++++++++++ src/Utils.h | 212 ++++++++ src/standalone.cpp | 320 ++++++++++++ 40 files changed, 9765 insertions(+) create mode 100644 src/DBusInterface.cpp create mode 100644 src/DBusInterface.h create mode 100644 src/DBusMethod.cpp create mode 100644 src/DBusMethod.h create mode 100644 src/DBusObject.cpp create mode 100644 src/DBusObject.h create mode 100644 src/DBusObjectPath.h create mode 100644 src/GattCharacteristic.cpp create mode 100644 src/GattCharacteristic.h create mode 100644 src/GattDescriptor.cpp create mode 100644 src/GattDescriptor.h create mode 100644 src/GattInterface.cpp create mode 100644 src/GattInterface.h create mode 100644 src/GattProperty.cpp create mode 100644 src/GattProperty.h create mode 100644 src/GattService.cpp create mode 100644 src/GattService.h create mode 100644 src/GattUuid.h create mode 100644 src/Globals.h create mode 100644 src/Gobbledegook.cpp create mode 100644 src/HciAdapter.cpp create mode 100644 src/HciAdapter.h create mode 100644 src/HciSocket.cpp create mode 100644 src/HciSocket.h create mode 100644 src/Init.cpp create mode 100644 src/Init.h create mode 100644 src/Logger.cpp create mode 100644 src/Logger.h create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/Mgmt.cpp create mode 100644 src/Mgmt.h create mode 100644 src/Server.cpp create mode 100644 src/Server.h create mode 100644 src/ServerUtils.cpp create mode 100644 src/ServerUtils.h create mode 100644 src/TickEvent.h create mode 100644 src/Utils.cpp create mode 100644 src/Utils.h create mode 100644 src/standalone.cpp (limited to 'src') 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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(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(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 + "\n"; + } + else + { + xml += prefix + "\n"; + + // Describe our methods + for (const DBusMethod &method : methods) + { + xml += method.generateIntrospectionXML(depth + 1); + } + + xml += prefix + "\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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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(pInterface) : \ + nullptr) + +#define TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, type) \ + (pInterface->getInterfaceType() == type::kInterfaceType ? \ + std::static_pointer_cast(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 methods; + std::list 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 + "\n"; + + // Add our input arguments + for (const std::string &inArg : getInArgs()) + { + xml += prefix + " \n"; + xml += prefix + " \n"; + xml += prefix + " \n"; + } + + const std::string &outArgs = getOutArgs(); + if (!outArgs.empty()) + { + xml += prefix + " \n"; + xml += prefix + " \n"; + xml += prefix + " \n"; + } + + xml += prefix + "\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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 &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 &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 + 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(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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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::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(child, "org.bluez.GattService1")); + service.addProperty("UUID", uuid); + service.addProperty("Primary", true); + return service; +} + +// +// Helpful routines for searching objects +// + +// Finds an interface by name within this D-Bus object +std::shared_ptr DBusObject::findInterface(const DBusObjectPath &path, const std::string &interfaceName, const DBusObjectPath &basePath) const +{ + if ((basePath + getPathNode()) == path) + { + for (std::shared_ptr interface : interfaces) + { + if (interfaceName == interface->getName()) + { + return interface; + } + } + } + + for (const DBusObject &child : getChildren()) + { + std::shared_ptr 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 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 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 += "\n"; + xml += "\n"; + } + + xml += prefix + "\n"; + xml += prefix + " \n"; + + for (std::shared_ptr interface : interfaces) + { + xml += interface->generateIntrospectionXML(depth + 1); + } + + for (DBusObject child : getChildren()) + { + xml += child.generateIntrospectionXML(depth + 1); + } + + xml += prefix + "\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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include + +#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 > 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 &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 + std::shared_ptr addInterface(std::shared_ptr interface) + { + interfaces.push_back(interface); + return std::static_pointer_cast(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 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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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(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(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(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(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(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 &flags) +{ + DBusObject &child = owner.addChild(DBusObjectPath(pathElement)); + GattDescriptor &descriptor = *child.addInterface(std::make_shared(child, *this, "org.bluez.GattDescriptor1")); + descriptor.addProperty("UUID", uuid); + descriptor.addProperty("Characteristic", getPath()); + descriptor.addProperty("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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include + +#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 &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 + 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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(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(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(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(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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 &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 + "\n"; + } + else + { + xml += prefix + "\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 + "\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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 &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 + T &addProperty(const GattProperty &property) + { + properties.push_back(property); + return *static_cast(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 + T &addProperty(const std::string &name, GVariant *pValue, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, pValue, getter, setter)); + } + + // Helper method for adding a named property with a `GattUuid` + template + T &addProperty(const std::string &name, const GattUuid &uuid, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromString(uuid.toString128().c_str()), getter, setter)); + } + + // Helper method for adding a named property with a `DBusObjectPath` + template + T &addProperty(const std::string &name, const DBusObjectPath &path, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromObject(path), getter, setter)); + } + + // Helper method for adding a named property with a std::strings + template + T &addProperty(const std::string &name, const std::string &str, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromString(str), getter, setter)); + } + + // Helper method for adding a named property with an array of std::strings + template + T &addProperty(const std::string &name, const std::vector &arr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromStringArray(arr), getter, setter)); + } + + // Helper method for adding a named property with an array of C strings + template + T &addProperty(const std::string &name, const std::vector &arr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromStringArray(arr), getter, setter)); + } + + // Helper method for adding a named property with a given C string + template + T &addProperty(const std::string &name, const char *pStr, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(GattProperty(name, Utils::gvariantFromString(pStr), getter, setter)); + } + + // Helper method for adding a named property with a given boolean value + template + T &addProperty(const std::string &name, bool value, GDBusInterfaceGetPropertyFunc getter = nullptr, GDBusInterfaceSetPropertyFunc setter = nullptr) + { + return addProperty(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("battery/level", 0); + template + T getDataValue(const char *pName, const T defaultValue) const + { + const void *pData = TheServer->getDataGetter()(pName); + return nullptr == pData ? defaultValue : *static_cast(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("text/string", ""); + template + T getDataPointer(const char *pName, const T defaultValue) const + { + const void *pData = TheServer->getDataGetter()(pName); + return nullptr == pData ? defaultValue : static_cast(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 + bool setDataValue(const char *pName, const T value) const + { + return TheServer->getDataSetter()(pName, static_cast(&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 + bool setDataPointer(const char *pName, const T pointer) const + { + return TheServer->getDataSetter()(pName, static_cast(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 + 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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +#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(getValue()); + const gchar *pType = g_variant_get_type_string(pValue); + xml += prefix + "\n"; + + if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_BOOLEAN)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT16)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT16)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT32)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT32)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_INT64)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_UINT64)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_DOUBLE)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_STRING)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_OBJECT_PATH)) + { + xml += prefix + " \n"; + } + else if (g_variant_is_of_type(pValue, G_VARIANT_TYPE_BYTESTRING)) + { + xml += prefix + " \n"; + } + + xml += prefix + "\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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 &flags) +{ + DBusObject &child = owner.addChild(DBusObjectPath(pathElement)); + GattCharacteristic &characteristic = *child.addInterface(std::make_shared(child, *this, "org.bluez.GattCharacteristic1")); + characteristic.addProperty("UUID", uuid); + characteristic.addProperty("Service", owner.getPath()); + characteristic.addProperty("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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 &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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include + +#include +#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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 + +// 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include +#include + +#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 QueueEntry; +std::deque 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 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 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(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 guard(updateQueueMutex); + return updateQueue.empty() ? 1 : 0; +} + +// Returns the number of entries waiting in the queue +int ggkUpdateQueueSize() +{ + std::lock_guard guard(updateQueueMutex); + return updateQueue.size(); +} + +// Removes all entries from the queue +void ggkUpdateQueueClear() +{ + std::lock_guard 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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 + +#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(&request); + std::vector requestPacket = std::vector(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 responsePacket = std::vector(); + 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(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 &buffer) const +{ + // Chew through each event in the buffer, removing those that are not related to the requested commandCode + std::vector lastGoodEvent; + while (!buffer.empty()) + { + // Get an event pointer into the start of our buffer + ResponseEvent *pEvent = reinterpret_cast(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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +#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 &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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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(&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 &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 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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +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 &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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include +#include +#include + +#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 registeredObjectIds; +static std::atomic 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 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 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(g_list_nth_data(pObjects, i)); + if (nullptr == pBluezAdapterObject) { continue; } + + // See if it has a GATT manager interface + pBluezGattManagerProxy = reinterpret_cast(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(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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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(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(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(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(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(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(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(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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 + +#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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 + +#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(newState)); + return false; + } + + response.toHost(); + + Logger::info(SSTR << " + " << pSettingName << " set to " << static_cast(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +#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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 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("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("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("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("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(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 Server::findInterface(const DBusObjectPath &objectPath, const std::string &interfaceName) const +{ + for (const DBusObject &object : objects) + { + std::shared_ptr 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 pInterface = findInterface(objectPath, interfaceName); + + // Try each of the GattInterface types that support properties? + if (std::shared_ptr pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattInterface)) + { + return pGattInterface->findProperty(propertyName); + } + else if (std::shared_ptr pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattService)) + { + return pGattInterface->findProperty(propertyName); + } + else if (std::shared_ptr 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include + +#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 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 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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include + +#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 pInterface : object.getInterfaces()) + { + Logger::debug(SSTR << " + Interface (type: " << pInterface->getInterfaceType() << ")"); + + if (std::shared_ptr 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 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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +#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 + 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(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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include + +#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 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 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 &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 &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 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(pVariant), &size, 1); + std::vector 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include +#include + +#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 &arr); + + // Returns an array of strings ("as") from an array of C strings + static GVariant *gvariantFromStringArray(const std::vector &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 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 . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> 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 +#include +#include +#include + +#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(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(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; +} -- cgit v1.2.3