// 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
#include "HciSocket.h"
#include "Logger.h"
#include "Utils.h"
namespace ggk {
// 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 (fd = " << 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())
{
Logger::debug("HciSocket disconnecting");
if (close(fdSocket) != 0)
{
logErrno("close(fdSocket)");
}
fdSocket = -1;
Logger::trace("HciSocket closed");
}
}
// Reads data from the HCI socket
//
// Raw data is read and returned in `response`.
//
// Returns true if data was read successfully, otherwise false is returned. A false return code does not necessarily depict
// an error, as this can arise from expected conditions (such as an interrupt.)
bool HciSocket::read(std::vector &response) const
{
// Fill our response with empty data
response.resize(kResponseMaxSize, 0);
// Wait for data or a cancellation
if (!waitForDataOrShutdown())
{
return false;
}
// Block until we receive data, a disconnect, or a signal
ssize_t bytesRead = ::recv(fdSocket, &response[0], kResponseMaxSize, MSG_WAITALL);
// If there was an error, wipe the data and return an error condition
if (bytesRead < 0)
{
if (errno == EINTR)
{
Logger::debug("HciSocket receive interrupted");
}
else
{
logErrno("recv");
}
response.resize(0);
return false;
}
else if (bytesRead == 0)
{
Logger::error("Peer closed the socket");
response.resize(0);
return false;
}
// We have data
response.resize(bytesRead);
std::string dump = "";
dump += " > Read " + std::to_string(response.size()) + " bytes\n";
dump += Utils::hex(response.data(), response.size());
Logger::debug(dump);
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
{
std::string dump = "";
dump += " > Writing " + std::to_string(count) + " bytes\n";
dump += Utils::hex(pBuffer, count);
Logger::debug(dump);
size_t len = ::write(fdSocket, pBuffer, count);
if (len != count)
{
logErrno("write");
return false;
}
return true;
}
// Wait for data to arrive, or for a shutdown event
//
// Returns true if data is available, false if we are shutting down
bool HciSocket::waitForDataOrShutdown() const
{
while(ggkIsServerRunning())
{
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fdSocket, &rfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = kDataWaitTimeMS * 1000;
int retval = select(fdSocket+1, &rfds, NULL, NULL, &tv);
// Do we have data?
if (retval > 0) { return true; }
// We have an error
if (retval < 0) { return false; }
// No data; keep waiting
continue;
}
return false;
}
// Utilitarian function for logging errors for the given operation
void HciSocket::logErrno(const char *pOperation) const
{
std::string errorDetail(strerror(errno));
if (errno == EAGAIN)
{
errorDetail += " or not enough permission for this operation";
}
Logger::error(SSTR << "Error on Bluetooth management socket during " << pOperation << " operation. Error code " << errno << ": " << errorDetail);
}
}; // namespace ggk