/* * * Copyright (c) 2020-2021 Project CHIP Authors * Copyright (c) 2014-2017 Nest Labs, Inc. * * 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. */ /** * @file * This file implements a Bluetooth Low Energy (BLE) connection * endpoint abstraction for the byte-streaming, * connection-oriented CHIP over Bluetooth Low Energy (CHIPoBLE) * Bluetooth Transport Protocol (BTP). * */ #define _CHIP_BLE_BLE_H #include "BLEEndPoint.h" #include #include #include #include #include #include #include #include #include #include "BleApplicationDelegate.h" #include "BleConfig.h" #include "BleError.h" #include "BleLayer.h" #include "BleLayerDelegate.h" #include "BlePlatformDelegate.h" #include "BleRole.h" #include "BleUUID.h" #include "BtpEngine.h" // Define below to enable extremely verbose, BLE end point-specific debug logging. #undef CHIP_BLE_END_POINT_DEBUG_LOGGING_ENABLED #ifdef CHIP_BLE_END_POINT_DEBUG_LOGGING_ENABLED #define ChipLogDebugBleEndPoint(MOD, MSG, ...) ChipLogDetail(MOD, MSG, ##__VA_ARGS__) #else #define ChipLogDebugBleEndPoint(MOD, MSG, ...) #endif /** * @def BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD * * @brief * If an end point's receive window drops equal to or below this value, it will send an immediate acknowledgement * packet to re-open its window instead of waiting for the send-ack timer to expire. * */ #define BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD 1 /** * @def BLE_UNSUBSCRIBE_TIMEOUT_MS * * @brief * This is amount of time, in milliseconds, which a BLE end point will wait for an unsubscribe operation to complete * before it automatically releases its BLE connection and frees itself. The default value of 5 seconds is arbitrary. * */ #define BLE_UNSUBSCRIBE_TIMEOUT_MS 5000 #define BTP_ACK_SEND_TIMEOUT_MS 2500 /** * @def BTP_WINDOW_NO_ACK_SEND_THRESHOLD * * @brief * Data fragments may only be sent without piggybacked acks if receiver's window size is above this threshold. * */ #define BTP_WINDOW_NO_ACK_SEND_THRESHOLD 1 namespace chip { namespace Ble { CHIP_ERROR BLEEndPoint::StartConnect() { CHIP_ERROR err = CHIP_NO_ERROR; BleTransportCapabilitiesRequestMessage req; PacketBufferHandle buf; constexpr uint8_t numVersions = CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION + 1; static_assert(numVersions <= NUM_SUPPORTED_PROTOCOL_VERSIONS, "Incompatibly protocol versions"); // Ensure we're in the correct state. VerifyOrExit(mState == kState_Ready, err = CHIP_ERROR_INCORRECT_STATE); mState = kState_Connecting; // Build BLE transport protocol capabilities request. buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); // Zero-initialize BLE transport capabilities request. memset(&req, 0, sizeof(req)); req.mMtu = mBle->mPlatformDelegate->GetMTU(mConnObj); req.mWindowSize = BLE_MAX_RECEIVE_WINDOW_SIZE; // Populate request with highest supported protocol versions for (uint8_t i = 0; i < numVersions; i++) { req.SetSupportedProtocolVersion(i, static_cast(CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - i)); } err = req.Encode(buf); SuccessOrExit(err); // Start connect timer. Canceled when end point freed or connection established. err = StartConnectTimer(); SuccessOrExit(err); // Send BLE transport capabilities request to peripheral via GATT write. // Add reference to message fragment for duration of platform's GATT write attempt. CHIP retains partial // ownership of message fragment's packet buffer, since this is the same buffer as that of the whole message, just // with a fragmenter-modified payload offset and data length, by a Retain() on the handle when calling this function. err = SendWrite(buf.Retain()); SuccessOrExit(err); // Free request buffer on write confirmation. Stash a reference to it in mSendQueue, which we don't use anyway // until the connection has been set up. QueueTx(std::move(buf), kType_Data); exit: // If we failed to initiate the connection, close the end point. if (err != CHIP_NO_ERROR) { StopConnectTimer(); DoClose(kBleCloseFlag_AbortTransmission, err); } return err; } CHIP_ERROR BLEEndPoint::HandleConnectComplete() { CHIP_ERROR err = CHIP_NO_ERROR; mState = kState_Connected; // Cancel the connect timer. StopConnectTimer(); // We've successfully completed the BLE transport protocol handshake, so let the application know we're open for business. if (mBleTransport != nullptr) { // Indicate connect complete to next-higher layer. mBleTransport->OnEndPointConnectComplete(this, CHIP_NO_ERROR); } else { // If no connect complete callback has been set up, close the end point. err = BLE_ERROR_NO_CONNECT_COMPLETE_CALLBACK; } return err; } CHIP_ERROR BLEEndPoint::HandleReceiveConnectionComplete() { CHIP_ERROR err = CHIP_NO_ERROR; ChipLogDebugBleEndPoint(Ble, "entered HandleReceiveConnectionComplete"); mState = kState_Connected; // Cancel receive connection timer. StopReceiveConnectionTimer(); // We've successfully completed the BLE transport protocol handshake, so let the transport know we're open for business. if (mBleTransport != nullptr) { // Indicate BLE transport protocol connection received to next-higher layer. err = mBleTransport->SetEndPoint(this); } else { err = BLE_ERROR_NO_CONNECTION_RECEIVED_CALLBACK; } return err; } void BLEEndPoint::HandleSubscribeReceived() { CHIP_ERROR err = CHIP_NO_ERROR; VerifyOrExit(mState == kState_Connecting || mState == kState_Aborting, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(!mSendQueue.IsNull(), err = CHIP_ERROR_INCORRECT_STATE); // Send BTP capabilities response to peripheral via GATT indication. // Add reference to message fragment for duration of platform's GATT indication attempt. CHIP retains partial // ownership of message fragment's packet buffer, since this is the same buffer as that of the whole message, just // with a fragmenter-modified payload offset and data length. err = SendIndication(mSendQueue.Retain()); if (err != CHIP_NO_ERROR) { // Ensure transmit queue is empty and set to NULL. mSendQueue = nullptr; ChipLogError(Ble, "cap resp ind failed"); ExitNow(); } // Shrink remote receive window counter by 1, since we've sent an indication which requires acknowledgement. mRemoteReceiveWindowSize = static_cast(mRemoteReceiveWindowSize - 1); ChipLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); // Start ack recvd timer for handshake indication. err = StartAckReceivedTimer(); SuccessOrExit(err); ChipLogDebugBleEndPoint(Ble, "got subscribe, sent indication w/ capabilities response"); // If SendIndication returns true, mSendQueue is freed on indication confirmation, or on close in case of // connection error. if (mState != kState_Aborting) { // If peripheral accepted the BTP connection, its end point must enter the connected state here, i.e. before it // receives a GATT confirmation for the capabilities response indication. This behavior is required to handle the // case where a peripheral's BLE controller passes up the central's first message fragment write before the // capabilities response indication confirmation. If the end point waited for this indication confirmation before // it entered the connected state, it'd be in the wrong state to receive the central's first data write, and drop // the corresponding message fragment. err = HandleReceiveConnectionComplete(); SuccessOrExit(err); } // Else State == kState_Aborting, so we'll close end point when indication confirmation received. exit: if (err != CHIP_NO_ERROR) { DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, err); } } void BLEEndPoint::HandleSubscribeComplete() { ChipLogProgress(Ble, "subscribe complete, ep = %p", this); mConnStateFlags.Clear(ConnectionStateFlag::kGattOperationInFlight); CHIP_ERROR err = DriveSending(); if (err != CHIP_NO_ERROR) { DoClose(kBleCloseFlag_AbortTransmission, CHIP_NO_ERROR); } } void BLEEndPoint::HandleUnsubscribeComplete() { // Don't bother to clear GattOperationInFlight, we're about to free the end point anyway. Free(); } bool BLEEndPoint::IsConnected(uint8_t state) const { return (state == kState_Connected || state == kState_Closing); } bool BLEEndPoint::IsUnsubscribePending() const { return mTimerStateFlags.Has(TimerStateFlag::kUnsubscribeTimerRunning); } void BLEEndPoint::Abort() { // No more callbacks after this point, since application explicitly called Abort(). OnConnectComplete = nullptr; OnConnectionClosed = nullptr; OnMessageReceived = nullptr; DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, CHIP_NO_ERROR); } void BLEEndPoint::Close() { // No more callbacks after this point, since application explicitly called Close(). OnConnectComplete = nullptr; OnConnectionClosed = nullptr; OnMessageReceived = nullptr; DoClose(kBleCloseFlag_SuppressCallback, CHIP_NO_ERROR); } void BLEEndPoint::DoClose(uint8_t flags, CHIP_ERROR err) { uint8_t oldState = mState; // If end point is not closed or closing, OR end point was closing gracefully, but tx abort has been specified... if ((mState != kState_Closed && mState != kState_Closing) || (mState == kState_Closing && (flags & kBleCloseFlag_AbortTransmission))) { // Cancel Connect and ReceiveConnect timers if they are running. // Check role first to avoid needless iteration over timer pool. if (mRole == kBleRole_Central) { StopConnectTimer(); } else // (mRole == kBleRole_Peripheral), verified on Init { StopReceiveConnectionTimer(); } // If transmit buffer is empty or a transmission abort was specified... if (mBtpEngine.TxState() == BtpEngine::kState_Idle || (flags & kBleCloseFlag_AbortTransmission)) { FinalizeClose(oldState, flags, err); } else { // Wait for send queue and fragmenter's tx buffer to become empty, to ensure all pending messages have been // sent. Only free end point and tell platform it can throw away the underlying BLE connection once all // pending messages have been sent and acknowledged by the remote CHIPoBLE stack, or once the remote stack // closes the CHIPoBLE connection. // // In so doing, BLEEndPoint attempts to emulate the level of reliability afforded by TCPEndPoint and TCP // sockets in general with a typical default SO_LINGER option. That said, there is no hard guarantee that // pending messages will be sent once (Do)Close() is called, so developers should use application-level // messages to confirm the receipt of all data sent prior to a Close() call. mState = kState_Closing; if ((flags & kBleCloseFlag_SuppressCallback) == 0) { DoCloseCallback(oldState, flags, err); } if (mBleTransport != nullptr && (flags & kBleCloseFlag_SuppressCallback) != 0) { mBleTransport->OnEndPointConnectionClosed(this, err); } } } } void BLEEndPoint::FinalizeClose(uint8_t oldState, uint8_t flags, CHIP_ERROR err) { mState = kState_Closed; // Ensure transmit queue is empty and set to NULL. mSendQueue = nullptr; // Fire application's close callback if we haven't already, and it's not suppressed. if (oldState != kState_Closing && (flags & kBleCloseFlag_SuppressCallback) == 0) { DoCloseCallback(oldState, flags, err); } if (mBleTransport != nullptr && (flags & kBleCloseFlag_SuppressCallback) != 0) { mBleTransport->OnEndPointConnectionClosed(this, err); } // If underlying BLE connection has closed, connection object is invalid, so just free the end point and return. if (err == BLE_ERROR_REMOTE_DEVICE_DISCONNECTED || err == BLE_ERROR_APP_CLOSED_CONNECTION) { mConnObj = BLE_CONNECTION_UNINITIALIZED; // Clear handle to BLE connection, so we don't double-close it. Free(); } else // Otherwise, try to signal close to remote device before end point releases BLE connection and frees itself. { if (mRole == kBleRole_Central && mConnStateFlags.Has(ConnectionStateFlag::kDidBeginSubscribe)) { // Cancel send and receive-ack timers, if running. StopAckReceivedTimer(); StopSendAckTimer(); // Indicate close of chipConnection to peripheral via GATT unsubscribe. Keep end point allocated until // unsubscribe completes or times out, so platform doesn't close underlying BLE connection before // we're really sure the unsubscribe request has been sent. err = mBle->mPlatformDelegate->UnsubscribeCharacteristic(mConnObj, &CHIP_BLE_SVC_ID, &CHIP_BLE_CHAR_2_UUID); if (err != CHIP_NO_ERROR) { ChipLogError(Ble, "BtpEngine unsubscribe failed %" CHIP_ERROR_FORMAT, err.Format()); // If unsubscribe fails, release BLE connection and free end point immediately. Free(); } else if (mConnObj != BLE_CONNECTION_UNINITIALIZED) { // Unsubscribe request was sent successfully, and a confirmation wasn't spontaneously generated or // received in the downcall to UnsubscribeCharacteristic, so set timer for the unsubscribe to complete. err = StartUnsubscribeTimer(); if (err != CHIP_NO_ERROR) { Free(); } // Mark unsubscribe GATT operation in progress. mConnStateFlags.Set(ConnectionStateFlag::kGattOperationInFlight); } } else // mRole == kBleRole_Peripheral, OR mTimerStateFlags.Has(ConnectionStateFlag::kDidBeginSubscribe) == false... { Free(); } } } void BLEEndPoint::DoCloseCallback(uint8_t state, uint8_t flags, CHIP_ERROR err) { if (state == kState_Connecting) { if (mBleTransport != nullptr) { mBleTransport->OnEndPointConnectComplete(this, err); } } else { if (mBleTransport != nullptr) { mBleTransport->OnEndPointConnectionClosed(this, err); } } // Callback fires once per end point lifetime. OnConnectComplete = nullptr; OnConnectionClosed = nullptr; } void BLEEndPoint::ReleaseBleConnection() { if (mConnObj != BLE_CONNECTION_UNINITIALIZED) { if (mConnStateFlags.Has(ConnectionStateFlag::kAutoClose)) { ChipLogProgress(Ble, "Auto-closing end point's BLE connection."); mBle->mPlatformDelegate->CloseConnection(mConnObj); } else { ChipLogProgress(Ble, "Releasing end point's BLE connection back to application."); mBle->mApplicationDelegate->NotifyChipConnectionClosed(mConnObj); } // Never release the same BLE connection twice. mConnObj = BLE_CONNECTION_UNINITIALIZED; } } void BLEEndPoint::Free() { // Release BLE connection. Will close connection if AutoClose enabled for this end point. Otherwise, informs // application that CHIP is done with this BLE connection, and application makes decision about whether to close // and clean up or retain connection. ReleaseBleConnection(); // Clear fragmentation and reassembly engine's Tx and Rx buffers. Counters will be reset by next engine init. FreeBtpEngine(); // Clear pending ack buffer, if any. mAckToSend = nullptr; // Cancel all timers. StopConnectTimer(); StopReceiveConnectionTimer(); StopAckReceivedTimer(); StopSendAckTimer(); StopUnsubscribeTimer(); // Clear callbacks. OnConnectComplete = nullptr; OnMessageReceived = nullptr; OnConnectionClosed = nullptr; // Clear handle to underlying BLE connection. mConnObj = BLE_CONNECTION_UNINITIALIZED; // Release the AddRef() that happened when the end point was allocated. Release(); } void BLEEndPoint::FreeBtpEngine() { // Free transmit disassembly buffer mBtpEngine.ClearTxPacket(); // Free receive reassembly buffer mBtpEngine.ClearRxPacket(); } CHIP_ERROR BLEEndPoint::Init(BleLayer * bleLayer, BLE_CONNECTION_OBJECT connObj, BleRole role, bool autoClose) { // Fail if already initialized. VerifyOrReturnError(mBle == nullptr, CHIP_ERROR_INCORRECT_STATE); // Validate args. VerifyOrReturnError(bleLayer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(connObj != BLE_CONNECTION_UNINITIALIZED, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError((role == kBleRole_Central || role == kBleRole_Peripheral), CHIP_ERROR_INVALID_ARGUMENT); // If end point plays peripheral role, expect ack for indication sent as last step of BTP handshake. // If central, peripheral's handshake indication 'ack's write sent by central to kick off the BTP handshake. bool expectInitialAck = (role == kBleRole_Peripheral); CHIP_ERROR err = mBtpEngine.Init(this, expectInitialAck); if (err != CHIP_NO_ERROR) { ChipLogError(Ble, "BtpEngine init failed"); return err; } mBle = bleLayer; mRefCount = 1; // BLEEndPoint data members: mConnObj = connObj; mRole = role; mTimerStateFlags.ClearAll(); mConnStateFlags.ClearAll().Set(ConnectionStateFlag::kAutoClose, autoClose); mLocalReceiveWindowSize = 0; mRemoteReceiveWindowSize = 0; mReceiveWindowMaxSize = 0; mSendQueue = nullptr; mAckToSend = nullptr; ChipLogDebugBleEndPoint(Ble, "initialized local rx window, size = %u", mLocalReceiveWindowSize); // End point is ready to connect or receive a connection. mState = kState_Ready; return CHIP_NO_ERROR; } void BLEEndPoint::AddRef() { VerifyOrDie(mRefCount < UINT32_MAX); mRefCount++; } void BLEEndPoint::Release() { VerifyOrDie(mRefCount > 0u); // Decrement the ref count. When it reaches zero, NULL out the pointer to the chip::System::Layer // object. This effectively declared the object free and ready for re-allocation. mRefCount--; if (mRefCount == 0) { mBle = nullptr; } } CHIP_ERROR BLEEndPoint::SendCharacteristic(PacketBufferHandle && buf) { CHIP_ERROR err = CHIP_NO_ERROR; if (mRole == kBleRole_Central) { SuccessOrExit(err = SendWrite(std::move(buf))); // Write succeeded, so shrink remote receive window counter by 1. mRemoteReceiveWindowSize = static_cast(mRemoteReceiveWindowSize - 1); ChipLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); } else // (mRole == kBleRole_Peripheral), verified on Init { SuccessOrExit(err = SendIndication(std::move(buf))); // Indication succeeded, so shrink remote receive window counter by 1. mRemoteReceiveWindowSize = static_cast(mRemoteReceiveWindowSize - 1); ChipLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); } exit: return err; } /* * Routine to queue the Tx packet with a packet type * kType_Data(0) - data packet * kType_Control(1) - control packet */ void BLEEndPoint::QueueTx(PacketBufferHandle && data, PacketType_t type) { if (mSendQueue.IsNull()) { mSendQueue = std::move(data); ChipLogDebugBleEndPoint(Ble, "%s: Set data as new mSendQueue %p, type %d", __FUNCTION__, mSendQueue->Start(), type); } else { mSendQueue->AddToEnd(std::move(data)); ChipLogDebugBleEndPoint(Ble, "%s: Append data to mSendQueue %p, type %d", __FUNCTION__, mSendQueue->Start(), type); } } CHIP_ERROR BLEEndPoint::Send(PacketBufferHandle && data) { ChipLogDebugBleEndPoint(Ble, "entered Send"); CHIP_ERROR err = CHIP_NO_ERROR; VerifyOrExit(!data.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT); VerifyOrExit(IsConnected(mState), err = CHIP_ERROR_INCORRECT_STATE); // Ensure outgoing message fits in a single contiguous packet buffer, as currently required by the // message fragmentation and reassembly engine. if (data->HasChainedBuffer()) { data->CompactHead(); if (data->HasChainedBuffer()) { err = CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG; ExitNow(); } } // Add new message to send queue. QueueTx(std::move(data), kType_Data); // Send first fragment of new message, if we can. err = DriveSending(); SuccessOrExit(err); exit: ChipLogDebugBleEndPoint(Ble, "exiting Send"); if (err != CHIP_NO_ERROR) { DoClose(kBleCloseFlag_AbortTransmission, err); } return err; } bool BLEEndPoint::PrepareNextFragment(PacketBufferHandle && data, bool & sentAck) { // If we have a pending fragment acknowledgement to send, piggyback it on the fragment we're about to transmit. if (mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning)) { // Reset local receive window counter. mLocalReceiveWindowSize = mReceiveWindowMaxSize; ChipLogDebugBleEndPoint(Ble, "reset local rx window on piggyback ack tx, size = %u", mLocalReceiveWindowSize); // Tell caller AND fragmenter we have an ack to piggyback. sentAck = true; } else { // No ack to piggyback. sentAck = false; } return mBtpEngine.HandleCharacteristicSend(std::move(data), sentAck); } CHIP_ERROR BLEEndPoint::SendNextMessage() { // Get the first queued packet to send PacketBufferHandle data = mSendQueue.PopHead(); // Hand whole message payload to the fragmenter. bool sentAck; VerifyOrReturnError(PrepareNextFragment(std::move(data), sentAck), BLE_ERROR_CHIPOBLE_PROTOCOL_ABORT); /* // Todo: reenabled it after integrating fault injection // Send first message fragment over the air. CHIP_FAULT_INJECT(chip::FaultInjection::kFault_CHIPOBLESend, { if (mRole == kBleRole_Central) { err = BLE_ERROR_GATT_WRITE_FAILED; } else { err = BLE_ERROR_GATT_INDICATE_FAILED; } ExitNow(); }); */ ReturnErrorOnFailure(SendCharacteristic(mBtpEngine.BorrowTxPacket())); if (sentAck) { // If sent piggybacked ack, stop send-ack timer. StopSendAckTimer(); } // Start ack received timer, if it's not already running. return StartAckReceivedTimer(); } CHIP_ERROR BLEEndPoint::ContinueMessageSend() { bool sentAck; if (!PrepareNextFragment(nullptr, sentAck)) { // Log BTP error ChipLogError(Ble, "btp fragmenter error on send!"); mBtpEngine.LogState(); return BLE_ERROR_CHIPOBLE_PROTOCOL_ABORT; } ReturnErrorOnFailure(SendCharacteristic(mBtpEngine.BorrowTxPacket())); if (sentAck) { // If sent piggybacked ack, stop send-ack timer. StopSendAckTimer(); } // Start ack received timer, if it's not already running. return StartAckReceivedTimer(); } CHIP_ERROR BLEEndPoint::HandleHandshakeConfirmationReceived() { ChipLogDebugBleEndPoint(Ble, "entered HandleHandshakeConfirmationReceived"); CHIP_ERROR err = CHIP_NO_ERROR; uint8_t closeFlags = kBleCloseFlag_AbortTransmission; // Free capabilities request/response payload. mSendQueue.FreeHead(); if (mRole == kBleRole_Central) { // Subscribe to characteristic which peripheral will use to send indications. Prompts peripheral to send // BLE transport capabilities indication. err = mBle->mPlatformDelegate->SubscribeCharacteristic(mConnObj, &CHIP_BLE_SVC_ID, &CHIP_BLE_CHAR_2_UUID); SuccessOrExit(err); // We just sent a GATT subscribe request, so make sure to attempt unsubscribe on close. mConnStateFlags.Set(ConnectionStateFlag::kDidBeginSubscribe); // Mark GATT operation in progress for subscribe request. mConnStateFlags.Set(ConnectionStateFlag::kGattOperationInFlight); } else // (mRole == kBleRole_Peripheral), verified on Init { ChipLogDebugBleEndPoint(Ble, "got peripheral handshake indication confirmation"); if (mState == kState_Connected) // If we accepted BTP connection... { // If local receive window size has shrunk to or below immediate ack threshold, AND a message fragment is not // pending on which to piggyback an ack, send immediate stand-alone ack. if (mLocalReceiveWindowSize <= BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && mSendQueue.IsNull()) { err = DriveStandAloneAck(); // Encode stand-alone ack and drive sending. SuccessOrExit(err); } else { // Drive sending in case application called Send() after we sent the handshake indication, but // before the GATT confirmation for this indication was received. err = DriveSending(); SuccessOrExit(err); } } else if (mState == kState_Aborting) // Else, if we rejected BTP connection... { closeFlags |= kBleCloseFlag_SuppressCallback; err = BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS; ExitNow(); } } exit: ChipLogDebugBleEndPoint(Ble, "exiting HandleHandshakeConfirmationReceived"); if (err != CHIP_NO_ERROR) { DoClose(closeFlags, err); } return err; } CHIP_ERROR BLEEndPoint::HandleFragmentConfirmationReceived() { CHIP_ERROR err = CHIP_NO_ERROR; ChipLogDebugBleEndPoint(Ble, "entered HandleFragmentConfirmationReceived"); // Suppress error logging if GATT confirmation overlaps with unsubscribe on final close. if (IsUnsubscribePending()) { ChipLogDebugBleEndPoint(Ble, "send conf rx'd while unsubscribe in flight"); ExitNow(); } // Ensure we're in correct state to receive confirmation of non-handshake GATT send. VerifyOrExit(IsConnected(mState), err = CHIP_ERROR_INCORRECT_STATE); if (mConnStateFlags.Has(ConnectionStateFlag::kStandAloneAckInFlight)) { // If confirmation was received for stand-alone ack, free its tx buffer. mAckToSend = nullptr; mConnStateFlags.Clear(ConnectionStateFlag::kStandAloneAckInFlight); } // If local receive window size has shrunk to or below immediate ack threshold, AND a message fragment is not // pending on which to piggyback an ack, send immediate stand-alone ack. // // This check covers the case where the local receive window has shrunk between transmission and confirmation of // the stand-alone ack, and also the case where a window size < the immediate ack threshold was detected in // Receive(), but the stand-alone ack was deferred due to a pending outbound message fragment. if (mLocalReceiveWindowSize <= BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && mSendQueue.IsNull() && mBtpEngine.TxState() != BtpEngine::kState_InProgress) { err = DriveStandAloneAck(); // Encode stand-alone ack and drive sending. SuccessOrExit(err); } else { err = DriveSending(); SuccessOrExit(err); } exit: if (err != CHIP_NO_ERROR) { DoClose(kBleCloseFlag_AbortTransmission, err); } return err; } CHIP_ERROR BLEEndPoint::HandleGattSendConfirmationReceived() { ChipLogDebugBleEndPoint(Ble, "entered HandleGattSendConfirmationReceived"); // Mark outstanding GATT operation as finished. mConnStateFlags.Clear(ConnectionStateFlag::kGattOperationInFlight); // If confirmation was for outbound portion of BTP connect handshake... if (!mConnStateFlags.Has(ConnectionStateFlag::kCapabilitiesConfReceived)) { mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesConfReceived); return HandleHandshakeConfirmationReceived(); } return HandleFragmentConfirmationReceived(); } CHIP_ERROR BLEEndPoint::DriveStandAloneAck() { // Stop send-ack timer if running. StopSendAckTimer(); // If stand-alone ack not already pending, allocate new payload buffer here. if (mAckToSend.IsNull()) { mAckToSend = System::PacketBufferHandle::New(kTransferProtocolStandaloneAckHeaderSize); VerifyOrReturnError(!mAckToSend.IsNull(), CHIP_ERROR_NO_MEMORY); } // Attempt to send stand-alone ack. return DriveSending(); } CHIP_ERROR BLEEndPoint::DoSendStandAloneAck() { ChipLogDebugBleEndPoint(Ble, "entered DoSendStandAloneAck; sending stand-alone ack"); // Encode and transmit stand-alone ack. mBtpEngine.EncodeStandAloneAck(mAckToSend); ReturnErrorOnFailure(SendCharacteristic(mAckToSend.Retain())); // Reset local receive window counter. mLocalReceiveWindowSize = mReceiveWindowMaxSize; ChipLogDebugBleEndPoint(Ble, "reset local rx window on stand-alone ack tx, size = %u", mLocalReceiveWindowSize); mConnStateFlags.Set(ConnectionStateFlag::kStandAloneAckInFlight); // Start ack received timer, if it's not already running. return StartAckReceivedTimer(); } CHIP_ERROR BLEEndPoint::DriveSending() { ChipLogDebugBleEndPoint(Ble, "entered DriveSending"); // If receiver's window is almost closed and we don't have an ack to send, OR we do have an ack to send but // receiver's window is completely empty, OR another GATT operation is in flight, awaiting confirmation... if ((mRemoteReceiveWindowSize <= BTP_WINDOW_NO_ACK_SEND_THRESHOLD && !mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning) && mAckToSend.IsNull()) || (mRemoteReceiveWindowSize == 0) || (mConnStateFlags.Has(ConnectionStateFlag::kGattOperationInFlight))) { #ifdef CHIP_BLE_END_POINT_DEBUG_LOGGING_ENABLED if (mRemoteReceiveWindowSize <= BTP_WINDOW_NO_ACK_SEND_THRESHOLD && !mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning) && mAckToSend.IsNull()) { ChipLogDebugBleEndPoint(Ble, "NO SEND: receive window almost closed, and no ack to send"); } if (mRemoteReceiveWindowSize == 0) { ChipLogDebugBleEndPoint(Ble, "NO SEND: remote receive window closed"); } if (mConnStateFlags.Has(ConnectionStateFlag::kGattOperationInFlight)) { ChipLogDebugBleEndPoint(Ble, "NO SEND: Gatt op in flight"); } #endif // Can't send anything. return CHIP_NO_ERROR; } // Otherwise, let's see what we can send. if (!mAckToSend.IsNull()) // If immediate, stand-alone ack is pending, send it. { ReturnErrorOnFailure(DoSendStandAloneAck()); } else if (mBtpEngine.TxState() == BtpEngine::kState_Idle) // Else send next message fragment, if any. { // Fragmenter's idle, let's see what's in the send queue... if (!mSendQueue.IsNull()) { // Transmit first fragment of next whole message in send queue. ReturnErrorOnFailure(SendNextMessage()); } else { // Nothing to send! } } else if (mBtpEngine.TxState() == BtpEngine::kState_InProgress) { // Send next fragment of message currently held by fragmenter. ReturnErrorOnFailure(ContinueMessageSend()); } else if (mBtpEngine.TxState() == BtpEngine::kState_Complete) { // Clear fragmenter's pointer to sent message buffer and reset its Tx state. // Buffer will be freed at scope exit. PacketBufferHandle sentBuf = mBtpEngine.TakeTxPacket(); if (!mSendQueue.IsNull()) { // Transmit first fragment of next whole message in send queue. ReturnErrorOnFailure(SendNextMessage()); } else if (mState == kState_Closing && !mBtpEngine.ExpectingAck()) // and mSendQueue is NULL, per above... { // If end point closing, got last ack, and got out-of-order confirmation for last send, finalize close. FinalizeClose(mState, kBleCloseFlag_SuppressCallback, CHIP_NO_ERROR); } else { // Nothing to send! mBle->mApplicationDelegate->CheckNonConcurrentBleClosing(); } } return CHIP_NO_ERROR; } CHIP_ERROR BLEEndPoint::HandleCapabilitiesRequestReceived(PacketBufferHandle && data) { BleTransportCapabilitiesRequestMessage req; BleTransportCapabilitiesResponseMessage resp; uint16_t mtu; VerifyOrReturnError(!data.IsNull(), CHIP_ERROR_INVALID_ARGUMENT); mState = kState_Connecting; // Decode BTP capabilities request. ReturnErrorOnFailure(BleTransportCapabilitiesRequestMessage::Decode(data, req)); PacketBufferHandle responseBuf = System::PacketBufferHandle::New(kCapabilitiesResponseLength); VerifyOrReturnError(!responseBuf.IsNull(), CHIP_ERROR_NO_MEMORY); // Determine BLE connection's negotiated ATT MTU, if possible. if (req.mMtu > 0) // If MTU was observed and provided by central... { mtu = req.mMtu; // Accept central's observation of the MTU. } else // Otherwise, retrieve it via the platform delegate... { mtu = mBle->mPlatformDelegate->GetMTU(mConnObj); } // Select fragment size for connection based on ATT MTU. if (mtu > 0) // If one or both device knows connection's MTU... { resp.mFragmentSize = std::min(static_cast(mtu - 3), BtpEngine::sMaxFragmentSize); // Reserve 3 bytes of MTU for ATT header. } else // Else, if neither device knows MTU... { ChipLogProgress(Ble, "cannot determine ATT MTU; selecting default fragment size = %u", BtpEngine::sDefaultFragmentSize); resp.mFragmentSize = BtpEngine::sDefaultFragmentSize; } // Select local and remote max receive window size based on local resources available for both incoming writes AND // GATT confirmations. mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize = std::min(req.mWindowSize, static_cast(BLE_MAX_RECEIVE_WINDOW_SIZE)); resp.mWindowSize = mReceiveWindowMaxSize; ChipLogProgress(Ble, "local and remote recv window sizes = %u", resp.mWindowSize); // Select BLE transport protocol version from those supported by central, or none if no supported version found. resp.mSelectedProtocolVersion = BleLayer::GetHighestSupportedProtocolVersion(req); ChipLogProgress(Ble, "selected BTP version %d", resp.mSelectedProtocolVersion); if (resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_None) { // If BLE transport protocol versions incompatible, prepare to close connection after subscription has been // received and capabilities response has been sent. ChipLogError(Ble, "incompatible BTP versions; peripheral expected between %d and %d", CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION, CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION); mState = kState_Aborting; } else { // Set Rx and Tx fragment sizes to the same value mBtpEngine.SetRxFragmentSize(resp.mFragmentSize); mBtpEngine.SetTxFragmentSize(resp.mFragmentSize); } ChipLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mBtpEngine.GetRxFragmentSize(), mBtpEngine.GetTxFragmentSize()); ReturnErrorOnFailure(resp.Encode(responseBuf)); // Stash capabilities response payload and wait for subscription from central. QueueTx(std::move(responseBuf), kType_Data); // Start receive timer. Canceled when end point freed or connection established. return StartReceiveConnectionTimer(); } CHIP_ERROR BLEEndPoint::HandleCapabilitiesResponseReceived(PacketBufferHandle && data) { BleTransportCapabilitiesResponseMessage resp; VerifyOrReturnError(!data.IsNull(), CHIP_ERROR_INVALID_ARGUMENT); // Decode BTP capabilities response. ReturnErrorOnFailure(BleTransportCapabilitiesResponseMessage::Decode(data, resp)); VerifyOrReturnError(resp.mFragmentSize > 0, BLE_ERROR_INVALID_FRAGMENT_SIZE); ChipLogProgress(Ble, "peripheral chose BTP version %d; central expected between %d and %d", resp.mSelectedProtocolVersion, CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION, CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION); if ((resp.mSelectedProtocolVersion < CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION) || (resp.mSelectedProtocolVersion > CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION)) { return BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS; } // Set fragment size as minimum of (reported ATT MTU, BTP characteristic size) resp.mFragmentSize = std::min(resp.mFragmentSize, BtpEngine::sMaxFragmentSize); mBtpEngine.SetRxFragmentSize(resp.mFragmentSize); mBtpEngine.SetTxFragmentSize(resp.mFragmentSize); ChipLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mBtpEngine.GetRxFragmentSize(), mBtpEngine.GetTxFragmentSize()); // Select local and remote max receive window size based on local resources available for both incoming indications // AND GATT confirmations. mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize = resp.mWindowSize; ChipLogProgress(Ble, "local and remote recv window size = %u", resp.mWindowSize); // Shrink local receive window counter by 1, since connect handshake indication requires acknowledgement. mLocalReceiveWindowSize = static_cast(mLocalReceiveWindowSize - 1); ChipLogDebugBleEndPoint(Ble, "decremented local rx window, new size = %u", mLocalReceiveWindowSize); // Send ack for connection handshake indication when timer expires. Sequence numbers always start at 0, // and the reassembler's "last received seq num" is initialized to 0 and updated when new fragments are // received from the peripheral, so we don't need to explicitly mark the ack num to send here. ReturnErrorOnFailure(StartSendAckTimer()); // We've sent a capabilities request write and received a compatible response, so the connect // operation has completed successfully. return HandleConnectComplete(); } // Returns number of open slots in remote receive window given the input values. SequenceNumber_t BLEEndPoint::AdjustRemoteReceiveWindow(SequenceNumber_t lastReceivedAck, SequenceNumber_t maxRemoteWindowSize, SequenceNumber_t newestUnackedSentSeqNum) { // Assumption: SequenceNumber_t is uint8_t. // Assumption: Maximum possible sequence number value is UINT8_MAX. // Assumption: Sequence numbers incremented past maximum value wrap to 0. // Assumption: newest unacked sent sequence number never exceeds current (and by extension, new and un-wrapped) // window boundary, so it never wraps relative to last received ack, if new window boundary would not // also wrap. // Define new window boundary (inclusive) as uint16_t, so its value can temporarily exceed UINT8_MAX. uint16_t newRemoteWindowBoundary = static_cast(lastReceivedAck + maxRemoteWindowSize); if (newRemoteWindowBoundary > UINT8_MAX && newestUnackedSentSeqNum < lastReceivedAck) { // New window boundary WOULD wrap, and latest unacked seq num already HAS wrapped, so add offset to difference. return static_cast(newRemoteWindowBoundary - (newestUnackedSentSeqNum + UINT8_MAX)); } // Neither values would or have wrapped, OR new boundary WOULD wrap but latest unacked seq num does not, so no // offset required. return static_cast(newRemoteWindowBoundary - newestUnackedSentSeqNum); } CHIP_ERROR BLEEndPoint::Receive(PacketBufferHandle && data) { ChipLogDebugBleEndPoint(Ble, "+++++++++++++++++++++ entered receive"); CHIP_ERROR err = CHIP_NO_ERROR; SequenceNumber_t receivedAck = 0; uint8_t closeFlags = kBleCloseFlag_AbortTransmission; bool didReceiveAck = false; { // This is a special handling on the first CHIPoBLE data packet, the CapabilitiesRequest. // Suppress error logging if peer's send overlaps with our unsubscribe on final close. if (IsUnsubscribePending()) { ChipLogDebugBleEndPoint(Ble, "characteristic rx'd while unsubscribe in flight"); ExitNow(); } // If we're receiving the first inbound packet of a BLE transport connection handshake... if (!mConnStateFlags.Has(ConnectionStateFlag::kCapabilitiesMsgReceived)) { if (mRole == kBleRole_Central) // If we're a central receiving a capabilities response indication... { // Ensure end point's in the right state before continuing. VerifyOrExit(mState == kState_Connecting, err = CHIP_ERROR_INCORRECT_STATE); mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesMsgReceived); err = HandleCapabilitiesResponseReceived(std::move(data)); SuccessOrExit(err); } else // Or, a peripheral receiving a capabilities request write... { // Ensure end point's in the right state before continuing. VerifyOrExit(mState == kState_Ready, err = CHIP_ERROR_INCORRECT_STATE); mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesMsgReceived); err = HandleCapabilitiesRequestReceived(std::move(data)); if (err != CHIP_NO_ERROR) { // If an error occurred decoding and handling the capabilities request, release the BLE connection. // Central's connect attempt will time out if peripheral's application decides to keep the BLE // connection open, or fail immediately if the application closes the connection. closeFlags = closeFlags | kBleCloseFlag_SuppressCallback; ExitNow(); } } // If received data was handshake packet, don't feed it to message reassembler. ExitNow(); } } // End handling the CapabilitiesRequest ChipLogDebugBleEndPoint(Ble, "prepared to rx post-handshake btp packet"); // We've received a post-handshake BTP packet. // Ensure end point's in the right state before continuing. if (!IsConnected(mState)) { ChipLogError(Ble, "ep rx'd packet in bad state"); err = CHIP_ERROR_INCORRECT_STATE; ExitNow(); } ChipLogDebugBleEndPoint(Ble, "BTP about to rx characteristic, state before:"); mBtpEngine.LogStateDebug(); // Pass received packet into BTP protocol engine. err = mBtpEngine.HandleCharacteristicReceived(std::move(data), receivedAck, didReceiveAck); ChipLogDebugBleEndPoint(Ble, "BTP rx'd characteristic, state after:"); mBtpEngine.LogStateDebug(); SuccessOrExit(err); // Protocol engine accepted the fragment, so shrink local receive window counter by 1. mLocalReceiveWindowSize = static_cast(mLocalReceiveWindowSize - 1); ChipLogDebugBleEndPoint(Ble, "decremented local rx window, new size = %u", mLocalReceiveWindowSize); // Respond to received ack, if any. if (didReceiveAck) { ChipLogDebugBleEndPoint(Ble, "got btp ack = %u", receivedAck); // If ack was rx'd for newest unacked sent fragment, stop ack received timer. if (!mBtpEngine.ExpectingAck()) { ChipLogDebugBleEndPoint(Ble, "got ack for last outstanding fragment"); StopAckReceivedTimer(); if (mState == kState_Closing && mSendQueue.IsNull() && mBtpEngine.TxState() == BtpEngine::kState_Idle) { // If end point closing, got confirmation for last send, and waiting for last ack, finalize close. FinalizeClose(mState, kBleCloseFlag_SuppressCallback, CHIP_NO_ERROR); ExitNow(); } } else // Else there are still sent fragments for which acks are expected, so restart ack received timer. { ChipLogDebugBleEndPoint(Ble, "still expecting ack(s), restarting timer..."); err = RestartAckReceivedTimer(); SuccessOrExit(err); } ChipLogDebugBleEndPoint(Ble, "about to adjust remote rx window; got ack num = %u, newest unacked sent seq num = %u, \ old window size = %u, max window size = %u", receivedAck, mBtpEngine.GetNewestUnackedSentSequenceNumber(), mRemoteReceiveWindowSize, mReceiveWindowMaxSize); // Open remote device's receive window according to sequence number it just acknowledged. mRemoteReceiveWindowSize = AdjustRemoteReceiveWindow(receivedAck, mReceiveWindowMaxSize, mBtpEngine.GetNewestUnackedSentSequenceNumber()); ChipLogDebugBleEndPoint(Ble, "adjusted remote rx window, new size = %u", mRemoteReceiveWindowSize); // Restart message transmission if it was previously paused due to window exhaustion. err = DriveSending(); SuccessOrExit(err); } // The previous DriveSending() might have generated a piggyback acknowledgement if there was // previously un-acked data. Otherwise, prepare to send acknowledgement for newly received fragment. // // If local receive window is below immediate ack threshold, AND there is no previous stand-alone ack in // flight, AND there is no pending outbound message fragment on which the ack can and will be piggybacked, // send immediate stand-alone ack to reopen window for sender. // // The "GATT operation in flight" check below covers "pending outbound message fragment" by extension, as when // a message has been passed to the end point via Send(), its next outbound fragment must either be in flight // itself, or awaiting the completion of another in-flight GATT operation. // // If any GATT operation is in flight that is NOT a stand-alone ack, the window size will be checked against // this threshold again when the GATT operation is confirmed. if (mBtpEngine.HasUnackedData()) { if (mLocalReceiveWindowSize <= BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && !mConnStateFlags.Has(ConnectionStateFlag::kGattOperationInFlight)) { ChipLogDebugBleEndPoint(Ble, "sending immediate ack"); err = DriveStandAloneAck(); SuccessOrExit(err); } else { ChipLogDebugBleEndPoint(Ble, "starting send-ack timer"); // Send ack when timer expires. err = StartSendAckTimer(); SuccessOrExit(err); } } // If we've reassembled a whole message... if (mBtpEngine.RxState() == BtpEngine::kState_Complete) { // Take ownership of message buffer System::PacketBufferHandle full_packet = mBtpEngine.TakeRxPacket(); ChipLogDebugBleEndPoint(Ble, "reassembled whole msg, len = %u", static_cast(full_packet->DataLength())); // If we have a message received callback, and end point is not closing... if (mBleTransport != nullptr && mState != kState_Closing) { // Pass received message up the stack. mBleTransport->OnEndPointMessageReceived(this, std::move(full_packet)); } } exit: if (err != CHIP_NO_ERROR) { DoClose(closeFlags, err); } return err; } CHIP_ERROR BLEEndPoint::SendWrite(PacketBufferHandle && buf) { mConnStateFlags.Set(ConnectionStateFlag::kGattOperationInFlight); auto err = mBle->mPlatformDelegate->SendWriteRequest(mConnObj, &CHIP_BLE_SVC_ID, &CHIP_BLE_CHAR_1_UUID, std::move(buf)); VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(Ble, "Send write request failed: %" CHIP_ERROR_FORMAT, err.Format())); return err; } CHIP_ERROR BLEEndPoint::SendIndication(PacketBufferHandle && buf) { mConnStateFlags.Set(ConnectionStateFlag::kGattOperationInFlight); auto err = mBle->mPlatformDelegate->SendIndication(mConnObj, &CHIP_BLE_SVC_ID, &CHIP_BLE_CHAR_2_UUID, std::move(buf)); VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(Ble, "Send indication failed: %" CHIP_ERROR_FORMAT, err.Format())); return err; } CHIP_ERROR BLEEndPoint::StartConnectTimer() { const CHIP_ERROR timerErr = mBle->mSystemLayer->StartTimer(System::Clock::Milliseconds32(BTP_CONN_RSP_TIMEOUT_MS), HandleConnectTimeout, this); ReturnErrorOnFailure(timerErr); mTimerStateFlags.Set(TimerStateFlag::kConnectTimerRunning); return CHIP_NO_ERROR; } CHIP_ERROR BLEEndPoint::StartReceiveConnectionTimer() { const CHIP_ERROR timerErr = mBle->mSystemLayer->StartTimer(System::Clock::Milliseconds32(BTP_CONN_RSP_TIMEOUT_MS), HandleReceiveConnectionTimeout, this); ReturnErrorOnFailure(timerErr); mTimerStateFlags.Set(TimerStateFlag::kReceiveConnectionTimerRunning); return CHIP_NO_ERROR; } CHIP_ERROR BLEEndPoint::StartAckReceivedTimer() { if (!mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning)) { const CHIP_ERROR timerErr = mBle->mSystemLayer->StartTimer(System::Clock::Milliseconds32(BTP_ACK_TIMEOUT_MS), HandleAckReceivedTimeout, this); ReturnErrorOnFailure(timerErr); mTimerStateFlags.Set(TimerStateFlag::kAckReceivedTimerRunning); } return CHIP_NO_ERROR; } CHIP_ERROR BLEEndPoint::RestartAckReceivedTimer() { VerifyOrReturnError(mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning), CHIP_ERROR_INCORRECT_STATE); StopAckReceivedTimer(); return StartAckReceivedTimer(); } CHIP_ERROR BLEEndPoint::StartSendAckTimer() { ChipLogDebugBleEndPoint(Ble, "entered StartSendAckTimer"); if (!mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning)) { ChipLogDebugBleEndPoint(Ble, "starting new SendAckTimer"); const CHIP_ERROR timerErr = mBle->mSystemLayer->StartTimer(System::Clock::Milliseconds32(BTP_ACK_SEND_TIMEOUT_MS), HandleSendAckTimeout, this); ReturnErrorOnFailure(timerErr); mTimerStateFlags.Set(TimerStateFlag::kSendAckTimerRunning); } return CHIP_NO_ERROR; } CHIP_ERROR BLEEndPoint::StartUnsubscribeTimer() { const CHIP_ERROR timerErr = mBle->mSystemLayer->StartTimer(System::Clock::Milliseconds32(BLE_UNSUBSCRIBE_TIMEOUT_MS), HandleUnsubscribeTimeout, this); ReturnErrorOnFailure(timerErr); mTimerStateFlags.Set(TimerStateFlag::kUnsubscribeTimerRunning); return CHIP_NO_ERROR; } void BLEEndPoint::StopConnectTimer() { // Cancel any existing connect timer. mBle->mSystemLayer->CancelTimer(HandleConnectTimeout, this); mTimerStateFlags.Clear(TimerStateFlag::kConnectTimerRunning); } void BLEEndPoint::StopReceiveConnectionTimer() { // Cancel any existing receive connection timer. mBle->mSystemLayer->CancelTimer(HandleReceiveConnectionTimeout, this); mTimerStateFlags.Clear(TimerStateFlag::kReceiveConnectionTimerRunning); } void BLEEndPoint::StopAckReceivedTimer() { // Cancel any existing ack-received timer. mBle->mSystemLayer->CancelTimer(HandleAckReceivedTimeout, this); mTimerStateFlags.Clear(TimerStateFlag::kAckReceivedTimerRunning); } void BLEEndPoint::StopSendAckTimer() { // Cancel any existing send-ack timer. mBle->mSystemLayer->CancelTimer(HandleSendAckTimeout, this); mTimerStateFlags.Clear(TimerStateFlag::kSendAckTimerRunning); } void BLEEndPoint::StopUnsubscribeTimer() { // Cancel any existing unsubscribe timer. mBle->mSystemLayer->CancelTimer(HandleUnsubscribeTimeout, this); mTimerStateFlags.Clear(TimerStateFlag::kUnsubscribeTimerRunning); } void BLEEndPoint::HandleConnectTimeout(chip::System::Layer * systemLayer, void * appState) { BLEEndPoint * ep = static_cast(appState); // Check for event-based timer race condition. if (ep->mTimerStateFlags.Has(TimerStateFlag::kConnectTimerRunning)) { ChipLogError(Ble, "connect handshake timed out, closing ep %p", ep); ep->mTimerStateFlags.Clear(TimerStateFlag::kConnectTimerRunning); ep->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_CONNECT_TIMED_OUT); } } void BLEEndPoint::HandleReceiveConnectionTimeout(chip::System::Layer * systemLayer, void * appState) { BLEEndPoint * ep = static_cast(appState); // Check for event-based timer race condition. if (ep->mTimerStateFlags.Has(TimerStateFlag::kReceiveConnectionTimerRunning)) { ChipLogError(Ble, "receive handshake timed out, closing ep %p", ep); ep->mTimerStateFlags.Clear(TimerStateFlag::kReceiveConnectionTimerRunning); ep->DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, BLE_ERROR_RECEIVE_TIMED_OUT); } } void BLEEndPoint::HandleAckReceivedTimeout(chip::System::Layer * systemLayer, void * appState) { BLEEndPoint * ep = static_cast(appState); // Check for event-based timer race condition. if (ep->mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning)) { ChipLogError(Ble, "ack recv timeout, closing ep %p", ep); ep->mBtpEngine.LogStateDebug(); ep->mTimerStateFlags.Clear(TimerStateFlag::kAckReceivedTimerRunning); ep->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_FRAGMENT_ACK_TIMED_OUT); } } void BLEEndPoint::HandleSendAckTimeout(chip::System::Layer * systemLayer, void * appState) { BLEEndPoint * ep = static_cast(appState); // Check for event-based timer race condition. if (ep->mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning)) { ep->mTimerStateFlags.Clear(TimerStateFlag::kSendAckTimerRunning); // If previous stand-alone ack isn't still in flight... if (!ep->mConnStateFlags.Has(ConnectionStateFlag::kStandAloneAckInFlight)) { CHIP_ERROR sendErr = ep->DriveStandAloneAck(); if (sendErr != CHIP_NO_ERROR) { ep->DoClose(kBleCloseFlag_AbortTransmission, sendErr); } } } } void BLEEndPoint::HandleUnsubscribeTimeout(chip::System::Layer * systemLayer, void * appState) { BLEEndPoint * ep = static_cast(appState); // Check for event-based timer race condition. if (ep->mTimerStateFlags.Has(TimerStateFlag::kUnsubscribeTimerRunning)) { ChipLogError(Ble, "unsubscribe timed out, ble ep %p", ep); ep->mTimerStateFlags.Clear(TimerStateFlag::kUnsubscribeTimerRunning); ep->HandleUnsubscribeComplete(); } } } /* namespace Ble */ } /* namespace chip */