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/Server.cpp | 618 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 618 insertions(+) create mode 100644 src/Server.cpp (limited to 'src/Server.cpp') 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; +} -- cgit v1.2.3