blob: d39411d18d6aef2bed9e64434b4b95b1b3231fd7 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BluezConnection.h"
#include <cstring>
#include <limits>
#include <memory>
#include <gio/gio.h>
#include <glib.h>
#include <ble/Ble.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <platform/ConnectivityManager.h>
#include <platform/GLibTypeDeleter.h>
#include <platform/Linux/dbus/bluez/DbusBluez.h>
#include <platform/PlatformManager.h>
#include <platform/internal/BLEManager.h>
#include <system/SystemPacketBuffer.h>
#include "BluezEndpoint.h"
#include "Types.h"
namespace chip {
namespace DeviceLayer {
namespace Internal {
namespace {
bool BluezIsServiceOnDevice(BluezGattService1 * aService, BluezDevice1 * aDevice)
{
auto servicePath = bluez_gatt_service1_get_device(aService);
auto devicePath = g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(aDevice));
return strcmp(servicePath, devicePath) == 0;
}
bool BluezIsCharOnService(BluezGattCharacteristic1 * aChar, BluezGattService1 * aService)
{
auto charPath = bluez_gatt_characteristic1_get_service(aChar);
auto servicePath = g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(aService));
return strcmp(charPath, servicePath) == 0;
}
bool BluezIsFlagOnChar(BluezGattCharacteristic1 * aChar, const char * flag)
{
auto charFlags = bluez_gatt_characteristic1_get_flags(aChar);
for (size_t i = 0; charFlags[i] != nullptr; i++)
if (strcmp(charFlags[i], flag) == 0)
return true;
return false;
}
} // namespace
BluezConnection::IOChannel::~IOChannel()
{
if (mWatchSource != nullptr)
// Make sure the source is detached before destroying the channel.
g_source_destroy(mWatchSource.get());
}
BluezConnection::ConnectionDataBundle::ConnectionDataBundle(const BluezConnection & aConn,
const chip::System::PacketBufferHandle & aBuf) :
mConn(aConn),
mData(g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, aBuf->Start(), aBuf->DataLength(), sizeof(uint8_t)))
{}
CHIP_ERROR BluezConnection::Init(const BluezEndpoint & aEndpoint)
{
if (!aEndpoint.mIsCentral)
{
mService.reset(reinterpret_cast<BluezGattService1 *>(g_object_ref(aEndpoint.mService.get())));
mC1.reset(reinterpret_cast<BluezGattCharacteristic1 *>(g_object_ref(aEndpoint.mC1.get())));
mC2.reset(reinterpret_cast<BluezGattCharacteristic1 *>(g_object_ref(aEndpoint.mC2.get())));
return CHIP_NO_ERROR;
}
for (BluezObject & object : aEndpoint.mObjectManager.GetObjects())
{
GAutoPtr<BluezGattService1> service(bluez_object_get_gatt_service1(&object));
if (service && BluezIsServiceOnDevice(service.get(), mDevice.get()))
{
if (strcmp(bluez_gatt_service1_get_uuid(service.get()), Ble::CHIP_BLE_SERVICE_LONG_UUID_STR) == 0)
{
ChipLogDetail(DeviceLayer, "CHIP service found");
mService.reset(service.release());
break;
}
}
}
VerifyOrReturnError(
mService, BLE_ERROR_NOT_CHIP_DEVICE,
ChipLogError(DeviceLayer, "CHIP service (%s) not found on %s", Ble::CHIP_BLE_SERVICE_LONG_UUID_STR, GetPeerAddress()));
for (BluezObject & object : aEndpoint.mObjectManager.GetObjects())
{
GAutoPtr<BluezGattCharacteristic1> chr(bluez_object_get_gatt_characteristic1(&object));
if (chr && BluezIsCharOnService(chr.get(), mService.get()))
{
if (strcmp(bluez_gatt_characteristic1_get_uuid(chr.get()), Ble::CHIP_BLE_CHAR_1_UUID_STR) == 0 &&
BluezIsFlagOnChar(chr.get(), "write"))
{
ChipLogDetail(DeviceLayer, "Valid C1 characteristic found");
mC1.reset(chr.release());
}
else if (strcmp(bluez_gatt_characteristic1_get_uuid(chr.get()), Ble::CHIP_BLE_CHAR_2_UUID_STR) == 0 &&
BluezIsFlagOnChar(chr.get(), "indicate"))
{
ChipLogDetail(DeviceLayer, "Valid C2 characteristic found");
mC2.reset(chr.release());
}
#if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING
else if (strcmp(bluez_gatt_characteristic1_get_uuid(chr.get()), Ble::CHIP_BLE_CHAR_3_UUID_STR) == 0 &&
BluezIsFlagOnChar(chr.get(), "read"))
{
ChipLogDetail(DeviceLayer, "Valid C3 characteristic found");
mC3.reset(chr.release());
}
#endif
}
}
VerifyOrReturnError(mC1, BLE_ERROR_NOT_CHIP_DEVICE,
ChipLogError(DeviceLayer, "No valid C1 (%s) on %s", Ble::CHIP_BLE_CHAR_1_UUID_STR, GetPeerAddress()));
VerifyOrReturnError(mC2, BLE_ERROR_NOT_CHIP_DEVICE,
ChipLogError(DeviceLayer, "No valid C2 (%s) on %s", Ble::CHIP_BLE_CHAR_2_UUID_STR, GetPeerAddress()));
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::CloseConnectionImpl(BluezConnection * conn)
{
GAutoPtr<GError> error;
gboolean success;
ChipLogDetail(DeviceLayer, "Close BLE connection: peer=%s", conn->GetPeerAddress());
success = bluez_device1_call_disconnect_sync(conn->mDevice.get(), nullptr, &error.GetReceiver());
VerifyOrExit(success == TRUE, ChipLogError(DeviceLayer, "FAIL: Disconnect: %s", error->message));
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::CloseConnection()
{
return PlatformMgrImpl().GLibMatterContextInvokeSync(CloseConnectionImpl, this);
}
const char * BluezConnection::GetPeerAddress() const
{
return bluez_device1_get_address(mDevice.get());
}
gboolean BluezConnection::WriteHandlerCallback(GIOChannel * aChannel, GIOCondition aCond, BluezConnection * apConn)
{
uint8_t buf[512 /* characteristic max size per BLE specification */];
bool isSuccess = false;
GVariant * newVal;
ssize_t len;
VerifyOrExit(!(aCond & G_IO_HUP), ChipLogError(DeviceLayer, "INFO: socket disconnected in %s", __func__));
VerifyOrExit(!(aCond & (G_IO_ERR | G_IO_NVAL)), ChipLogError(DeviceLayer, "INFO: socket error in %s", __func__));
VerifyOrExit(aCond == G_IO_IN, ChipLogError(DeviceLayer, "FAIL: error in %s", __func__));
ChipLogDetail(DeviceLayer, "C1 %s MTU: %d", __func__, apConn->GetMTU());
len = read(g_io_channel_unix_get_fd(aChannel), buf, sizeof(buf));
VerifyOrExit(len > 0, ChipLogError(DeviceLayer, "FAIL: short read in %s (%zd)", __func__, len));
// Casting len to size_t is safe, since we ensured that it's not negative.
newVal = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, buf, static_cast<size_t>(len), sizeof(uint8_t));
bluez_gatt_characteristic1_set_value(apConn->mC1.get(), newVal);
BLEManagerImpl::HandleRXCharWrite(apConn, buf, static_cast<size_t>(len));
isSuccess = true;
exit:
return isSuccess ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}
void BluezConnection::SetupWriteHandler(int aSocketFd)
{
auto channel = g_io_channel_unix_new(aSocketFd);
g_io_channel_set_encoding(channel, nullptr, nullptr);
g_io_channel_set_close_on_unref(channel, TRUE);
g_io_channel_set_buffered(channel, FALSE);
auto watchSource = g_io_create_watch(channel, static_cast<GIOCondition>(G_IO_HUP | G_IO_IN | G_IO_ERR | G_IO_NVAL));
g_source_set_callback(watchSource, G_SOURCE_FUNC(WriteHandlerCallback), this, nullptr);
mC1Channel.mChannel.reset(channel);
mC1Channel.mWatchSource.reset(watchSource);
PlatformMgrImpl().GLibMatterContextAttachSource(watchSource);
}
gboolean BluezConnection::NotifyHandlerCallback(GIOChannel *, GIOCondition, BluezConnection *)
{
return G_SOURCE_REMOVE;
}
void BluezConnection::SetupNotifyHandler(int aSocketFd, bool aAdditionalAdvertising)
{
auto channel = g_io_channel_unix_new(aSocketFd);
g_io_channel_set_encoding(channel, nullptr, nullptr);
g_io_channel_set_close_on_unref(channel, TRUE);
g_io_channel_set_buffered(channel, FALSE);
auto watchSource = g_io_create_watch(channel, static_cast<GIOCondition>(G_IO_HUP | G_IO_ERR | G_IO_NVAL));
g_source_set_callback(watchSource, G_SOURCE_FUNC(NotifyHandlerCallback), this, nullptr);
#if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING
if (aAdditionalAdvertising)
{
mC3Channel.mChannel.reset(channel);
mC3Channel.mWatchSource.reset(watchSource);
}
else
#endif
{
mC2Channel.mChannel.reset(channel);
mC2Channel.mWatchSource.reset(watchSource);
}
PlatformMgrImpl().GLibMatterContextAttachSource(watchSource);
}
// SendIndication callbacks
CHIP_ERROR BluezConnection::SendIndicationImpl(ConnectionDataBundle * data)
{
GAutoPtr<GError> error;
size_t len, written;
if (bluez_gatt_characteristic1_get_notify_acquired(data->mConn.mC2.get()) == TRUE)
{
auto * buf = static_cast<const char *>(g_variant_get_fixed_array(data->mData.get(), &len, sizeof(uint8_t)));
VerifyOrExit(len <= static_cast<size_t>(std::numeric_limits<gssize>::max()),
ChipLogError(DeviceLayer, "FAIL: buffer too large in %s", __func__));
auto status = g_io_channel_write_chars(data->mConn.mC2Channel.mChannel.get(), buf, static_cast<gssize>(len), &written,
&error.GetReceiver());
VerifyOrExit(status == G_IO_STATUS_NORMAL, ChipLogError(DeviceLayer, "FAIL: C2 Indicate: %s", error->message));
}
else
{
bluez_gatt_characteristic1_set_value(data->mConn.mC2.get(), data->mData.release());
}
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::SendIndication(chip::System::PacketBufferHandle apBuf)
{
VerifyOrReturnError(!apBuf.IsNull(), CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(DeviceLayer, "apBuf is NULL in %s", __func__));
VerifyOrReturnError(mC2, CHIP_ERROR_INTERNAL, ChipLogError(DeviceLayer, "C2 is NULL in %s", __func__));
ConnectionDataBundle bundle(*this, apBuf);
return PlatformMgrImpl().GLibMatterContextInvokeSync(SendIndicationImpl, &bundle);
}
// SendWriteRequest callbacks
void BluezConnection::SendWriteRequestDone(GObject * aObject, GAsyncResult * aResult, gpointer apConnection)
{
auto * pC1 = reinterpret_cast<BluezGattCharacteristic1 *>(aObject);
GAutoPtr<GError> error;
gboolean success = bluez_gatt_characteristic1_call_write_value_finish(pC1, aResult, &error.GetReceiver());
VerifyOrReturn(success == TRUE, ChipLogError(DeviceLayer, "FAIL: SendWriteRequest : %s", error->message));
BLEManagerImpl::HandleWriteComplete(static_cast<BluezConnection *>(apConnection));
}
CHIP_ERROR BluezConnection::SendWriteRequestImpl(ConnectionDataBundle * data)
{
GVariantBuilder optionsBuilder;
g_variant_builder_init(&optionsBuilder, G_VARIANT_TYPE_ARRAY);
g_variant_builder_add(&optionsBuilder, "{sv}", "type", g_variant_new_string("request"));
auto options = g_variant_builder_end(&optionsBuilder);
bluez_gatt_characteristic1_call_write_value(data->mConn.mC1.get(), data->mData.release(), options, nullptr,
SendWriteRequestDone, const_cast<BluezConnection *>(&data->mConn));
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::SendWriteRequest(chip::System::PacketBufferHandle apBuf)
{
VerifyOrReturnError(!apBuf.IsNull(), CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(DeviceLayer, "apBuf is NULL in %s", __func__));
VerifyOrReturnError(mC1, CHIP_ERROR_INTERNAL, ChipLogError(DeviceLayer, "C1 is NULL in %s", __func__));
ConnectionDataBundle bundle(*this, apBuf);
return PlatformMgrImpl().GLibMatterContextInvokeSync(SendWriteRequestImpl, &bundle);
}
// SubscribeCharacteristic callbacks
void BluezConnection::OnCharacteristicChanged(GDBusProxy * aInterface, GVariant * aChangedProperties,
const gchar * const * aInvalidatedProps, gpointer apConnection)
{
GAutoPtr<GVariant> dataValue(g_variant_lookup_value(aChangedProperties, "Value", G_VARIANT_TYPE_BYTESTRING));
VerifyOrReturn(dataValue != nullptr);
size_t bufferLen;
auto buffer = g_variant_get_fixed_array(dataValue.get(), &bufferLen, sizeof(uint8_t));
VerifyOrReturn(buffer != nullptr, ChipLogError(DeviceLayer, "Characteristic value has unexpected type"));
BLEManagerImpl::HandleTXCharChanged(static_cast<BluezConnection *>(apConnection), static_cast<const uint8_t *>(buffer),
bufferLen);
}
void BluezConnection::SubscribeCharacteristicDone(GObject * aObject, GAsyncResult * aResult, gpointer apConnection)
{
auto * pC2 = reinterpret_cast<BluezGattCharacteristic1 *>(aObject);
GAutoPtr<GError> error;
gboolean success = bluez_gatt_characteristic1_call_start_notify_finish(pC2, aResult, &error.GetReceiver());
VerifyOrReturn(success == TRUE, ChipLogError(DeviceLayer, "FAIL: SubscribeCharacteristic : %s", error->message));
BLEManagerImpl::HandleSubscribeOpComplete(static_cast<BluezConnection *>(apConnection), true);
}
CHIP_ERROR BluezConnection::SubscribeCharacteristicImpl(BluezConnection * connection)
{
BluezGattCharacteristic1 * pC2 = connection->mC2.get();
VerifyOrExit(pC2 != nullptr, ChipLogError(DeviceLayer, "C2 is NULL in %s", __func__));
// Get notifications on the TX characteristic change (e.g. indication is received)
g_signal_connect(pC2, "g-properties-changed", G_CALLBACK(OnCharacteristicChanged), connection);
bluez_gatt_characteristic1_call_start_notify(pC2, nullptr, SubscribeCharacteristicDone, connection);
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::SubscribeCharacteristic()
{
return PlatformMgrImpl().GLibMatterContextInvokeSync(SubscribeCharacteristicImpl, this);
}
// UnsubscribeCharacteristic callbacks
void BluezConnection::UnsubscribeCharacteristicDone(GObject * aObject, GAsyncResult * aResult, gpointer apConnection)
{
auto * pC2 = reinterpret_cast<BluezGattCharacteristic1 *>(aObject);
GAutoPtr<GError> error;
gboolean success = bluez_gatt_characteristic1_call_stop_notify_finish(pC2, aResult, &error.GetReceiver());
VerifyOrReturn(success == TRUE, ChipLogError(DeviceLayer, "FAIL: UnsubscribeCharacteristic : %s", error->message));
// Stop listening to the TX characteristic changes
g_signal_handlers_disconnect_by_data(pC2, apConnection);
BLEManagerImpl::HandleSubscribeOpComplete(static_cast<BluezConnection *>(apConnection), false);
}
CHIP_ERROR BluezConnection::UnsubscribeCharacteristicImpl(BluezConnection * connection)
{
BluezGattCharacteristic1 * pC2 = connection->mC2.get();
VerifyOrExit(pC2 != nullptr, ChipLogError(DeviceLayer, "C2 is NULL in %s", __func__));
bluez_gatt_characteristic1_call_stop_notify(pC2, nullptr, UnsubscribeCharacteristicDone, connection);
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezConnection::UnsubscribeCharacteristic()
{
return PlatformMgrImpl().GLibMatterContextInvokeSync(UnsubscribeCharacteristicImpl, this);
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip