| /* |
| * |
| * <COPYRIGHT> |
| * |
| * 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). |
| * |
| */ |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <ble/BleConfig.h> |
| |
| #if CONFIG_NETWORK_LAYER_BLE |
| #include <core/CHIPConfig.h> |
| #include <support/logging/CHIPLogging.h> |
| #include <support/CodeUtils.h> |
| #include <support/FlagUtils.hpp> |
| #include <support/CHIPFaultInjection.h> |
| |
| #include <ble/BLEEndPoint.h> |
| #include <ble/BleLayer.h> |
| #include <ble/CHIPoBle.h> |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| #include "CHIPoBleTest.h" |
| #endif |
| |
| // clang-format off |
| |
| // 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, ...) ChipLogError(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_CONNECT_TIMEOUT_MS |
| * |
| * @brief |
| * This is the amount of time, in milliseconds, after a BLE end point initiates a transport protocol connection |
| * or receives the initial portion of a connect request before the end point will automatically release its BLE |
| * connection and free itself if the transport connection has not been established. |
| * |
| */ |
| #define BLE_CONNECT_TIMEOUT_MS 5000 // 5 seconds |
| |
| /** |
| * @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 arbitary. |
| * |
| */ |
| #define BLE_UNSUBSCRIBE_TIMEOUT_MS 5000 // 5 seconds |
| |
| #define BTP_ACK_RECEIVED_TIMEOUT_MS 15000 // 15 seconds |
| #define BTP_ACK_SEND_TIMEOUT_MS 2500 // 2.5 seconds |
| |
| #define BTP_WINDOW_NO_ACK_SEND_THRESHOLD 1 // Data fragments may only be sent without piggybacked |
| // acks if receiver's window size is above this threshold. |
| |
| // clang-format on |
| |
| namespace chip { |
| namespace Ble { |
| |
| BLE_ERROR BLEEndPoint::StartConnect() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| BleTransportCapabilitiesRequestMessage req; |
| PacketBuffer * buf = NULL; |
| int i; |
| int numVersions; |
| |
| // Ensure we're in the correct state. |
| VerifyOrExit(mState == kState_Ready, err = BLE_ERROR_INCORRECT_STATE); |
| mState = kState_Connecting; |
| |
| // Build BLE transport protocol capabilities request. |
| buf = PacketBuffer::New(); |
| VerifyOrExit(buf != NULL, err = BLE_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 |
| numVersions = CHIP_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION + 1; |
| VerifyOrExit(numVersions <= NUM_SUPPORTED_PROTOCOL_VERSIONS, err = BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS); |
| for (i = 0; i < numVersions; i++) |
| { |
| req.SetSupportedProtocolVersion(i, 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. |
| if (!SendWrite(buf)) |
| { |
| err = BLE_ERROR_GATT_WRITE_FAILED; |
| ExitNow(); |
| } |
| |
| // 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(buf, kType_Data); |
| buf = NULL; |
| |
| exit: |
| if (buf != NULL) |
| { |
| PacketBuffer::Free(buf); |
| } |
| |
| // If we failed to initiate the connection, close the end point. |
| if (err != BLE_NO_ERROR) |
| { |
| StopConnectTimer(); |
| DoClose(kBleCloseFlag_AbortTransmission, err); |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleConnectComplete() |
| { |
| BLE_ERROR err = BLE_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 (OnConnectComplete != NULL) |
| { |
| // Indicate connect complete to next-higher layer. |
| OnConnectComplete(this, BLE_NO_ERROR); |
| } |
| else |
| { |
| // If no connect complete callback has been set up, close the end point. |
| err = BLE_ERRROR_NO_CONNECT_COMPLETE_CALLBACK; |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleReceiveConnectionComplete() |
| { |
| BLE_ERROR err = BLE_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 application know we're open for business. |
| if (mBle->OnchipBleConnectReceived != NULL) |
| { |
| // Indicate BLE transport protocol connection received to next-higher layer. |
| mBle->OnchipBleConnectReceived(this); |
| } |
| else |
| { |
| err = BLE_ERROR_NO_CONNECTION_RECEIVED_CALLBACK; |
| } |
| |
| return err; |
| } |
| |
| void BLEEndPoint::HandleSubscribeReceived() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| VerifyOrExit(mState == kState_Connecting || mState == kState_Aborting, err = BLE_ERROR_INCORRECT_STATE); |
| VerifyOrExit(mSendQueue != NULL, err = BLE_ERROR_INCORRECT_STATE); |
| |
| // Send BTP capabilities response to peripheral via GATT indication. |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| VerifyOrExit(mCHIPoBle.PopPacketTag(mSendQueue) == kType_Data, err = BLE_ERROR_INVALID_BTP_HEADER_FLAGS); |
| #endif |
| if (!SendIndication(mSendQueue)) |
| { |
| // Ensure transmit queue is empty and set to NULL. |
| QueueTxLock(); |
| PacketBuffer::Free(mSendQueue); |
| mSendQueue = NULL; |
| QueueTxUnlock(); |
| |
| ChipLogError(Ble, "cap resp ind failed"); |
| err = BLE_ERROR_GATT_INDICATE_FAILED; |
| ExitNow(); |
| } |
| |
| // Shrink remote receive window counter by 1, since we've sent an indication which requires acknowledgement. |
| 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 != BLE_NO_ERROR) |
| { |
| DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, err); |
| } |
| |
| return; |
| } |
| |
| void BLEEndPoint::HandleSubscribeComplete() |
| { |
| ChipLogProgress(Ble, "subscribe complete, ep = %p", this); |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, false); |
| |
| BLE_ERROR err = DriveSending(); |
| |
| if (err != BLE_NO_ERROR) |
| { |
| DoClose(kBleCloseFlag_AbortTransmission, BLE_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 (GetFlag(mTimerStateFlags, kTimerState_UnsubscribeTimerRunning)); |
| } |
| |
| void BLEEndPoint::Abort() |
| { |
| // No more callbacks after this point, since application explicitly called Abort(). |
| OnConnectComplete = NULL; |
| OnConnectionClosed = NULL; |
| OnMessageReceived = NULL; |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| OnCommandReceived = NULL; |
| #endif |
| |
| DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, BLE_NO_ERROR); |
| } |
| |
| void BLEEndPoint::Close() |
| { |
| // No more callbacks after this point, since application explicitly called Close(). |
| OnConnectComplete = NULL; |
| OnConnectionClosed = NULL; |
| OnMessageReceived = NULL; |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| OnCommandReceived = NULL; |
| #endif |
| |
| DoClose(kBleCloseFlag_SuppressCallback, BLE_NO_ERROR); |
| } |
| |
| void BLEEndPoint::DoClose(uint8_t flags, BLE_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 (mCHIPoBle.TxState() == CHIPoBle::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); |
| } |
| } |
| } |
| } |
| |
| void BLEEndPoint::FinalizeClose(uint8_t oldState, uint8_t flags, BLE_ERROR err) |
| { |
| mState = kState_Closed; |
| |
| // Ensure transmit queue is empty and set to NULL. |
| QueueTxLock(); |
| PacketBuffer::Free(mSendQueue); |
| mSendQueue = NULL; |
| QueueTxUnlock(); |
| |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| PacketBuffer::Free(mCHIPoBleTest.mCommandReceiveQueue); |
| mCHIPoBleTest.mCommandReceiveQueue = NULL; |
| #endif |
| |
| // 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 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 && GetFlag(mConnStateFlags, kConnState_DidBeginSubscribe)) |
| { |
| // 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. |
| if (!mBle->mPlatformDelegate->UnsubscribeCharacteristic(mConnObj, &CHIP_BLE_SVC_ID, &mBle->CHIP_BLE_CHAR_2_ID)) |
| { |
| ChipLogError(Ble, "CHIPoBle unsub failed"); |
| |
| // 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 != BLE_NO_ERROR) |
| { |
| Free(); |
| } |
| |
| // Mark unsubscribe GATT operation in progress. |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, true); |
| } |
| } |
| else // mRole == kBleRole_Peripheral, OR GetFlag(mTimerStateFlags, kConnState_DidBeginSubscribe) == false... |
| { |
| Free(); |
| } |
| } |
| } |
| |
| void BLEEndPoint::DoCloseCallback(uint8_t state, uint8_t flags, BLE_ERROR err) |
| { |
| if (state == kState_Connecting) |
| { |
| if (OnConnectComplete != NULL) |
| { |
| OnConnectComplete(this, err); |
| } |
| } |
| else |
| { |
| if (OnConnectionClosed != NULL) |
| { |
| OnConnectionClosed(this, err); |
| } |
| } |
| |
| // Callback fires once per end point lifetime. |
| OnConnectComplete = NULL; |
| OnConnectionClosed = NULL; |
| } |
| |
| void BLEEndPoint::ReleaseBleConnection() |
| { |
| if (mConnObj != BLE_CONNECTION_UNINITIALIZED) |
| { |
| if (GetFlag(mConnStateFlags, kConnState_AutoClose)) |
| { |
| 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. |
| FreeCHIPoBle(); |
| |
| // Clear pending ack buffer, if any. |
| PacketBuffer::Free(mAckToSend); |
| |
| // Cancel all timers. |
| StopConnectTimer(); |
| StopReceiveConnectionTimer(); |
| StopAckReceivedTimer(); |
| StopSendAckTimer(); |
| StopUnsubscribeTimer(); |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| mCHIPoBleTest.StopTestTimer(); |
| // Clear callback |
| OnCommandReceived = NULL; |
| #endif |
| |
| // Clear callbacks. |
| OnConnectComplete = NULL; |
| OnMessageReceived = NULL; |
| OnConnectionClosed = NULL; |
| |
| // Clear handle to underlying BLE connection. |
| mConnObj = BLE_CONNECTION_UNINITIALIZED; |
| |
| // Release the AddRef() that happened when the end point was allocated. |
| Release(); |
| } |
| |
| void BLEEndPoint::FreeCHIPoBle() |
| { |
| PacketBuffer * buf; |
| |
| // Free transmit disassembly buffer |
| buf = mCHIPoBle.TxPacket(); |
| mCHIPoBle.ClearTxPacket(); |
| PacketBuffer::Free(buf); |
| |
| // Free receive reassembly buffer |
| buf = mCHIPoBle.RxPacket(); |
| mCHIPoBle.ClearRxPacket(); |
| PacketBuffer::Free(buf); |
| } |
| |
| BLE_ERROR BLEEndPoint::Init(BleLayer * bleLayer, BLE_CONNECTION_OBJECT connObj, BleRole role, bool autoClose) |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| bool expectInitialAck; |
| |
| // Fail if already initialized. |
| VerifyOrExit(mBle == NULL, err = BLE_ERROR_INCORRECT_STATE); |
| |
| // Validate args. |
| VerifyOrExit(bleLayer != NULL, err = BLE_ERROR_BAD_ARGS); |
| VerifyOrExit(connObj != BLE_CONNECTION_UNINITIALIZED, err = BLE_ERROR_BAD_ARGS); |
| VerifyOrExit((role == kBleRole_Central || role == kBleRole_Peripheral), err = BLE_ERROR_BAD_ARGS); |
| |
| // Null-initialize callbacks and data members. |
| // |
| // Beware this line should we ever use virtuals in this class or its |
| // super(s). See similar lines in chip::System::Layer end points. |
| memset((void *) this, 0, sizeof(*this)); |
| |
| // If end point plays peripheral role, expect ack for indication sent as last step of BTP handshake. |
| // If central, periperal's handshake indication 'ack's write sent by central to kick off the BTP handshake. |
| expectInitialAck = (role == kBleRole_Peripheral); |
| |
| err = mCHIPoBle.Init(this, expectInitialAck); |
| if (err != BLE_NO_ERROR) |
| { |
| ChipLogError(Ble, "CHIPoBle init failed"); |
| ExitNow(); |
| } |
| |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| err = (BLE_ERROR) mTxQueueMutex.Init(mTxQueueMutex); |
| if (err != BLE_NO_ERROR) |
| { |
| ChipLogError(Ble, "%s: Mutex init failed", __FUNCTION__); |
| ExitNow(); |
| } |
| err = mCHIPoBleTest.Init(this); |
| if (err != BLE_NO_ERROR) |
| { |
| ChipLogError(Ble, "CHIPoBleTest init failed"); |
| ExitNow(); |
| } |
| #endif |
| |
| // BleLayerObject initialization: |
| mBle = bleLayer; |
| mRefCount = 1; |
| |
| // BLEEndPoint data members: |
| mConnObj = connObj; |
| mRole = role; |
| mConnStateFlags = 0; |
| mTimerStateFlags = 0; |
| SetFlag(mConnStateFlags, kConnState_AutoClose, autoClose); |
| mLocalReceiveWindowSize = 0; |
| mRemoteReceiveWindowSize = 0; |
| mReceiveWindowMaxSize = 0; |
| mSendQueue = NULL; |
| mAckToSend = NULL; |
| |
| ChipLogDebugBleEndPoint(Ble, "initialized local rx window, size = %u", mLocalReceiveWindowSize); |
| |
| // End point is ready to connect or receive a connection. |
| mState = kState_Ready; |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::SendCharacteristic(PacketBuffer * buf) |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| if (mRole == kBleRole_Central) |
| { |
| if (!SendWrite(buf)) |
| { |
| err = BLE_ERROR_GATT_WRITE_FAILED; |
| } |
| else |
| { |
| // Write succeeded, so shrink remote receive window counter by 1. |
| mRemoteReceiveWindowSize -= 1; |
| ChipLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); |
| } |
| } |
| else // (mRole == kBleRole_Peripheral), verified on Init |
| { |
| if (!SendIndication(buf)) |
| { |
| err = BLE_ERROR_GATT_INDICATE_FAILED; |
| } |
| else |
| { |
| // Indication succeeded, so shrink remote receive window counter by 1. |
| mRemoteReceiveWindowSize -= 1; |
| ChipLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); |
| } |
| } |
| |
| 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(PacketBuffer * data, PacketType_t type) |
| { |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| ChipLogDebugBleEndPoint(Ble, "%s: data->%p, type %d, len %d", __FUNCTION__, data, type, data->DataLength()); |
| mCHIPoBle.PushPacketTag(data, type); |
| #endif |
| |
| QueueTxLock(); |
| |
| if (mSendQueue == NULL) |
| { |
| mSendQueue = data; |
| ChipLogDebugBleEndPoint(Ble, "%s: Set data as new mSendQueue %p, type %d", __FUNCTION__, mSendQueue, type); |
| } |
| else |
| { |
| mSendQueue->AddToEnd(data); |
| ChipLogDebugBleEndPoint(Ble, "%s: Append data to mSendQueue %p, type %d", __FUNCTION__, mSendQueue, type); |
| } |
| |
| QueueTxUnlock(); |
| } |
| |
| BLE_ERROR BLEEndPoint::Send(PacketBuffer * data) |
| { |
| ChipLogDebugBleEndPoint(Ble, "entered Send"); |
| |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| VerifyOrExit(data != NULL, err = BLE_ERROR_BAD_ARGS); |
| VerifyOrExit(IsConnected(mState), err = BLE_ERROR_INCORRECT_STATE); |
| |
| // Ensure outgoing message fits in a single contiguous PacketBuffer, as currently required by the |
| // message fragmentation and reassembly engine. |
| if (data->Next() != NULL) |
| { |
| data->CompactHead(); |
| |
| if (data->Next() != NULL) |
| { |
| err = BLE_ERROR_OUTBOUND_MESSAGE_TOO_BIG; |
| ExitNow(); |
| } |
| } |
| |
| // Add new message to send queue. |
| QueueTx(data, kType_Data); |
| data = NULL; // Buffer freed when send queue freed on close, or on completion of current message transmission. |
| |
| // Send first fragment of new message, if we can. |
| err = DriveSending(); |
| SuccessOrExit(err); |
| |
| exit: |
| ChipLogDebugBleEndPoint(Ble, "exiting Send"); |
| |
| if (data != NULL) |
| { |
| PacketBuffer::Free(data); |
| } |
| |
| if (err != BLE_NO_ERROR) |
| { |
| DoClose(kBleCloseFlag_AbortTransmission, err); |
| } |
| |
| return err; |
| } |
| |
| bool BLEEndPoint::PrepareNextFragment(PacketBuffer * data, bool & sentAck) |
| { |
| // If we have a pending fragment acknowledgement to send, piggyback it on the fragment we're about to transmit. |
| if (GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning)) |
| { |
| // 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 mCHIPoBle.HandleCharacteristicSend(data, sentAck); |
| } |
| |
| BLE_ERROR BLEEndPoint::SendNextMessage() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| bool sentAck; |
| |
| // Get the first queued packet to send |
| QueueTxLock(); |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| // Return if tx queue is empty |
| // Note: DetachTail() does not check an empty queue |
| if (mSendQueue == NULL) |
| { |
| QueueTxUnlock(); |
| return err; |
| } |
| #endif |
| |
| PacketBuffer * data = mSendQueue; |
| mSendQueue = mSendQueue->DetachTail(); |
| QueueTxUnlock(); |
| |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| // Get and consume the packet tag in message buffer |
| PacketType_t type = mCHIPoBle.PopPacketTag(data); |
| mCHIPoBle.SetTxPacketType(type); |
| mCHIPoBleTest.DoTxTiming(data, CHIPOBLE_TX_START); |
| #endif |
| |
| // Hand whole message payload to the fragmenter. |
| VerifyOrExit(PrepareNextFragment(data, sentAck), err = BLE_ERROR_CHIPOBLE_PROTOCOL_ABORT); |
| data = NULL; // Ownership passed to fragmenter's tx buf on PrepareNextFragment success. |
| |
| // 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(); |
| }); |
| err = SendCharacteristic(mCHIPoBle.TxPacket()); |
| SuccessOrExit(err); |
| |
| if (sentAck) |
| { |
| // If sent piggybacked ack, stop send-ack timer. |
| StopSendAckTimer(); |
| } |
| |
| // Start ack received timer, if it's not already running. |
| err = StartAckReceivedTimer(); |
| SuccessOrExit(err); |
| |
| exit: |
| if (data != NULL) |
| { |
| PacketBuffer::Free(data); |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::ContinueMessageSend() |
| { |
| BLE_ERROR err; |
| bool sentAck; |
| |
| if (!PrepareNextFragment(NULL, sentAck)) |
| { |
| // Log BTP error |
| ChipLogError(Ble, "btp fragmenter error on send!"); |
| mCHIPoBle.LogState(); |
| |
| err = BLE_ERROR_CHIPOBLE_PROTOCOL_ABORT; |
| ExitNow(); |
| } |
| |
| err = SendCharacteristic(mCHIPoBle.TxPacket()); |
| SuccessOrExit(err); |
| |
| if (sentAck) |
| { |
| // If sent piggybacked ack, stop send-ack timer. |
| StopSendAckTimer(); |
| } |
| |
| // Start ack received timer, if it's not already running. |
| err = StartAckReceivedTimer(); |
| SuccessOrExit(err); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleHandshakeConfirmationReceived() |
| { |
| ChipLogDebugBleEndPoint(Ble, "entered HandleHandshakeConfirmationReceived"); |
| |
| BLE_ERROR err = BLE_NO_ERROR; |
| uint8_t closeFlags = kBleCloseFlag_AbortTransmission; |
| |
| // Free capabilities request/response payload. |
| QueueTxLock(); |
| mSendQueue = PacketBuffer::FreeHead(mSendQueue); |
| QueueTxUnlock(); |
| |
| if (mRole == kBleRole_Central) |
| { |
| // Subscribe to characteristic which peripheral will use to send indications. Prompts peripheral to send |
| // BLE transport capabilities indication. |
| VerifyOrExit(mBle->mPlatformDelegate->SubscribeCharacteristic(mConnObj, &CHIP_BLE_SVC_ID, &mBle->CHIP_BLE_CHAR_2_ID), |
| err = BLE_ERROR_GATT_SUBSCRIBE_FAILED); |
| |
| // We just sent a GATT subscribe request, so make sure to attempt unsubscribe on close. |
| SetFlag(mConnStateFlags, kConnState_DidBeginSubscribe, true); |
| |
| // Mark GATT operation in progress for subscribe request. |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, true); |
| } |
| 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 == NULL) |
| { |
| err = DriveStandAloneAck(); // Encode stand-alone ack and drive sending. |
| SuccessOrExit(err); |
| } |
| else |
| { |
| // Drive sending in case application callend 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 != BLE_NO_ERROR) |
| { |
| DoClose(closeFlags, err); |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleFragmentConfirmationReceived() |
| { |
| BLE_ERROR err = BLE_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 = BLE_ERROR_INCORRECT_STATE); |
| |
| // TODO PacketBuffer high water mark optimization: if ack pending, but fragmenter state == complete, free fragmenter's |
| // tx buf before sending ack. |
| |
| if (GetFlag(mConnStateFlags, kConnState_StandAloneAckInFlight)) |
| { |
| // If confirmation was received for stand-alone ack, free its tx buffer. |
| PacketBuffer::Free(mAckToSend); |
| mAckToSend = NULL; |
| |
| SetFlag(mConnStateFlags, kConnState_StandAloneAckInFlight, false); |
| } |
| |
| // 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 != NULL || mCHIPoBle.TxState() == CHIPoBle::kState_InProgress)) |
| { |
| err = DriveStandAloneAck(); // Encode stand-alone ack and drive sending. |
| SuccessOrExit(err); |
| } |
| else |
| { |
| err = DriveSending(); |
| SuccessOrExit(err); |
| } |
| |
| exit: |
| if (err != BLE_NO_ERROR) |
| { |
| DoClose(kBleCloseFlag_AbortTransmission, err); |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleGattSendConfirmationReceived() |
| { |
| ChipLogDebugBleEndPoint(Ble, "entered HandleGattSendConfirmationReceived"); |
| |
| // Mark outstanding GATT operation as finished. |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, false); |
| |
| // If confirmation was for outbound portion of BTP connect handshake... |
| if (!GetFlag(mConnStateFlags, kConnState_CapabilitiesConfReceived)) |
| { |
| SetFlag(mConnStateFlags, kConnState_CapabilitiesConfReceived, true); |
| |
| return HandleHandshakeConfirmationReceived(); |
| } |
| else |
| { |
| return HandleFragmentConfirmationReceived(); |
| } |
| } |
| |
| BLE_ERROR BLEEndPoint::DriveStandAloneAck() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| // Stop send-ack timer if running. |
| StopSendAckTimer(); |
| |
| // If stand-alone ack not already pending, allocate new payload buffer here. |
| if (mAckToSend == NULL) |
| { |
| mAckToSend = PacketBuffer::New(); |
| VerifyOrExit(mAckToSend != NULL, err = BLE_ERROR_NO_MEMORY); |
| } |
| |
| // Attempt to send stand-alone ack. |
| err = DriveSending(); |
| SuccessOrExit(err); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::DoSendStandAloneAck() |
| { |
| ChipLogDebugBleEndPoint(Ble, "entered DoSendStandAloneAck; sending stand-alone ack"); |
| |
| // Encode and transmit stand-alone ack. |
| mCHIPoBle.EncodeStandAloneAck(mAckToSend); |
| BLE_ERROR err = SendCharacteristic(mAckToSend); |
| SuccessOrExit(err); |
| |
| // Reset local receive window counter. |
| mLocalReceiveWindowSize = mReceiveWindowMaxSize; |
| ChipLogDebugBleEndPoint(Ble, "reset local rx window on stand-alone ack tx, size = %u", mLocalReceiveWindowSize); |
| |
| SetFlag(mConnStateFlags, kConnState_StandAloneAckInFlight, true); |
| |
| // Start ack received timer, if it's not already running. |
| err = StartAckReceivedTimer(); |
| SuccessOrExit(err); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::DriveSending() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| 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 && |
| !GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning) && mAckToSend == NULL) || |
| (mRemoteReceiveWindowSize == 0) || (GetFlag(mConnStateFlags, kConnState_GattOperationInFlight))) |
| { |
| #ifdef CHIP_BLE_END_POINT_DEBUG_LOGGING_ENABLED |
| if (mRemoteReceiveWindowSize <= BTP_WINDOW_NO_ACK_SEND_THRESHOLD && |
| !GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning) && mAckToSend == NULL) |
| { |
| 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 (GetFlag(mConnStateFlags, kConnState_GattOperationInFlight)) |
| { |
| ChipLogDebugBleEndPoint(Ble, "NO SEND: Gatt op in flight"); |
| } |
| #endif |
| |
| // Can't send anything. |
| ExitNow(); |
| } |
| |
| // Otherwise, let's see what we can send. |
| |
| if (mAckToSend != NULL) // If immediate, stand-alone ack is pending, send it. |
| { |
| err = DoSendStandAloneAck(); |
| SuccessOrExit(err); |
| } |
| else if (mCHIPoBle.TxState() == CHIPoBle::kState_Idle) // Else send next message fragment, if any. |
| { |
| // Fragmenter's idle, let's see what's in the send queue... |
| if (mSendQueue != NULL) |
| { |
| // Transmit first fragment of next whole message in send queue. |
| err = SendNextMessage(); |
| SuccessOrExit(err); |
| } |
| else |
| { |
| // Nothing to send! |
| } |
| } |
| else if (mCHIPoBle.TxState() == CHIPoBle::kState_InProgress) |
| { |
| // Send next fragment of message currently held by fragmenter. |
| err = ContinueMessageSend(); |
| SuccessOrExit(err); |
| } |
| else if (mCHIPoBle.TxState() == CHIPoBle::kState_Complete) |
| { |
| // Clear fragmenter's pointer to sent message buffer and reset its Tx state. |
| PacketBuffer * sentBuf = mCHIPoBle.TxPacket(); |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| mCHIPoBleTest.DoTxTiming(sentBuf, CHIPOBLE_TX_DONE); |
| #endif // CHIP_ENABLE_CHIPOBLE_TEST |
| mCHIPoBle.ClearTxPacket(); |
| |
| // Free sent buffer. |
| PacketBuffer::Free(sentBuf); |
| sentBuf = NULL; |
| |
| if (mSendQueue != NULL) |
| { |
| // Transmit first fragment of next whole message in send queue. |
| err = SendNextMessage(); |
| SuccessOrExit(err); |
| } |
| else if (mState == kState_Closing && !mCHIPoBle.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, BLE_NO_ERROR); |
| } |
| else |
| { |
| // Nothing to send! |
| } |
| } |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleCapabilitiesRequestReceived(PacketBuffer * data) |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| BleTransportCapabilitiesRequestMessage req; |
| BleTransportCapabilitiesResponseMessage resp; |
| PacketBuffer * responseBuf = NULL; |
| uint16_t mtu; |
| |
| VerifyOrExit(data != NULL, err = BLE_ERROR_BAD_ARGS); |
| |
| mState = kState_Connecting; |
| |
| // Decode BTP capabilities request. |
| err = BleTransportCapabilitiesRequestMessage::Decode((*data), req); |
| SuccessOrExit(err); |
| |
| responseBuf = PacketBuffer::New(); |
| VerifyOrExit(responseBuf != NULL, err = BLE_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 = |
| chip::min(static_cast<uint16_t>(mtu - 3), CHIPoBle::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", CHIPoBle::sDefaultFragmentSize); |
| resp.mFragmentSize = CHIPoBle::sDefaultFragmentSize; |
| } |
| |
| // Select local and remote max receive window size based on local resources available for both incoming writes AND |
| // GATT confirmations. |
| mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize = |
| chip::min(req.mWindowSize, static_cast<uint8_t>(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 if ((resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V1) || |
| (resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V2)) |
| { |
| // Set Rx and Tx fragment sizes to the same value |
| mCHIPoBle.SetRxFragmentSize(resp.mFragmentSize); |
| mCHIPoBle.SetTxFragmentSize(resp.mFragmentSize); |
| } |
| else // resp.SelectedProtocolVersion >= kBleTransportProtocolVersion_V3 |
| { |
| // This is the peripheral, so set Rx fragment size, and leave Tx at default |
| mCHIPoBle.SetRxFragmentSize(resp.mFragmentSize); |
| } |
| ChipLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mCHIPoBle.GetRxFragmentSize(), mCHIPoBle.GetTxFragmentSize()); |
| |
| err = resp.Encode(responseBuf); |
| SuccessOrExit(err); |
| |
| // Stash capabilities response payload and wait for subscription from central. |
| QueueTx(responseBuf, kType_Data); |
| responseBuf = NULL; |
| |
| // Start receive timer. Canceled when end point freed or connection established. |
| err = StartReceiveConnectionTimer(); |
| SuccessOrExit(err); |
| |
| exit: |
| if (responseBuf != NULL) |
| { |
| PacketBuffer::Free(responseBuf); |
| } |
| |
| if (data != NULL) |
| { |
| PacketBuffer::Free(data); |
| } |
| |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::HandleCapabilitiesResponseReceived(PacketBuffer * data) |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| BleTransportCapabilitiesResponseMessage resp; |
| |
| VerifyOrExit(data != NULL, err = BLE_ERROR_BAD_ARGS); |
| |
| // Decode BTP capabilities response. |
| err = BleTransportCapabilitiesResponseMessage::Decode((*data), resp); |
| SuccessOrExit(err); |
| |
| VerifyOrExit(resp.mFragmentSize > 0, err = 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)) |
| { |
| err = BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS; |
| ExitNow(); |
| } |
| |
| // Set fragment size as minimum of (reported ATT MTU, CHIPoBLE characteristic size) |
| resp.mFragmentSize = chip::min(resp.mFragmentSize, CHIPoBle::sMaxFragmentSize); |
| |
| if ((resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V1) || |
| (resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V2)) |
| { |
| mCHIPoBle.SetRxFragmentSize(resp.mFragmentSize); |
| mCHIPoBle.SetTxFragmentSize(resp.mFragmentSize); |
| } |
| else // resp.SelectedProtocolVersion >= kBleTransportProtocolVersion_V3 |
| { |
| // This is the central, so set Tx fragement size, and leave Rx at default. |
| mCHIPoBle.SetTxFragmentSize(resp.mFragmentSize); |
| } |
| ChipLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mCHIPoBle.GetRxFragmentSize(), mCHIPoBle.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 -= 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. |
| err = StartSendAckTimer(); |
| SuccessOrExit(err); |
| |
| // We've sent a capabilities request write and received a compatible response, so the connect |
| // operation has completed successfully. |
| err = HandleConnectComplete(); |
| SuccessOrExit(err); |
| |
| exit: |
| if (data != NULL) |
| { |
| PacketBuffer::Free(data); |
| } |
| |
| return err; |
| } |
| |
| // 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 = 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 (newRemoteWindowBoundary - (newestUnackedSentSeqNum + UINT8_MAX)); |
| } |
| else |
| { |
| // Neither values would or have wrapped, OR new boundary WOULD wrap but latest unacked seq num does not, so no |
| // offset required. |
| return (newRemoteWindowBoundary - newestUnackedSentSeqNum); |
| } |
| } |
| |
| BLE_ERROR BLEEndPoint::Receive(PacketBuffer * data) |
| { |
| ChipLogDebugBleEndPoint(Ble, "+++++++++++++++++++++ entered receive"); |
| BLE_ERROR err = BLE_NO_ERROR; |
| SequenceNumber_t receivedAck = 0; |
| uint8_t closeFlags = kBleCloseFlag_AbortTransmission; |
| bool didReceiveAck = false; |
| |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| if (mCHIPoBle.IsCommandPacket(data)) |
| { |
| ChipLogDebugBleEndPoint(Ble, "%s: Received Control frame: Flags %x", __FUNCTION__, *(data->Start())); |
| } |
| else |
| #endif |
| { // 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 (!GetFlag(mConnStateFlags, kConnState_CapabilitiesMsgReceived)) |
| { |
| 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 = BLE_ERROR_INCORRECT_STATE); |
| SetFlag(mConnStateFlags, kConnState_CapabilitiesMsgReceived, true); |
| |
| err = HandleCapabilitiesResponseReceived(data); |
| data = NULL; |
| 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 = BLE_ERROR_INCORRECT_STATE); |
| SetFlag(mConnStateFlags, kConnState_CapabilitiesMsgReceived, true); |
| |
| err = HandleCapabilitiesRequestReceived(data); |
| data = NULL; |
| |
| if (err != BLE_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 = BLE_ERROR_INCORRECT_STATE; |
| |
| ExitNow(); |
| } |
| |
| ChipLogDebugBleEndPoint(Ble, "CHIPoBLE about to rx characteristic, state before:"); |
| mCHIPoBle.LogStateDebug(); |
| |
| // Pass received packet into BTP protocol engine. |
| err = mCHIPoBle.HandleCharacteristicReceived(data, receivedAck, didReceiveAck); |
| data = NULL; // Buffer consumed by protocol engine; either freed or added to message reassembly area. |
| |
| ChipLogDebugBleEndPoint(Ble, "CHIPoBLE rx'd characteristic, state after:"); |
| mCHIPoBle.LogStateDebug(); |
| |
| SuccessOrExit(err); |
| |
| // Protocol engine accepted the fragment, so shrink local receive window counter by 1. |
| 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 neweset unacked sent fragment, stop ack received timer. |
| if (!mCHIPoBle.ExpectingAck()) |
| { |
| ChipLogDebugBleEndPoint(Ble, "got ack for last outstanding fragment"); |
| StopAckReceivedTimer(); |
| |
| if (mState == kState_Closing && mSendQueue == NULL && mCHIPoBle.TxState() == CHIPoBle::kState_Idle) |
| { |
| // If end point closing, got confirmation for last send, and waiting for last ack, finalize close. |
| FinalizeClose(mState, kBleCloseFlag_SuppressCallback, BLE_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, mCHIPoBle.GetNewestUnackedSentSequenceNumber(), mRemoteReceiveWindowSize, |
| mReceiveWindowMaxSize); |
| |
| // Open remote device's receive window according to sequence number it just acknowledged. |
| mRemoteReceiveWindowSize = |
| AdjustRemoteReceiveWindow(receivedAck, mReceiveWindowMaxSize, mCHIPoBle.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 (mCHIPoBle.HasUnackedData()) |
| { |
| if (mLocalReceiveWindowSize <= BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && |
| !GetFlag(mConnStateFlags, kConnState_GattOperationInFlight)) |
| { |
| 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 (mCHIPoBle.RxState() == CHIPoBle::kState_Complete) |
| { |
| // Take ownership of message PacketBuffer |
| PacketBuffer * full_packet = mCHIPoBle.RxPacket(); |
| mCHIPoBle.ClearRxPacket(); |
| |
| ChipLogDebugBleEndPoint(Ble, "reassembled whole msg, len = %d", full_packet->DataLength()); |
| |
| #if CHIP_ENABLE_CHIPOBLE_TEST |
| // If we have a control message received callback, and end point is not closing... |
| if (mCHIPoBle.RxPacketType() == kType_Control && OnCommandReceived && mState != kState_Closing) |
| { |
| ChipLogDebugBleEndPoint(Ble, "%s: calling OnCommandReceived, seq# %u, len = %u, type %u", __FUNCTION__, receivedAck, |
| full_packet->DataLength(), mCHIPoBle.RxPacketType()); |
| // Pass received control message up the stack. |
| mCHIPoBle.SetRxPacketSeq(receivedAck); |
| OnCommandReceived(this, full_packet); |
| } |
| else |
| #endif |
| // If we have a message received callback, and end point is not closing... |
| if (OnMessageReceived && mState != kState_Closing) |
| { |
| // Pass received message up the stack. |
| OnMessageReceived(this, full_packet); |
| } |
| else |
| { |
| // Free received message if there's no one to own it. |
| PacketBuffer::Free(full_packet); |
| } |
| } |
| |
| exit: |
| if (data != NULL) |
| { |
| PacketBuffer::Free(data); |
| } |
| |
| if (err != BLE_NO_ERROR) |
| { |
| DoClose(closeFlags, err); |
| } |
| |
| return err; |
| } |
| |
| bool BLEEndPoint::SendWrite(PacketBuffer * buf) |
| { |
| // Add reference to message fragment for duration of platform's GATT write attempt. chip retains partial |
| // ownership of message fragment's PacketBuffer, since this is the same buffer as that of the whole message, just |
| // with a fragmenter-modified payload offset and data length. Buffer must be decref'd (i.e. PacketBuffer::Free'd) by |
| // platform when BLE GATT operation completes. |
| buf->AddRef(); |
| |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, true); |
| |
| return mBle->mPlatformDelegate->SendWriteRequest(mConnObj, &CHIP_BLE_SVC_ID, &mBle->CHIP_BLE_CHAR_1_ID, buf); |
| } |
| |
| bool BLEEndPoint::SendIndication(PacketBuffer * buf) |
| { |
| // Add reference to message fragment for duration of platform's GATT indication attempt. chip retains partial |
| // ownership of message fragment's PacketBuffer, since this is the same buffer as that of the whole message, just |
| // with a fragmenter-modified payload offset and data length. Buffer must be decref'd (i.e. PacketBuffer::Free'd) by |
| // platform when BLE GATT operation completes. |
| buf->AddRef(); |
| |
| SetFlag(mConnStateFlags, kConnState_GattOperationInFlight, true); |
| |
| return mBle->mPlatformDelegate->SendIndication(mConnObj, &CHIP_BLE_SVC_ID, &mBle->CHIP_BLE_CHAR_2_ID, buf); |
| } |
| |
| BLE_ERROR BLEEndPoint::StartConnectTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| chip::System::Error timerErr; |
| |
| timerErr = mBle->mSystemLayer->StartTimer(BLE_CONNECT_TIMEOUT_MS, HandleConnectTimeout, this); |
| VerifyOrExit(timerErr == CHIP_SYSTEM_NO_ERROR, err = BLE_ERROR_START_TIMER_FAILED); |
| SetFlag(mTimerStateFlags, kTimerState_ConnectTimerRunning, true); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::StartReceiveConnectionTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| chip::System::Error timerErr; |
| |
| timerErr = mBle->mSystemLayer->StartTimer(BLE_CONNECT_TIMEOUT_MS, HandleReceiveConnectionTimeout, this); |
| VerifyOrExit(timerErr == CHIP_SYSTEM_NO_ERROR, err = BLE_ERROR_START_TIMER_FAILED); |
| SetFlag(mTimerStateFlags, kTimerState_ReceiveConnectionTimerRunning, true); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::StartAckReceivedTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| chip::System::Error timerErr; |
| |
| if (!GetFlag(mTimerStateFlags, kTimerState_AckReceivedTimerRunning)) |
| { |
| timerErr = mBle->mSystemLayer->StartTimer(BTP_ACK_RECEIVED_TIMEOUT_MS, HandleAckReceivedTimeout, this); |
| VerifyOrExit(timerErr == CHIP_SYSTEM_NO_ERROR, err = BLE_ERROR_START_TIMER_FAILED); |
| |
| SetFlag(mTimerStateFlags, kTimerState_AckReceivedTimerRunning, true); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::RestartAckReceivedTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| |
| VerifyOrExit(GetFlag(mTimerStateFlags, kTimerState_AckReceivedTimerRunning), err = BLE_ERROR_INCORRECT_STATE); |
| |
| StopAckReceivedTimer(); |
| |
| err = StartAckReceivedTimer(); |
| SuccessOrExit(err); |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::StartSendAckTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| chip::System::Error timerErr; |
| |
| ChipLogDebugBleEndPoint(Ble, "entered StartSendAckTimer"); |
| |
| if (!GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning)) |
| { |
| ChipLogDebugBleEndPoint(Ble, "starting new SendAckTimer"); |
| timerErr = mBle->mSystemLayer->StartTimer(BTP_ACK_SEND_TIMEOUT_MS, HandleSendAckTimeout, this); |
| VerifyOrExit(timerErr == CHIP_SYSTEM_NO_ERROR, err = BLE_ERROR_START_TIMER_FAILED); |
| |
| SetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning, true); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| BLE_ERROR BLEEndPoint::StartUnsubscribeTimer() |
| { |
| BLE_ERROR err = BLE_NO_ERROR; |
| chip::System::Error timerErr; |
| |
| timerErr = mBle->mSystemLayer->StartTimer(BLE_UNSUBSCRIBE_TIMEOUT_MS, HandleUnsubscribeTimeout, this); |
| VerifyOrExit(timerErr == CHIP_SYSTEM_NO_ERROR, err = BLE_ERROR_START_TIMER_FAILED); |
| SetFlag(mTimerStateFlags, kTimerState_UnsubscribeTimerRunning, true); |
| |
| exit: |
| return err; |
| } |
| |
| void BLEEndPoint::StopConnectTimer() |
| { |
| // Cancel any existing connect timer. |
| mBle->mSystemLayer->CancelTimer(HandleConnectTimeout, this); |
| SetFlag(mTimerStateFlags, kTimerState_ConnectTimerRunning, false); |
| } |
| |
| void BLEEndPoint::StopReceiveConnectionTimer() |
| { |
| // Cancel any existing receive connection timer. |
| mBle->mSystemLayer->CancelTimer(HandleReceiveConnectionTimeout, this); |
| SetFlag(mTimerStateFlags, kTimerState_ReceiveConnectionTimerRunning, false); |
| } |
| |
| void BLEEndPoint::StopAckReceivedTimer() |
| { |
| // Cancel any existing ack-received timer. |
| mBle->mSystemLayer->CancelTimer(HandleAckReceivedTimeout, this); |
| SetFlag(mTimerStateFlags, kTimerState_AckReceivedTimerRunning, false); |
| } |
| |
| void BLEEndPoint::StopSendAckTimer() |
| { |
| // Cancel any existing send-ack timer. |
| mBle->mSystemLayer->CancelTimer(HandleSendAckTimeout, this); |
| SetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning, false); |
| } |
| |
| void BLEEndPoint::StopUnsubscribeTimer() |
| { |
| // Cancel any existing unsubscribe timer. |
| mBle->mSystemLayer->CancelTimer(HandleUnsubscribeTimeout, this); |
| SetFlag(mTimerStateFlags, kTimerState_UnsubscribeTimerRunning, false); |
| } |
| |
| void BLEEndPoint::HandleConnectTimeout(chip::System::Layer * systemLayer, void * appState, chip::System::Error err) |
| { |
| BLEEndPoint * ep = static_cast<BLEEndPoint *>(appState); |
| |
| // Check for event-based timer race condition. |
| if (GetFlag(ep->mTimerStateFlags, kTimerState_ConnectTimerRunning)) |
| { |
| ChipLogError(Ble, "connect handshake timed out, closing ep %p", ep); |
| SetFlag(ep->mTimerStateFlags, kTimerState_ConnectTimerRunning, false); |
| ep->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_CONNECT_TIMED_OUT); |
| } |
| } |
| |
| void BLEEndPoint::HandleReceiveConnectionTimeout(chip::System::Layer * systemLayer, void * appState, chip::System::Error err) |
| { |
| BLEEndPoint * ep = static_cast<BLEEndPoint *>(appState); |
| |
| // Check for event-based timer race condition. |
| if (GetFlag(ep->mTimerStateFlags, kTimerState_ReceiveConnectionTimerRunning)) |
| { |
| ChipLogError(Ble, "receive handshake timed out, closing ep %p", ep); |
| SetFlag(ep->mTimerStateFlags, kTimerState_ReceiveConnectionTimerRunning, false); |
| ep->DoClose(kBleCloseFlag_SuppressCallback | kBleCloseFlag_AbortTransmission, BLE_ERROR_RECEIVE_TIMED_OUT); |
| } |
| } |
| |
| void BLEEndPoint::HandleAckReceivedTimeout(chip::System::Layer * systemLayer, void * appState, chip::System::Error err) |
| { |
| BLEEndPoint * ep = static_cast<BLEEndPoint *>(appState); |
| |
| // Check for event-based timer race condition. |
| if (GetFlag(ep->mTimerStateFlags, kTimerState_AckReceivedTimerRunning)) |
| { |
| ChipLogError(Ble, "ack recv timeout, closing ep %p", ep); |
| ep->mCHIPoBle.LogStateDebug(); |
| SetFlag(ep->mTimerStateFlags, kTimerState_AckReceivedTimerRunning, false); |
| ep->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_FRAGMENT_ACK_TIMED_OUT); |
| } |
| } |
| |
| void BLEEndPoint::HandleSendAckTimeout(chip::System::Layer * systemLayer, void * appState, chip::System::Error err) |
| { |
| BLEEndPoint * ep = static_cast<BLEEndPoint *>(appState); |
| |
| // Check for event-based timer race condition. |
| if (GetFlag(ep->mTimerStateFlags, kTimerState_SendAckTimerRunning)) |
| { |
| SetFlag(ep->mTimerStateFlags, kTimerState_SendAckTimerRunning, false); |
| |
| // If previous stand-alone ack isn't still in flight... |
| if (!GetFlag(ep->mConnStateFlags, kConnState_StandAloneAckInFlight)) |
| { |
| BLE_ERROR sendErr = ep->DriveStandAloneAck(); |
| |
| if (sendErr != BLE_NO_ERROR) |
| { |
| ep->DoClose(kBleCloseFlag_AbortTransmission, sendErr); |
| } |
| } |
| } |
| } |
| |
| void BLEEndPoint::HandleUnsubscribeTimeout(chip::System::Layer * systemLayer, void * appState, chip::System::Error err) |
| { |
| BLEEndPoint * ep = static_cast<BLEEndPoint *>(appState); |
| |
| // Check for event-based timer race condition. |
| if (GetFlag(ep->mTimerStateFlags, kTimerState_UnsubscribeTimerRunning)) |
| { |
| ChipLogError(Ble, "unsubscribe timed out, ble ep %p", ep); |
| SetFlag(ep->mTimerStateFlags, kTimerState_UnsubscribeTimerRunning, false); |
| ep->HandleUnsubscribeComplete(); |
| } |
| } |
| |
| } /* namespace Ble */ |
| } /* namespace chip */ |
| |
| #endif /* CONFIG_NETWORK_LAYER_BLE */ |