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