// 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"
namespace ggk {
// 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;
}
}; // namespace ggk