/* * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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(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(g_object_ref(aEndpoint.mService.get()))); mC1.reset(reinterpret_cast(g_object_ref(aEndpoint.mC1.get()))); mC2.reset(reinterpret_cast(g_object_ref(aEndpoint.mC2.get()))); return CHIP_NO_ERROR; } for (BluezObject & object : aEndpoint.mObjectManager.GetObjects()) { GAutoPtr 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 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 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(len), sizeof(uint8_t)); bluez_gatt_characteristic1_set_value(apConn->mC1.get(), newVal); BLEManagerImpl::HandleRXCharWrite(apConn, buf, static_cast(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(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(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 error; size_t len, written; if (bluez_gatt_characteristic1_get_notify_acquired(data->mConn.mC2.get()) == TRUE) { auto * buf = static_cast(g_variant_get_fixed_array(data->mData.get(), &len, sizeof(uint8_t))); VerifyOrExit(len <= static_cast(std::numeric_limits::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(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(aObject); GAutoPtr 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(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(&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 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(apConnection), static_cast(buffer), bufferLen); } void BluezConnection::SubscribeCharacteristicDone(GObject * aObject, GAsyncResult * aResult, gpointer apConnection) { auto * pC2 = reinterpret_cast(aObject); GAutoPtr 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(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(aObject); GAutoPtr 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(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