diff --git a/src/ble/BLEEndPoint.cpp b/src/ble/BLEEndPoint.cpp new file mode 100644 index 00000000000000..cb437dcc1f1a61 --- /dev/null +++ b/src/ble/BLEEndPoint.cpp @@ -0,0 +1,1748 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 Weave over Bluetooth Low Energy (WoBLE) + * Bluetooth Transport Protocol (BTP). + * + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#include +#include + +#include + +#if CONFIG_NETWORK_LAYER_BLE +#include +#include +#include +#include +#include + +#include +#include +#include +#if WEAVE_ENABLE_WOBLE_TEST +#include "WoBleTest.h" +#endif + +// clang-format off + +// Define below to enable extremely verbose, BLE end point-specific debug logging. +#undef NL_BLE_END_POINT_DEBUG_LOGGING_ENABLED + +#ifdef NL_BLE_END_POINT_DEBUG_LOGGING_ENABLED +#define WeaveLogDebugBleEndPoint(MOD, MSG, ...) WeaveLogError(MOD, MSG, ## __VA_ARGS__) +#else +#define WeaveLogDebugBleEndPoint(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 nl { +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 = NL_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - NL_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, NL_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; + + WeaveLogDebugBleEndPoint(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->OnWeaveBleConnectReceived != NULL) + { + // Indicate BLE transport protocol connection received to next-higher layer. + mBle->OnWeaveBleConnectReceived(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 WEAVE_ENABLE_WOBLE_TEST + VerifyOrExit(mWoBle.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(); + + WeaveLogError(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; + WeaveLogDebugBleEndPoint(Ble, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize); + + // Start ack recvd timer for handshake indication. + err = StartAckReceivedTimer(); + SuccessOrExit(err); + + WeaveLogDebugBleEndPoint(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() +{ + WeaveLogProgress(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 WEAVE_ENABLE_WOBLE_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 WEAVE_ENABLE_WOBLE_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 (mWoBle.TxState() == WoBle::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 WoBLE stack, or once the remote stack + // closes the WoBLE 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 WEAVE_ENABLE_WOBLE_TEST + PacketBuffer::Free(mWoBleTest.mCommandReceiveQueue); + mWoBleTest.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 WeaveConnection 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, &WEAVE_BLE_SVC_ID, &mBle->WEAVE_BLE_CHAR_2_ID)) + { + WeaveLogError(Ble, "WoBle 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)) + { + WeaveLogProgress(Ble, "Auto-closing end point's BLE connection."); + mBle->mPlatformDelegate->CloseConnection(mConnObj); + } + else + { + WeaveLogProgress(Ble, "Releasing end point's BLE connection back to application."); + mBle->mApplicationDelegate->NotifyWeaveConnectionClosed(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 Weave 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. + FreeWoBle(); + + // Clear pending ack buffer, if any. + PacketBuffer::Free(mAckToSend); + + // Cancel all timers. + StopConnectTimer(); + StopReceiveConnectionTimer(); + StopAckReceivedTimer(); + StopSendAckTimer(); + StopUnsubscribeTimer(); +#if WEAVE_ENABLE_WOBLE_TEST + mWoBleTest.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::FreeWoBle() +{ + PacketBuffer * buf; + + // Free transmit disassembly buffer + buf = mWoBle.TxPacket(); + mWoBle.ClearTxPacket(); + PacketBuffer::Free(buf); + + // Free receive reassembly buffer + buf = mWoBle.RxPacket(); + mWoBle.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 Weave::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 = mWoBle.Init(this, expectInitialAck); + if (err != BLE_NO_ERROR) + { + WeaveLogError(Ble, "WoBle init failed"); + ExitNow(); + } + +#if WEAVE_ENABLE_WOBLE_TEST + err = (BLE_ERROR) mTxQueueMutex.Init(mTxQueueMutex); + if (err != BLE_NO_ERROR) + { + WeaveLogError(Ble, "%s: Mutex init failed", __FUNCTION__); + ExitNow(); + } + err = mWoBleTest.Init(this); + if (err != BLE_NO_ERROR) + { + WeaveLogError(Ble, "WoBleTest 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; + + WeaveLogDebugBleEndPoint(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; + WeaveLogDebugBleEndPoint(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; + WeaveLogDebugBleEndPoint(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 WEAVE_ENABLE_WOBLE_TEST + WeaveLogDebugBleEndPoint(Ble, "%s: data->%p, type %d, len %d", __FUNCTION__, data, type, data->DataLength()); + mWoBle.PushPacketTag(data, type); +#endif + + QueueTxLock(); + + if (mSendQueue == NULL) + { + mSendQueue = data; + WeaveLogDebugBleEndPoint(Ble, "%s: Set data as new mSendQueue %p, type %d", __FUNCTION__, mSendQueue, type); + } + else + { + mSendQueue->AddToEnd(data); + WeaveLogDebugBleEndPoint(Ble, "%s: Append data to mSendQueue %p, type %d", __FUNCTION__, mSendQueue, type); + } + + QueueTxUnlock(); +} + +BLE_ERROR BLEEndPoint::Send(PacketBuffer * data) +{ + WeaveLogDebugBleEndPoint(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: + WeaveLogDebugBleEndPoint(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; + WeaveLogDebugBleEndPoint(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 mWoBle.HandleCharacteristicSend(data, sentAck); +} + +BLE_ERROR BLEEndPoint::SendNextMessage() +{ + BLE_ERROR err = BLE_NO_ERROR; + bool sentAck; + + // Get the first queued packet to send + QueueTxLock(); +#if WEAVE_ENABLE_WOBLE_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 WEAVE_ENABLE_WOBLE_TEST + // Get and consume the packet tag in message buffer + PacketType_t type = mWoBle.PopPacketTag(data); + mWoBle.SetTxPacketType(type); + mWoBleTest.DoTxTiming(data, WOBLE_TX_START); +#endif + + // Hand whole message payload to the fragmenter. + VerifyOrExit(PrepareNextFragment(data, sentAck), err = BLE_ERROR_WOBLE_PROTOCOL_ABORT); + data = NULL; // Ownership passed to fragmenter's tx buf on PrepareNextFragment success. + + // Send first message fragment over the air. + WEAVE_FAULT_INJECT(nl::Weave::FaultInjection::kFault_WOBLESend, + { + if (mRole == kBleRole_Central) + { + err = BLE_ERROR_GATT_WRITE_FAILED; + } else { + err = BLE_ERROR_GATT_INDICATE_FAILED; + } + ExitNow(); + } + ); + err = SendCharacteristic(mWoBle.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 + WeaveLogError(Ble, "btp fragmenter error on send!"); + mWoBle.LogState(); + + err = BLE_ERROR_WOBLE_PROTOCOL_ABORT; + ExitNow(); + } + + err = SendCharacteristic(mWoBle.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() +{ + WeaveLogDebugBleEndPoint(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, &WEAVE_BLE_SVC_ID, &mBle->WEAVE_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 + { + WeaveLogDebugBleEndPoint(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: + WeaveLogDebugBleEndPoint(Ble, "exiting HandleHandshakeConfirmationReceived"); + + if (err != BLE_NO_ERROR) + { + DoClose(closeFlags, err); + } + + return err; +} + +BLE_ERROR BLEEndPoint::HandleFragmentConfirmationReceived() +{ + BLE_ERROR err = BLE_NO_ERROR; + + WeaveLogDebugBleEndPoint(Ble, "entered HandleFragmentConfirmationReceived"); + + // Suppress error logging if GATT confirmation overlaps with unsubscribe on final close. + if (IsUnsubscribePending()) + { + WeaveLogDebugBleEndPoint(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 || mWoBle.TxState() == WoBle::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() +{ + WeaveLogDebugBleEndPoint(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() +{ + WeaveLogDebugBleEndPoint(Ble, "entered DoSendStandAloneAck; sending stand-alone ack"); + + // Encode and transmit stand-alone ack. + mWoBle.EncodeStandAloneAck(mAckToSend); + BLE_ERROR err = SendCharacteristic(mAckToSend); + SuccessOrExit(err); + + // Reset local receive window counter. + mLocalReceiveWindowSize = mReceiveWindowMaxSize; + WeaveLogDebugBleEndPoint(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; + + WeaveLogDebugBleEndPoint(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 NL_BLE_END_POINT_DEBUG_LOGGING_ENABLED + if (mRemoteReceiveWindowSize <= BTP_WINDOW_NO_ACK_SEND_THRESHOLD && + !GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning) && mAckToSend == NULL) + { + WeaveLogDebugBleEndPoint(Ble, "NO SEND: receive window almost closed, and no ack to send"); + } + + if (mRemoteReceiveWindowSize == 0) + { + WeaveLogDebugBleEndPoint(Ble, "NO SEND: remote receive window closed"); + } + + if (GetFlag(mConnStateFlags, kConnState_GattOperationInFlight)) + { + WeaveLogDebugBleEndPoint(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 (mWoBle.TxState() == WoBle::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 (mWoBle.TxState() == WoBle::kState_InProgress) + { + // Send next fragment of message currently held by fragmenter. + err = ContinueMessageSend(); + SuccessOrExit(err); + } + else if (mWoBle.TxState() == WoBle::kState_Complete) + { + // Clear fragmenter's pointer to sent message buffer and reset its Tx state. + PacketBuffer * sentBuf = mWoBle.TxPacket(); +#if WEAVE_ENABLE_WOBLE_TEST + mWoBleTest.DoTxTiming(sentBuf, WOBLE_TX_DONE); +#endif // WEAVE_ENABLE_WOBLE_TEST + mWoBle.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 && !mWoBle.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 = + nl::Weave::min(static_cast(mtu - 3), WoBle::sMaxFragmentSize); // Reserve 3 bytes of MTU for ATT header. + } + else // Else, if neither device knows MTU... + { + WeaveLogProgress(Ble, "cannot determine ATT MTU; selecting default fragment size = %u", WoBle::sDefaultFragmentSize); + resp.mFragmentSize = WoBle::sDefaultFragmentSize; + } + + // Select local and remote max receive window size based on local resources available for both incoming writes AND + // GATT confirmations. + mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize = + nl::Weave::min(req.mWindowSize, static_cast(BLE_MAX_RECEIVE_WINDOW_SIZE)); + resp.mWindowSize = mReceiveWindowMaxSize; + + WeaveLogProgress(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); + WeaveLogProgress(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. + WeaveLogError(Ble, "incompatible BTP versions; peripheral expected between %d and %d", + NL_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION, NL_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 + mWoBle.SetRxFragmentSize(resp.mFragmentSize); + mWoBle.SetTxFragmentSize(resp.mFragmentSize); + } + else // resp.SelectedProtocolVersion >= kBleTransportProtocolVersion_V3 + { + // This is the peripheral, so set Rx fragment size, and leave Tx at default + mWoBle.SetRxFragmentSize(resp.mFragmentSize); + } + WeaveLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mWoBle.GetRxFragmentSize(), mWoBle.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); + + WeaveLogProgress(Ble, "peripheral chose BTP version %d; central expected between %d and %d", resp.mSelectedProtocolVersion, + NL_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION, NL_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION); + + if ((resp.mSelectedProtocolVersion < NL_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION) || + (resp.mSelectedProtocolVersion > NL_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION)) + { + err = BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS; + ExitNow(); + } + + // Set fragment size as minimum of (reported ATT MTU, WoBLE characteristic size) + resp.mFragmentSize = nl::Weave::min(resp.mFragmentSize, WoBle::sMaxFragmentSize); + + if ((resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V1) || + (resp.mSelectedProtocolVersion == kBleTransportProtocolVersion_V2)) + { + mWoBle.SetRxFragmentSize(resp.mFragmentSize); + mWoBle.SetTxFragmentSize(resp.mFragmentSize); + } + else // resp.SelectedProtocolVersion >= kBleTransportProtocolVersion_V3 + { + // This is the central, so set Tx fragement size, and leave Rx at default. + mWoBle.SetTxFragmentSize(resp.mFragmentSize); + } + WeaveLogProgress(Ble, "using BTP fragment sizes rx %d / tx %d.", mWoBle.GetRxFragmentSize(), mWoBle.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; + + WeaveLogProgress(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; + WeaveLogDebugBleEndPoint(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) +{ + WeaveLogDebugBleEndPoint(Ble, "+++++++++++++++++++++ entered receive"); + BLE_ERROR err = BLE_NO_ERROR; + SequenceNumber_t receivedAck = 0; + uint8_t closeFlags = kBleCloseFlag_AbortTransmission; + bool didReceiveAck = false; + +#if WEAVE_ENABLE_WOBLE_TEST + if (mWoBle.IsCommandPacket(data)) + { + WeaveLogDebugBleEndPoint(Ble, "%s: Received Control frame: Flags %x", __FUNCTION__, *(data->Start())); + } + else +#endif + { // This is a special handling on the first Woble data packet, the CapabilitiesRequest. + // Suppress error logging if peer's send overlaps with our unsubscribe on final close. + if (IsUnsubscribePending()) + { + WeaveLogDebugBleEndPoint(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 + + WeaveLogDebugBleEndPoint(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)) + { + WeaveLogError(Ble, "ep rx'd packet in bad state"); + err = BLE_ERROR_INCORRECT_STATE; + + ExitNow(); + } + + WeaveLogDebugBleEndPoint(Ble, "woble about to rx characteristic, state before:"); + mWoBle.LogStateDebug(); + + // Pass received packet into BTP protocol engine. + err = mWoBle.HandleCharacteristicReceived(data, receivedAck, didReceiveAck); + data = NULL; // Buffer consumed by protocol engine; either freed or added to message reassembly area. + + WeaveLogDebugBleEndPoint(Ble, "woble rx'd characteristic, state after:"); + mWoBle.LogStateDebug(); + + SuccessOrExit(err); + + // Protocol engine accepted the fragment, so shrink local receive window counter by 1. + mLocalReceiveWindowSize -= 1; + WeaveLogDebugBleEndPoint(Ble, "decremented local rx window, new size = %u", mLocalReceiveWindowSize); + + // Respond to received ack, if any. + if (didReceiveAck) + { + WeaveLogDebugBleEndPoint(Ble, "got btp ack = %u", receivedAck); + + // If ack was rx'd for neweset unacked sent fragment, stop ack received timer. + if (!mWoBle.ExpectingAck()) + { + WeaveLogDebugBleEndPoint(Ble, "got ack for last outstanding fragment"); + StopAckReceivedTimer(); + + if (mState == kState_Closing && mSendQueue == NULL && mWoBle.TxState() == WoBle::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. + { + WeaveLogDebugBleEndPoint(Ble, "still expecting ack(s), restarting timer..."); + err = RestartAckReceivedTimer(); + SuccessOrExit(err); + } + + WeaveLogDebugBleEndPoint(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, mWoBle.GetNewestUnackedSentSequenceNumber(), mRemoteReceiveWindowSize, + mReceiveWindowMaxSize); + + // Open remote device's receive window according to sequence number it just acknowledged. + mRemoteReceiveWindowSize = + AdjustRemoteReceiveWindow(receivedAck, mReceiveWindowMaxSize, mWoBle.GetNewestUnackedSentSequenceNumber()); + + WeaveLogDebugBleEndPoint(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 (mWoBle.HasUnackedData()) + { + if (mLocalReceiveWindowSize <= BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && + !GetFlag(mConnStateFlags, kConnState_GattOperationInFlight)) + { + WeaveLogDebugBleEndPoint(Ble, "sending immediate ack"); + err = DriveStandAloneAck(); + SuccessOrExit(err); + } + else + { + WeaveLogDebugBleEndPoint(Ble, "starting send-ack timer"); + + // Send ack when timer expires. + err = StartSendAckTimer(); + SuccessOrExit(err); + } + } + + // If we've reassembled a whole message... + if (mWoBle.RxState() == WoBle::kState_Complete) + { + // Take ownership of message PacketBuffer + PacketBuffer * full_packet = mWoBle.RxPacket(); + mWoBle.ClearRxPacket(); + + WeaveLogDebugBleEndPoint(Ble, "reassembled whole msg, len = %d", full_packet->DataLength()); + +#if WEAVE_ENABLE_WOBLE_TEST + // If we have a control message received callback, and end point is not closing... + if (mWoBle.RxPacketType() == kType_Control && OnCommandReceived && mState != kState_Closing) + { + WeaveLogDebugBleEndPoint(Ble, "%s: calling OnCommandReceived, seq# %u, len = %u, type %u", __FUNCTION__, receivedAck, + full_packet->DataLength(), mWoBle.RxPacketType()); + // Pass received control message up the stack. + mWoBle.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. Weave 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, &WEAVE_BLE_SVC_ID, &mBle->WEAVE_BLE_CHAR_1_ID, buf); +} + +bool BLEEndPoint::SendIndication(PacketBuffer * buf) +{ + // Add reference to message fragment for duration of platform's GATT indication attempt. Weave 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, &WEAVE_BLE_SVC_ID, &mBle->WEAVE_BLE_CHAR_2_ID, buf); +} + +BLE_ERROR BLEEndPoint::StartConnectTimer() +{ + BLE_ERROR err = BLE_NO_ERROR; + Weave::System::Error timerErr; + + timerErr = mBle->mSystemLayer->StartTimer(BLE_CONNECT_TIMEOUT_MS, HandleConnectTimeout, this); + VerifyOrExit(timerErr == WEAVE_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; + Weave::System::Error timerErr; + + timerErr = mBle->mSystemLayer->StartTimer(BLE_CONNECT_TIMEOUT_MS, HandleReceiveConnectionTimeout, this); + VerifyOrExit(timerErr == WEAVE_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; + Weave::System::Error timerErr; + + if (!GetFlag(mTimerStateFlags, kTimerState_AckReceivedTimerRunning)) + { + timerErr = mBle->mSystemLayer->StartTimer(BTP_ACK_RECEIVED_TIMEOUT_MS, HandleAckReceivedTimeout, this); + VerifyOrExit(timerErr == WEAVE_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; + Weave::System::Error timerErr; + + WeaveLogDebugBleEndPoint(Ble, "entered StartSendAckTimer"); + + if (!GetFlag(mTimerStateFlags, kTimerState_SendAckTimerRunning)) + { + WeaveLogDebugBleEndPoint(Ble, "starting new SendAckTimer"); + timerErr = mBle->mSystemLayer->StartTimer(BTP_ACK_SEND_TIMEOUT_MS, HandleSendAckTimeout, this); + VerifyOrExit(timerErr == WEAVE_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; + Weave::System::Error timerErr; + + timerErr = mBle->mSystemLayer->StartTimer(BLE_UNSUBSCRIBE_TIMEOUT_MS, HandleUnsubscribeTimeout, this); + VerifyOrExit(timerErr == WEAVE_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(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err) +{ + BLEEndPoint * ep = static_cast(appState); + + // Check for event-based timer race condition. + if (GetFlag(ep->mTimerStateFlags, kTimerState_ConnectTimerRunning)) + { + WeaveLogError(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(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err) +{ + BLEEndPoint * ep = static_cast(appState); + + // Check for event-based timer race condition. + if (GetFlag(ep->mTimerStateFlags, kTimerState_ReceiveConnectionTimerRunning)) + { + WeaveLogError(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(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err) +{ + BLEEndPoint * ep = static_cast(appState); + + // Check for event-based timer race condition. + if (GetFlag(ep->mTimerStateFlags, kTimerState_AckReceivedTimerRunning)) + { + WeaveLogError(Ble, "ack recv timeout, closing ep %p", ep); + ep->mWoBle.LogStateDebug(); + SetFlag(ep->mTimerStateFlags, kTimerState_AckReceivedTimerRunning, false); + ep->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_FRAGMENT_ACK_TIMED_OUT); + } +} + +void BLEEndPoint::HandleSendAckTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err) +{ + BLEEndPoint * ep = static_cast(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(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err) +{ + BLEEndPoint * ep = static_cast(appState); + + // Check for event-based timer race condition. + if (GetFlag(ep->mTimerStateFlags, kTimerState_UnsubscribeTimerRunning)) + { + WeaveLogError(Ble, "unsubscribe timed out, ble ep %p", ep); + SetFlag(ep->mTimerStateFlags, kTimerState_UnsubscribeTimerRunning, false); + ep->HandleUnsubscribeComplete(); + } +} + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* CONFIG_NETWORK_LAYER_BLE */ diff --git a/src/ble/BLEEndPoint.h b/src/ble/BLEEndPoint.h new file mode 100644 index 00000000000000..4894ef54b70822 --- /dev/null +++ b/src/ble/BLEEndPoint.h @@ -0,0 +1,235 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines a Bluetooth Low Energy (BLE) connection + * endpoint abstraction for the byte-streaming, + * connection-oriented Weave over Bluetooth Low Energy (WoBLE) + * Bluetooth Transport Protocol (BTP). + * + */ + +#ifndef BLEENDPOINT_H_ +#define BLEENDPOINT_H_ + +#include + +#include +#include +#if WEAVE_ENABLE_WOBLE_TEST +#include +#endif + +namespace nl { +namespace Ble { + +using ::nl::Weave::System::PacketBuffer; + +enum +{ + kBleCloseFlag_SuppressCallback = 0x01, + kBleCloseFlag_AbortTransmission = 0x02 +}; + +// Forward declarations +class BleLayer; +class BleEndPointPool; +#if WEAVE_ENABLE_WOBLE_TEST +class WoBleTest; +#endif + +class NL_DLL_EXPORT BLEEndPoint : public BleLayerObject +{ + friend class BleLayer; + friend class BleEndPointPool; +#if WEAVE_ENABLE_WOBLE_TEST + friend class WoBleTest; +#endif + +public: + typedef uint64_t AlignT; + + // Public data members: + enum + { + kState_Ready = 0, + kState_Connecting = 1, + kState_Aborting = 2, + kState_Connected = 3, + kState_Closing = 4, + kState_Closed = 5 + } mState; // [READ-ONLY] End point connection state. Refers to state of Weave over + // BLE transport protocol connection, not of underlying BLE connection. + + // Public function pointers: + typedef void (*OnConnectCompleteFunct)(BLEEndPoint * endPoint, BLE_ERROR err); + OnConnectCompleteFunct OnConnectComplete; + + typedef void (*OnMessageReceivedFunct)(BLEEndPoint * endPoint, PacketBuffer * msg); + OnMessageReceivedFunct OnMessageReceived; + + typedef void (*OnConnectionClosedFunct)(BLEEndPoint * endPoint, BLE_ERROR err); + OnConnectionClosedFunct OnConnectionClosed; + +#if WEAVE_ENABLE_WOBLE_TEST + typedef void (*OnCommandReceivedFunct)(BLEEndPoint * endPoint, PacketBuffer * msg); + OnCommandReceivedFunct OnCommandReceived; + inline void SetOnCommandReceivedCB(OnCommandReceivedFunct cb) { OnCommandReceived = cb; }; + WoBleTest mWoBleTest; + inline void SetTxWindowSize(uint8_t size) { mRemoteReceiveWindowSize = size; }; + inline void SetRxWindowSize(uint8_t size) { mReceiveWindowMaxSize = size; }; +#endif + +public: + // Public functions: + BLE_ERROR Send(PacketBuffer * data); + BLE_ERROR Receive(PacketBuffer * data); + BLE_ERROR StartConnect(void); + + bool IsUnsubscribePending(void) const; + void Close(void); + void Abort(void); + +private: + // Private data members: + enum ConnectionStateFlags + { + kConnState_AutoClose = 0x01, // End point should close underlying BLE conn on BTP close. + kConnState_CapabilitiesConfReceived = 0x02, // GATT confirmation received for sent capabilities req/resp. + kConnState_CapabilitiesMsgReceived = 0x04, // Capabilities request or response message received. + kConnState_DidBeginSubscribe = 0x08, // GATT subscribe request sent; must unsubscribe on close. + kConnState_StandAloneAckInFlight = 0x10, // Stand-alone ack in flight, awaiting GATT confirmation. + kConnState_GattOperationInFlight = 0x20 // GATT write, indication, subscribe, or unsubscribe in flight, + // awaiting GATT confirmation. + }; + + enum TimerStateFlags + { + kTimerState_ConnectTimerRunning = 0x01, // BTP connect completion timer running. + kTimerState_ReceiveConnectionTimerRunning = 0x02, // BTP receive connection completion timer running. + kTimerState_AckReceivedTimerRunning = 0x04, // Ack received timer running due to unacked sent fragment. + kTimerState_SendAckTimerRunning = 0x08, // Send ack timer running; indicates pending ack to send. + kTimerState_UnsubscribeTimerRunning = 0x10, // Unsubscribe completion timer running. +#if WEAVE_ENABLE_WOBLE_TEST + kTimerState_UnderTestTimerRunnung = 0x80 // running throughput Tx test +#endif + }; + + // BLE connection to which an end point is uniquely bound. Type BLE_CONNECTION_OBJECT is defined by the platform or + // void* by default. This object is passed back to the platform delegate with each call to send traffic over or + // modify the state of the underlying BLE connection. + BLE_CONNECTION_OBJECT mConnObj; + + // Queue of outgoing messages to send when current WoBle transmission completes. + // + // Re-used during connection setup to cache capabilities request and response payloads; payloads are freed when + // connection is established. + PacketBuffer * mSendQueue; + + // Pending stand-alone BTP acknolwedgement. Pre-empts regular send queue or fragmented message transmission in + // progress. + PacketBuffer * mAckToSend; + + WoBle mWoBle; + BleRole mRole; + uint8_t mConnStateFlags; + uint8_t mTimerStateFlags; + SequenceNumber_t mLocalReceiveWindowSize; + SequenceNumber_t mRemoteReceiveWindowSize; + SequenceNumber_t mReceiveWindowMaxSize; +#if WEAVE_ENABLE_WOBLE_TEST + nl::Weave::System::Mutex mTxQueueMutex; // For MT-safe Tx queuing +#endif + +private: + // Private functions: + BLEEndPoint(void); // not defined + ~BLEEndPoint(void); // not defined + + BLE_ERROR Init(BleLayer * bleLayer, BLE_CONNECTION_OBJECT connObj, BleRole role, bool autoClose); + bool IsConnected(uint8_t state) const; + void DoClose(uint8_t flags, BLE_ERROR err); + + // Transmit path: + BLE_ERROR DriveSending(void); + BLE_ERROR DriveStandAloneAck(void); + bool PrepareNextFragment(PacketBuffer * data, bool & sentAck); + BLE_ERROR SendNextMessage(void); + BLE_ERROR ContinueMessageSend(void); + BLE_ERROR DoSendStandAloneAck(void); + BLE_ERROR SendCharacteristic(PacketBuffer * buf); + bool SendIndication(PacketBuffer * buf); + bool SendWrite(PacketBuffer * buf); + + // Receive path: + BLE_ERROR HandleConnectComplete(void); + BLE_ERROR HandleReceiveConnectionComplete(void); + void HandleSubscribeReceived(void); + void HandleSubscribeComplete(void); + void HandleUnsubscribeComplete(void); + BLE_ERROR HandleGattSendConfirmationReceived(void); + BLE_ERROR HandleHandshakeConfirmationReceived(void); + BLE_ERROR HandleFragmentConfirmationReceived(void); + BLE_ERROR HandleCapabilitiesRequestReceived(PacketBuffer * data); + BLE_ERROR HandleCapabilitiesResponseReceived(PacketBuffer * data); + SequenceNumber_t AdjustRemoteReceiveWindow(SequenceNumber_t lastReceivedAck, SequenceNumber_t maxRemoteWindowSize, + SequenceNumber_t newestUnackedSentSeqNum); + + // Timer control functions: + BLE_ERROR StartConnectTimer(void); // Start connect timer. + BLE_ERROR StartReceiveConnectionTimer(void); // Start receive connection timer. + BLE_ERROR StartAckReceivedTimer(void); // Start ack-received timer if it's not already running. + BLE_ERROR RestartAckReceivedTimer(void); // Restart ack-received timer. + BLE_ERROR StartSendAckTimer(void); // Start send-ack timer if it's not already running. + BLE_ERROR StartUnsubscribeTimer(void); + void StopConnectTimer(void); // Stop connect timer. + void StopReceiveConnectionTimer(void); // Stop receive connection timer. + void StopAckReceivedTimer(void); // Stop ack-received timer. + void StopSendAckTimer(void); // Stop send-ack timer. + void StopUnsubscribeTimer(void); // Stop unsubscribe timer. + + // Timer expired callbacks: + static void HandleConnectTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err); + static void HandleReceiveConnectionTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err); + static void HandleAckReceivedTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err); + static void HandleSendAckTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err); + static void HandleUnsubscribeTimeout(Weave::System::Layer * systemLayer, void * appState, Weave::System::Error err); + + // Close functions: + void DoCloseCallback(uint8_t state, uint8_t flags, BLE_ERROR err); + void FinalizeClose(uint8_t state, uint8_t flags, BLE_ERROR err); + void ReleaseBleConnection(void); + void Free(void); + void FreeWoBle(void); + + // Mutex lock on Tx queue. Used only in WoBle test build for now. +#if WEAVE_ENABLE_WOBLE_TEST + inline void QueueTxLock() { mTxQueueMutex.Lock(); }; + inline void QueueTxUnlock() { mTxQueueMutex.Unlock(); }; +#else + inline void QueueTxLock() { }; + inline void QueueTxUnlock() { }; +#endif + void QueueTx(PacketBuffer * data, PacketType_t type); +}; + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* BLEENDPOINT_H_ */ diff --git a/src/ble/Ble.h b/src/ble/Ble.h new file mode 100644 index 00000000000000..a653bc0932a9f0 --- /dev/null +++ b/src/ble/Ble.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2015-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 is an umbrella header for the Ble library, a + * portable Bluetooth Low Energy (BLE), also known as Bluetooth + * Smart, layer for transporting Weave over a BLE connection. + * + */ + +#ifndef NL_BLE_H +#define NL_BLE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @namespace nl::Ble + * + * @brief + * This namespace includes all interfaces within Weave for + * Bluetooth Low Energy (BLE), also known as Bluetooth Smart. + */ + +#endif // NL_BLE_H diff --git a/src/ble/BleApplicationDelegate.h b/src/ble/BleApplicationDelegate.h new file mode 100644 index 00000000000000..c2f21114a43960 --- /dev/null +++ b/src/ble/BleApplicationDelegate.h @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines the interface for upcalls from BleLayer + * to a client application. + */ + +#ifndef BLEAPPLICATIONDELEGATE_H_ +#define BLEAPPLICATIONDELEGATE_H_ + +#include + +#include "BleConfig.h" + +namespace nl { +namespace Ble { + +// Platform-agnostic BLE interface +class NL_DLL_EXPORT BleApplicationDelegate +{ +public: + // Weave calls this function once it closes the last BLEEndPoint associated with a BLE given connection object. + // A call to this function means Weave no longer cares about the state of the given BLE connection. + // The application can use this callback to e.g. close the underlying BLE conection if it is no longer needed, + // decrement the connection's refcount if it has one, or perform any other sort of cleanup as desired. + virtual void NotifyWeaveConnectionClosed(BLE_CONNECTION_OBJECT connObj) = 0; +}; + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* BLEAPPLICATIONDELEGATE_H_ */ diff --git a/src/ble/BleConfig.h b/src/ble/BleConfig.h new file mode 100644 index 00000000000000..0a4bf56db32fce --- /dev/null +++ b/src/ble/BleConfig.h @@ -0,0 +1,254 @@ +/* + * + * Copyright (c) 2014-2018 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines default compile-time configuration constants + * for the Nest BleLayer, a Bluetooth Low Energy communications + * abstraction layer. + * + * Package integrators that wish to override these values should + * either use preprocessor definitions or create a project- + * specific BleProjectConfig.h header and then assert + * HAVE_BLEPROJECTCONFIG_H via the package configuration tool + * via --with-weave-ble-project-includes=DIR where DIR is the + * directory that contains the header. + * + * NOTE WELL: On some platforms, this header is included by C-language programs. + * + */ + +#ifndef BLECONFIG_H_ +#define BLECONFIG_H_ + +#include + +/* Include a project-specific configuration file, if defined. + * + * An application or module that incorporates Weave can define a project configuration + * file to override standard BLE Layer configuration with application-specific values. + * The project config file is typically located outside the OpenWeave source tree, + * alongside the source code for the application. + */ +#ifdef BLE_PROJECT_CONFIG_INCLUDE +#include BLE_PROJECT_CONFIG_INCLUDE +#endif + +/* Include a platform-specific configuration file, if defined. + * + * A platform configuration file contains overrides to standard BLE Layer configuration + * that are specific to the platform or OS on which Weave is running. It is typically + * provided as apart of an adaptation layer that adapts OpenWeave to the target + * environment. This adaptation layer may be included in the OpenWeave source tree + * itself or implemented externally. + */ +#ifdef BLE_PLATFORM_CONFIG_INCLUDE +#include BLE_PLATFORM_CONFIG_INCLUDE +#endif + +// clang-format off + +/** + * @def BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + * + * @brief + * This boolean configuration option is (1) if the obsolescent interfaces + * of the BLE layer that now reside elsewhere, for example, in the Weave System + * Layer or the INET layer, are aliased for transitional purposes. + * + */ +#ifndef BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES +#define BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES 0 +#endif // BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + +#if BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES +#if !WEAVE_SYSTEM_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES +#error "REQUIRED: if BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES then WEAVE_SYSTEM_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES!" +#endif // !WEAVE_SYSTEM_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + +#include +#endif // BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + +/** + * @def BLE_LAYER_NUM_BLE_ENDPOINTS + * + * @brief + * This defines the number of BLEEndPoint objects allocated for use by the + * BleLayer subsystem. Value should be defined as the minimum of (max number + * of simultaneous BLE connections the system supports, max number of + * simultaneous BLE connections the application will establish). + */ +#ifndef BLE_LAYER_NUM_BLE_ENDPOINTS +#define BLE_LAYER_NUM_BLE_ENDPOINTS 1 +#endif // BLE_LAYER_NUM_BLE_ENDPOINTS + +#if (BLE_LAYER_NUM_BLE_ENDPOINTS < 1) +#error "BLE_LAYER_NUM_BLE_ENDPOINTS must be greater than 0. configure options may be used to disable Weave over BLE." +#endif + +/** + * @def BLE_CONNECTION_OBJECT + * + * @brief + * This defines the type of BLE_CONNECTION_OBJECT parameters passed between + * BLE platform code and the BleLayer subsystem. + * + * This type must support operator == such that BLE_CONNECTION_OBJECT instances + * which refer to the same BLE connection are considered equivalent. + * + * Most platforms should be able to retain this type's default definition as + * (void *), and pass [pointers to] connection handles generated by their + * platform interface where BLE_CONNECTION_OBJECT arguments are required by + * BleLayer input functions. + * + */ +#ifndef BLE_CONNECTION_OBJECT +#define BLE_CONNECTION_OBJECT void* +#endif // BLE_CONNECTION_OBJECT + +/** + * @def BLE_CONFIG_BLUEZ_MTU_FEATURE + * + * @brief + * This define if BLUEZ MTU FEATURE is enabled or not + */ +#ifndef BLE_CONFIG_BLUEZ_MTU_FEATURE +#define BLE_CONFIG_BLUEZ_MTU_FEATURE 0 +#endif // BLE_CONFIG_BLUEZ_MTU_FEATURE + +/** + * @def BLE_CONNECTION_UNINITIALIZED + * + * @brief + * This defines the value of an uninitialized BLE_CONNECTION_OBJECT. + * + */ +#ifndef BLE_CONNECTION_UNINITIALIZED +#define BLE_CONNECTION_UNINITIALIZED NULL +#endif // BLE_CONNECTION_UNINITIALIZED + +/** + * @def BLE_READ_REQUEST_CONTEXT + * + * @brief + * This defines the type of BLE_READ_REQUEST_CONTEXT parameters passed between + * BLE platform code and the BleLayer subsystem. + * + * BLE_READ_REQUEST_CONTEXT objects are handed to BleLayer when a read request + * is received by the BLE platform. BleLayer hands these objects back to the + * appropriate platform delegate function when sending the read response. + * + */ +#ifndef BLE_READ_REQUEST_CONTEXT +#define BLE_READ_REQUEST_CONTEXT void* +#endif // BLE_READ_REQUEST_CONTEXT + +/** + * @def BLE_MAX_RECEIVE_WINDOW_SIZE + * + * @brief + * This is the maximum allowed size of a BLE end point's receive window, defined as the number of fragments the + * end point may reliably receive without BTP-layer acknowledgement. This value should be no larger than the floor + * of ONE-HALF the total number of slots or buffers reserved for GATT operations at any point along a platform's + * BLE pipeline. The BLE layer reserves all of these buffers for its own use - one half for incoming GATT writes or + * indications, and the other half for incoming GATT confirmations. + * + * This value must be greater than 1, or race condition avoidance logic will prevent send the on remote device. This + * logic prevents any send with no piggybacked ack when the receiver's window has only 1 slot open. Without this + * logic, simultaneous data transmissions could fill both receiver's windows, leaving no room for the acks required + * to re-open them. Both senders would wedge, and the BTP connection would stall. + * + * This value must also exceed (BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD + 1), or ***immediate*** stand-alone + * acks will forever be sent without delay in response to one another as each peer's window size dips below + * BLE_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD with receipt of any single message fragment. + * + * Default value of 3 is absolute minimum for stable performance, and an attempt to ensure safe window sizes on new + * platforms. + * + */ +#ifndef BLE_MAX_RECEIVE_WINDOW_SIZE +#define BLE_MAX_RECEIVE_WINDOW_SIZE 3 +#endif + +#if (BLE_MAX_RECEIVE_WINDOW_SIZE < 3) +#error "BLE_MAX_RECEIVE_WINDOW_SIZE must be greater than 2 for BLE transport protocol stability." +#endif + +/** + * @def BLE_CONFIG_ERROR_TYPE + * + * @brief + * This defines the data type used to represent errors for the + * BleLayer subsystem. + * + */ +#ifndef BLE_CONFIG_ERROR_TYPE +#include +#define BLE_CONFIG_ERROR_TYPE int32_t +#endif // BLE_CONFIG_ERROR_TYPE + +/** + * @def BLE_CONFIG_NO_ERROR + * + * @brief + * This defines the BleLayer error code for no error or success. + * + */ +#ifndef BLE_CONFIG_NO_ERROR +#define BLE_CONFIG_NO_ERROR 0 +#endif // BLE_CONFIG_NO_ERROR + +/** + * @def BLE_CONFIG_ERROR_MIN + * + * @brief + * This defines the base or minimum BleLayer error number range. + * + */ +#ifndef BLE_CONFIG_ERROR_MIN +#define BLE_CONFIG_ERROR_MIN 6000 +#endif // BLE_CONFIG_ERROR_MIN + +/** + * @def BLE_CONFIG_ERROR_MAX + * + * @brief + * This defines the top or maximum BleLayer error number range. + * + */ +#ifndef BLE_CONFIG_ERROR_MAX +#define BLE_CONFIG_ERROR_MAX 6999 +#endif // BLE_CONFIG_ERROR_MAX + +/** + * @def _BLE_CONFIG_ERROR + * + * @brief + * This defines a mapping function for BleLayer errors that allows + * mapping such errors into a platform- or system-specific range. + * + */ +#ifndef _BLE_CONFIG_ERROR +#define _BLE_CONFIG_ERROR(e) (BLE_ERROR_MIN + (e)) +#endif // _BLE_CONFIG_ERROR + +// clang-format on + +#include + +#endif /* BLECONFIG_H_ */ diff --git a/src/ble/BleError.cpp b/src/ble/BleError.cpp new file mode 100644 index 00000000000000..9d86de61836c6c --- /dev/null +++ b/src/ble/BleError.cpp @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2019 Google LLC. + * All rights reserved. + * + * 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 contains functions for working with BLE Layer errors. + */ + +#include + +#include + +#if CONFIG_NETWORK_LAYER_BLE + +#include +#include + +#include + +namespace nl { +namespace Ble { + +/** + * Register a text error formatter for BLE Layer errors. + */ +void RegisterBleLayerErrorFormatter(void) +{ + static ErrorFormatter sBleLayerErrorFormatter = + { + FormatBleLayerError, + NULL + }; + + RegisterErrorFormatter(&sBleLayerErrorFormatter); +} + +bool FormatBleLayerError(char * buf, uint16_t bufSize, int32_t err) +{ + const char * desc = NULL; + + if (err < BLE_ERROR_MIN || err > BLE_ERROR_MAX) + { + return false; + } + +#if !WEAVE_CONFIG_SHORT_ERROR_STR + switch (err) + { + case BLE_ERROR_BAD_ARGS : desc = "Bad arguments"; break; + case BLE_ERROR_INCORRECT_STATE : desc = "Incorrect state"; break; + case BLE_ERROR_NO_ENDPOINTS : desc = "No more BLE endpoints"; break; + case BLE_ERROR_NO_CONNECTION_RECEIVED_CALLBACK : desc = "No Weave over BLE connection received callback set"; break; + case BLE_ERROR_CENTRAL_UNSUBSCRIBED : desc = "BLE central unsubscribed"; break; + case BLE_ERROR_GATT_SUBSCRIBE_FAILED : desc = "GATT subscribe operation failed"; break; + case BLE_ERROR_GATT_UNSUBSCRIBE_FAILED : desc = "GATT unsubscribe operation failed"; break; + case BLE_ERROR_GATT_WRITE_FAILED : desc = "GATT write characteristic operation failed"; break; + case BLE_ERROR_GATT_INDICATE_FAILED : desc = "GATT indicate characteristic operation failed"; break; + case BLE_ERROR_NOT_IMPLEMENTED : desc = "Not implemented"; break; + case BLE_ERROR_WOBLE_PROTOCOL_ABORT : desc = "BLE transport protocol fired abort"; break; + case BLE_ERROR_REMOTE_DEVICE_DISCONNECTED : desc = "Remote device closed BLE connection"; break; + case BLE_ERROR_APP_CLOSED_CONNECTION : desc = "Application closed BLE connection"; break; + case BLE_ERROR_OUTBOUND_MESSAGE_TOO_BIG : desc = "Outbound message too big"; break; + case BLE_ERROR_NOT_WEAVE_DEVICE : desc = "BLE device doesn't seem to support Weave"; break; + case BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS : desc = "Incompatible BLE transport protocol versions"; break; + case BLE_ERROR_NO_MEMORY : desc = "No memory"; break; + case BLE_ERROR_MESSAGE_INCOMPLETE : desc = "Message incomplete"; break; + case BLE_ERROR_INVALID_FRAGMENT_SIZE : desc = "Invalid fragment size"; break; + case BLE_ERROR_START_TIMER_FAILED : desc = "Start timer failed"; break; + case BLE_ERROR_CONNECT_TIMED_OUT : desc = "Connect handshake timed out"; break; + case BLE_ERROR_RECEIVE_TIMED_OUT : desc = "Receive handshake timed out"; break; + case BLE_ERROR_INVALID_MESSAGE : desc = "Invalid message"; break; + case BLE_ERROR_FRAGMENT_ACK_TIMED_OUT : desc = "Message fragment acknowledgement timed out"; break; + case BLE_ERROR_KEEP_ALIVE_TIMED_OUT : desc = "Keep-alive receipt timed out"; break; + case BLE_ERRROR_NO_CONNECT_COMPLETE_CALLBACK : desc = "Missing required callback"; break; + case BLE_ERROR_INVALID_ACK : desc = "Received invalid BLE transport protocol fragment acknowledgement"; break; + case BLE_ERROR_REASSEMBLER_MISSING_DATA : desc = "BLE message reassembler did not receive enough data"; break; + case BLE_ERROR_INVALID_BTP_HEADER_FLAGS : desc = "Received invalid BLE transport protocol header flags"; break; + case BLE_ERROR_INVALID_BTP_SEQUENCE_NUMBER : desc = "Received invalid BLE transport protocol sequence number"; break; + case BLE_ERROR_REASSEMBLER_INCORRECT_STATE : desc = "BLE message reassembler received packet in incorrect state"; break; + case BLE_ERROR_RECEIVED_MESSAGE_TOO_BIG : desc = "Message received by BLE message reassembler was too large"; break; + } +#endif // !WEAVE_CONFIG_SHORT_ERROR_STR + + FormatError(buf, bufSize, "Ble", err, desc); + + return true; +} + +} /* namespace Ble */ +} /* namespace nl */ + +#endif // CONFIG_NETWORK_LAYER_BLE diff --git a/src/ble/BleError.h b/src/ble/BleError.h new file mode 100644 index 00000000000000..df00d3d0ce3b3e --- /dev/null +++ b/src/ble/BleError.h @@ -0,0 +1,430 @@ +/* + * + * Copyright (c) 2015-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines constants for the Nest BleLayer subsystem. + * + * Error types, ranges, and mappings overrides may be made by + * defining the appropriate BLE_CONFIG_* or _BLE_CONFIG_* + * macros. + * + * NOTE WELL: On some platforms, this header is included by C-language programs. + * + */ + +#ifndef BLEERROR_H_ +#define BLEERROR_H_ + +#include "BleConfig.h" + +// clang-format off + +/** + * @def BLE_NO_ERROR + * + * @brief + * This defines the BleLayer error code for success or no + * error. This value may be configured via #BLE_CONFIG_NO_ERROR. + * + */ +#define BLE_NO_ERROR BLE_CONFIG_NO_ERROR + +/** + * @def BLE_ERROR_MIN + * + * @brief + * This defines the base or minimum value of the BleLayer error number + * range. This value may be configured via #BLE_CONFIG_ERROR_MIN. + * + */ +#define BLE_ERROR_MIN BLE_CONFIG_ERROR_MIN + +/** + * @def BLE_ERROR_MAX + * + * @brief + * This defines the top or maximum value of the BleLayer error number + * range. This value may be configured via #BLE_CONFIG_ERROR_MAX. + * + */ +#define BLE_ERROR_MAX BLE_CONFIG_ERROR_MAX + +/** + * @def _BLE_ERROR(e) + * + * @brief + * This defines a mapping function for BleLayer errors that allows + * mapping such errors into a platform- or system-specific + * range. This function may be configured via + * #_BLE_CONFIG_ERROR. + * + * @param[in] e The BleLayer error to map. + * + * @return The mapped BleLayer error. + * + */ +#define _BLE_ERROR(e) _BLE_CONFIG_ERROR(e) + +/** + * @typedef BLE_ERROR + * + * The basic type for all BleLayer errors. + * + * This is defined to a platform- or system-specific type. + * + */ +typedef BLE_CONFIG_ERROR_TYPE BLE_ERROR; + +/** + * @name Error Definitions + * + * @{ + */ + +/** + * @def BLE_ERROR_BAD_ARGS + * + * @brief + * An invalid argument or arguments were supplied. + * + */ +#define BLE_ERROR_BAD_ARGS _BLE_ERROR(0) + +/** + * @def BLE_ERROR_INCORRECT_STATE + * + * @brief + * An unexpected state was encountered. + * + */ +#define BLE_ERROR_INCORRECT_STATE _BLE_ERROR(1) + +/** + * @def BLE_ERROR_NO_ENDPOINTS + * + * @brief + * No BLE endpoint is available. + * + */ +#define BLE_ERROR_NO_ENDPOINTS _BLE_ERROR(2) + +/** + * @def BLE_ERROR_NO_CONNECTION_RECEIVED_CALLBACK + * + * @brief + * No callback was registered to receive a BLE Transport Protocol (BTP) + * connection. + * + */ +#define BLE_ERROR_NO_CONNECTION_RECEIVED_CALLBACK _BLE_ERROR(3) + +/** + * @def BLE_ERROR_CENTRAL_UNSUBSCRIBED + * + * @brief + * A BLE central device unsubscribed from a peripheral device's BLE + * Transport Protocol (BTP) transmit characteristic. + * + */ +#define BLE_ERROR_CENTRAL_UNSUBSCRIBED _BLE_ERROR(4) + +/** + * @def BLE_ERROR_GATT_SUBSCRIBE_FAILED + * + * @brief + * A BLE central device failed to subscribe to a peripheral device's BLE + * Transport Protocol (BTP) transmit characteristic. + * + */ +#define BLE_ERROR_GATT_SUBSCRIBE_FAILED _BLE_ERROR(5) + +/** + * @def BLE_ERROR_GATT_UNSUBSCRIBE_FAILED + * + * @brief + * A BLE central device failed to unsubscribe from a peripheral device's + * BLE Transport Protocol (BTP) transmit characteristic. + * + */ +#define BLE_ERROR_GATT_UNSUBSCRIBE_FAILED _BLE_ERROR(6) + +/** + * @def BLE_ERROR_GATT_WRITE_FAILED + * + * @brief + * A General Attribute Profile (GATT) write operation failed. + * + */ +#define BLE_ERROR_GATT_WRITE_FAILED _BLE_ERROR(7) + +/** + * @def BLE_ERROR_GATT_INDICATE_FAILED + * + * @brief + * A General Attribute Profile (GATT) indicate operation failed. + * + */ +#define BLE_ERROR_GATT_INDICATE_FAILED _BLE_ERROR(8) + +/** + * @def BLE_ERROR_NOT_IMPLEMENTED + * + * @brief + * A requested function or feature is not implemented. + * + */ +#define BLE_ERROR_NOT_IMPLEMENTED _BLE_ERROR(9) + +/* + * Unused _BLE_ERROR(10) + */ + +/** + * @def BLE_ERROR_WOBLE_PROTOCOL_ABORT + * + * @brief + * A BLE Transport Protocol (BTP) error was encountered. + * + */ +#define BLE_ERROR_WOBLE_PROTOCOL_ABORT _BLE_ERROR(11) + +/** + * @def BLE_ERROR_REMOTE_DEVICE_DISCONNECTED + * + * @brief + * A remote BLE connection peer disconnected, either actively or due to the + * expiration of a BLE connection supervision timeout. + * + */ +#define BLE_ERROR_REMOTE_DEVICE_DISCONNECTED _BLE_ERROR(12) + +/** + * @def BLE_ERROR_APP_CLOSED_CONNECTION + * + * @brief + * The local application closed a BLE connection, and has informed BleLayer. + * + */ +#define BLE_ERROR_APP_CLOSED_CONNECTION _BLE_ERROR(13) + +/** + * @def BLE_ERROR_OUTBOUND_MESSAGE_TOO_BIG + * + * @brief + * More outbound message data is pending than available buffer space + * available to copy it. + * + */ +#define BLE_ERROR_OUTBOUND_MESSAGE_TOO_BIG _BLE_ERROR(14) + +/** + * @def BLE_ERROR_NOT_WEAVE_DEVICE + * + * @brief + * A BLE peripheral device did not expose the General Attribute Profile + * (GATT) service required by the Bluetooth Transport Protocol (BTP). + * + */ +#define BLE_ERROR_NOT_WEAVE_DEVICE _BLE_ERROR(15) + +/** + * @def BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS + * + * @brief + * A remote device does not offer a compatible version of the Bluetooth + * Transport Protocol (BTP). + * + */ +#define BLE_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS _BLE_ERROR(16) + +/** + * @def BLE_ERROR_NO_MEMORY + * + * @brief + * A request for memory could not be fulfilled. + * + */ +#define BLE_ERROR_NO_MEMORY _BLE_ERROR(17) + +/** + * @def BLE_ERROR_MESSAGE_INCOMPLETE + * + * @brief + * A received Bluetooth Transport Protocol (BTP) message was incomplete. + * + */ +#define BLE_ERROR_MESSAGE_INCOMPLETE _BLE_ERROR(18) + +/** + * @def BLE_ERROR_INVALID_FRAGMENT_SIZE + * + * @brief + * A remote device selected in invalid Bluetooth Transport Protocol (BTP) + * fragment size. + * + */ +#define BLE_ERROR_INVALID_FRAGMENT_SIZE _BLE_ERROR(19) + +/** + * @def BLE_ERROR_START_TIMER_FAILED + * + * @brief + * A timer failed to start within BleLayer. + * + */ +#define BLE_ERROR_START_TIMER_FAILED _BLE_ERROR(20) + +/** + * @def BLE_ERROR_CONNECT_TIMED_OUT + * + * @brief + * A remote BLE peripheral device's Bluetooth Transport Protocol (BTP) + * connect handshake response timed out. + * + */ +#define BLE_ERROR_CONNECT_TIMED_OUT _BLE_ERROR(21) + +/** + * @def BLE_ERROR_RECEIVE_TIMED_OUT + * + * @brief + * A remote BLE central device's Bluetooth Transport Protocol (BTP) connect + * handshake timed out. + * + */ +#define BLE_ERROR_RECEIVE_TIMED_OUT _BLE_ERROR(22) + +/** + * @def BLE_ERROR_INVALID_MESSAGE + * + * @brief + * An invalid Bluetooth Transport Protocol (BTP) message was received. + * + */ +#define BLE_ERROR_INVALID_MESSAGE _BLE_ERROR(23) + +/** + * @def BLE_ERROR_FRAGMENT_ACK_TIMED_OUT + * + * @brief + * Receipt of an expected Bluetooth Transport Protocol (BTP) fragment + * acknowledgement timed out. + * + */ +#define BLE_ERROR_FRAGMENT_ACK_TIMED_OUT _BLE_ERROR(24) + +/** + * @def BLE_ERROR_KEEP_ALIVE_TIMED_OUT + * + * @brief + * Receipt of an expected Bluetooth Transport Protocol (BTP) keep-alive + * fragment timed out. + * + */ +#define BLE_ERROR_KEEP_ALIVE_TIMED_OUT _BLE_ERROR(25) + +/** + * @def BLE_ERRROR_NO_CONNECT_COMPLETE_CALLBACK + * + * @brief + * No callback was registered to handle Bluetooth Transport Protocol (BTP) + * connect completion. + * + */ +#define BLE_ERRROR_NO_CONNECT_COMPLETE_CALLBACK _BLE_ERROR(26) + +/** + * @def BLE_ERROR_INVALID_ACK + * + * @brief + * A Bluetooth Transport Protcol (BTP) fragment acknowledgement was invalid. + * + */ +#define BLE_ERROR_INVALID_ACK _BLE_ERROR(27) + +/** + * @def BLE_ERROR_REASSEMBLER_MISSING_DATA + * + * @brief + * A Bluetooth Transport Protocol (BTP) end-of-message fragment was + * received, but the total size of the received fragments is less than + * the indicated size of the original fragmented message. + * + */ +#define BLE_ERROR_REASSEMBLER_MISSING_DATA _BLE_ERROR(28) + +/** + * @def BLE_ERROR_INVALID_BTP_HEADER_FLAGS + * + * @brief + * A set of Bluetooth Transport Protocol (BTP) header flags is invalid. + * + */ +#define BLE_ERROR_INVALID_BTP_HEADER_FLAGS _BLE_ERROR(29) + +/** + * @def BLE_ERROR_INVALID_BTP_SEQUENCE_NUMBER + * + * @brief + * A Bluetooth Transport Protocol (BTP) fragment sequence number is invalid. + * + */ +#define BLE_ERROR_INVALID_BTP_SEQUENCE_NUMBER _BLE_ERROR(30) + +/** + * @def BLE_ERROR_REASSEMBLER_INCORRECT_STATE + * + * @brief + * The Bluetooth Transport Protocol (BTP) message reassembly engine + * encountered an unexpected state. + * + */ +#define BLE_ERROR_REASSEMBLER_INCORRECT_STATE _BLE_ERROR(31) + +/** + * @def BLE_ERROR_RECEIVED_MESSAGE_TOO_BIG + * + * @brief + * More inbound message data is pending than available buffer space + * available to copy it. + * + */ +#define BLE_ERROR_RECEIVED_MESSAGE_TOO_BIG _BLE_ERROR(32) + +// !!!!! IMPORTANT !!!!! If you add new Ble errors, please update the translation +// of error codes to strings in BleError.cpp, and add them to unittest +// in test-apps/TestErrorStr.cpp + +/** + * @} + */ + +// clang-format on + +namespace nl { +namespace Ble { + +extern void RegisterBleLayerErrorFormatter(void); +extern bool FormatBleLayerError(char * buf, uint16_t bufSize, int32_t err); + +} /* namespace Ble */ +} /* namespace nl */ + + +#endif /* BLEERROR_H_ */ diff --git a/src/ble/BleLayer.am b/src/ble/BleLayer.am new file mode 100644 index 00000000000000..21bb8b3156ca16 --- /dev/null +++ b/src/ble/BleLayer.am @@ -0,0 +1,39 @@ +# +# Copyright (c) 2014-2017 Nest Labs, Inc. +# All rights reserved. +# +# 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. +# + +# +# Description: +# This file is the GNU automake header for the Nest BleLayer +# library sources. +# +# These sources are shared by other SDK makefiles and consequently +# must be anchored relative to the top build directory. +# + +nl_BleLayer_sources = \ + @top_builddir@/src/ble/BleLayer.cpp \ + @top_builddir@/src/ble/BLEEndPoint.cpp \ + @top_builddir@/src/ble/WoBle.cpp \ + @top_builddir@/src/ble/BleUUID.cpp \ + @top_builddir@/src/ble/BleError.cpp \ + $(NULL) + +if WEAVE_ENABLE_WOBLE_TEST +nl_BleLayer_sources += \ + @top_builddir@/src/device-manager/WoBleTest.cpp \ + $(NULL) +endif # WEAVE_ENABLE_WOBLE_TEST diff --git a/src/ble/BleLayer.cpp b/src/ble/BleLayer.cpp new file mode 100644 index 00000000000000..83b25f032cf633 --- /dev/null +++ b/src/ble/BleLayer.cpp @@ -0,0 +1,738 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 objects which provide an abstraction layer between + * a platform's Bluetooth Low Energy (BLE) implementation and the Weave + * stack. + * + * The BleLayer obect accepts BLE data and control input from the + * application via a functional interface. It performs the fragmentation + * and reassembly required to transmit Weave message via a BLE GATT + * characteristic interface, and drives incoming messages up the Weave + * stack. + * + * During initialization, the BleLayer object requires a pointer to the + * platform's implementation of the BlePlatformDelegate and + * BleApplicationDelegate objects. + * + * The BlePlatformDelegate provides the Weave stack with an interface + * by which to form and cancel GATT subscriptions, read and write + * GATT characteristic values, send GATT characteristic notifications, + * respond to GATT read requests, and close BLE connections. + * + * The BleApplicationDelegate provides a mechanism for Weave to inform + * the application when it has finished using a given BLE connection, + * i.e when the WeaveConnection object wrapping this connection has + * closed. This allows the application to either close the BLE connection + * or continue to keep it open for non-Weave purposes. + * + * To enable Weave over BLE for a new platform, the application developer + * must provide an implementation for both delegates, provides points to + * instances of these delegates on startup, and ensure that the + * application calls the necessary BleLayer functions when appropriate to + * drive BLE data and control input up the stack. + */ + +#include + +#if CONFIG_NETWORK_LAYER_BLE + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +// clang-format off + +#define CAPABILITIES_REQUEST_MAGICNUM_LEN 2 +#define CAPABILITIES_REQUEST_L2CAP_MTU_LEN 2 +#define CAPABILITIES_REQUEST_SUPPORTED_VERSIONS_LEN 4 +#define CAPABILITIES_REQUEST_WINDOW_SIZE_LEN 1 +#define CAPABILITIES_REQUEST_LEN (CAPABILITIES_REQUEST_MAGICNUM_LEN + \ + CAPABILITIES_REQUEST_L2CAP_MTU_LEN + \ + CAPABILITIES_REQUEST_SUPPORTED_VERSIONS_LEN + \ + CAPABILITIES_REQUEST_WINDOW_SIZE_LEN) + +#define CAPABILITIES_RESPONSE_MAGICNUM_LEN 2 +#define CAPABILITIES_RESPONSE_L2CAP_MTU_LEN 2 +#define CAPABILITIES_RESPONSE_SELECTED_PROTOCOL_VERSION_LEN 1 +#define CAPABILITIES_RESPONSE_WINDOW_SIZE_LEN 1 +#define CAPABILITIES_RESPONSE_LEN (CAPABILITIES_RESPONSE_MAGICNUM_LEN + \ + CAPABILITIES_RESPONSE_L2CAP_MTU_LEN + \ + CAPABILITIES_RESPONSE_SELECTED_PROTOCOL_VERSION_LEN + \ + CAPABILITIES_RESPONSE_WINDOW_SIZE_LEN) + +// Magic values expected in first 2 bytes of valid BLE transport capabilities request or response: +#define CAPABILITIES_MSG_CHECK_BYTE_1 'n' +#define CAPABILITIES_MSG_CHECK_BYTE_2 'l' + +// clang-format on + +namespace nl { +namespace Ble { + +class BleEndPointPool +{ +public: + int Size() const { return BLE_LAYER_NUM_BLE_ENDPOINTS; } + + BLEEndPoint * Get(int i) const + { + static union + { + uint8_t Pool[sizeof(BLEEndPoint) * BLE_LAYER_NUM_BLE_ENDPOINTS]; + BLEEndPoint::AlignT ForceAlignment; + } sEndPointPool; + + if (i < BLE_LAYER_NUM_BLE_ENDPOINTS) + { + return (BLEEndPoint *) (sEndPointPool.Pool + (sizeof(BLEEndPoint) * i)); + } + else + { + return NULL; + } + } + + BLEEndPoint * Find(BLE_CONNECTION_OBJECT c) + { + if (c == BLE_CONNECTION_UNINITIALIZED) + { + return NULL; + } + + for (int i = 0; i < BLE_LAYER_NUM_BLE_ENDPOINTS; i++) + { + BLEEndPoint * elem = Get(i); + if (elem->mBle != NULL && elem->mConnObj == c) + { + return elem; + } + } + + return NULL; + } + + BLEEndPoint * GetFree() const + { + for (int i = 0; i < BLE_LAYER_NUM_BLE_ENDPOINTS; i++) + { + BLEEndPoint * elem = Get(i); + if (elem->mBle == NULL) + { + return elem; + } + } + return NULL; + } +}; + +// EndPoint Pools +// +static BleEndPointPool sBLEEndPointPool; + +// UUIDs used internally by BleLayer: + +const WeaveBleUUID BleLayer::WEAVE_BLE_CHAR_1_ID = { { // 18EE2EF5-263D-4559-959F-4F9C429F9D11 + 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, + 0x9F, 0x9D, 0x11 } }; + +const WeaveBleUUID BleLayer::WEAVE_BLE_CHAR_2_ID = { { // 18EE2EF5-263D-4559-959F-4F9C429F9D12 + 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, + 0x9F, 0x9D, 0x12 } }; + +void BleLayerObject::Release() +{ + // Decrement the ref count. When it reaches zero, NULL out the pointer to the Weave::System::Layer + // object. This effectively declared the object free and ready for re-allocation. + mRefCount--; + if (mRefCount == 0) + { + mBle = NULL; + } +} + +// BleTransportCapabilitiesRequestMessage implementation: + +void BleTransportCapabilitiesRequestMessage::SetSupportedProtocolVersion(uint8_t index, uint8_t version) +{ + uint8_t mask; + + // If even-index, store version in lower 4 bits; else, higher 4 bits. + if (index % 2 == 0) + { + mask = 0x0F; + } + else + { + mask = 0xF0; + version = version << 4; + } + + version &= mask; + + mSupportedProtocolVersions[(index / 2)] &= ~mask; // Clear version at index; leave other version in same byte alone + mSupportedProtocolVersions[(index / 2)] |= version; +} + +BLE_ERROR BleTransportCapabilitiesRequestMessage::Encode(PacketBuffer * msgBuf) const +{ + uint8_t * p = msgBuf->Start(); + BLE_ERROR err = BLE_NO_ERROR; + + // Verify we can write the fixed-length request without running into the end of the buffer. + VerifyOrExit(msgBuf->MaxDataLength() >= CAPABILITIES_REQUEST_LEN, err = BLE_ERROR_NO_MEMORY); + + nl::Weave::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_1); + nl::Weave::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_2); + + for (int i = 0; i < CAPABILITIES_REQUEST_SUPPORTED_VERSIONS_LEN; i++) + { + nl::Weave::Encoding::Write8(p, mSupportedProtocolVersions[i]); + } + + nl::Weave::Encoding::LittleEndian::Write16(p, mMtu); + nl::Weave::Encoding::Write8(p, mWindowSize); + + msgBuf->SetDataLength(CAPABILITIES_REQUEST_LEN); + +exit: + return err; +} + +BLE_ERROR BleTransportCapabilitiesRequestMessage::Decode(const PacketBuffer & msgBuf, BleTransportCapabilitiesRequestMessage & msg) +{ + const uint8_t * p = msgBuf.Start(); + BLE_ERROR err = BLE_NO_ERROR; + + // Verify we can read the fixed-length request without running into the end of the buffer. + VerifyOrExit(msgBuf.DataLength() >= CAPABILITIES_REQUEST_LEN, err = BLE_ERROR_MESSAGE_INCOMPLETE); + + VerifyOrExit(CAPABILITIES_MSG_CHECK_BYTE_1 == nl::Weave::Encoding::Read8(p), err = BLE_ERROR_INVALID_MESSAGE); + VerifyOrExit(CAPABILITIES_MSG_CHECK_BYTE_2 == nl::Weave::Encoding::Read8(p), err = BLE_ERROR_INVALID_MESSAGE); + + for (int i = 0; i < CAPABILITIES_REQUEST_SUPPORTED_VERSIONS_LEN; i++) + { + msg.mSupportedProtocolVersions[i] = nl::Weave::Encoding::Read8(p); + } + + msg.mMtu = nl::Weave::Encoding::LittleEndian::Read16(p); + msg.mWindowSize = nl::Weave::Encoding::Read8(p); + +exit: + return err; +} + +// BleTransportCapabilitiesResponseMessage implementation: + +BLE_ERROR BleTransportCapabilitiesResponseMessage::Encode(PacketBuffer * msgBuf) const +{ + uint8_t * p = msgBuf->Start(); + BLE_ERROR err = BLE_NO_ERROR; + + // Verify we can write the fixed-length request without running into the end of the buffer. + VerifyOrExit(msgBuf->MaxDataLength() >= CAPABILITIES_RESPONSE_LEN, err = BLE_ERROR_NO_MEMORY); + + nl::Weave::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_1); + nl::Weave::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_2); + + nl::Weave::Encoding::Write8(p, mSelectedProtocolVersion); + nl::Weave::Encoding::LittleEndian::Write16(p, mFragmentSize); + nl::Weave::Encoding::Write8(p, mWindowSize); + + msgBuf->SetDataLength(CAPABILITIES_RESPONSE_LEN); + +exit: + return err; +} + +BLE_ERROR BleTransportCapabilitiesResponseMessage::Decode(const PacketBuffer & msgBuf, + BleTransportCapabilitiesResponseMessage & msg) +{ + const uint8_t * p = msgBuf.Start(); + BLE_ERROR err = BLE_NO_ERROR; + + // Verify we can read the fixed-length response without running into the end of the buffer. + VerifyOrExit(msgBuf.DataLength() >= CAPABILITIES_RESPONSE_LEN, err = BLE_ERROR_MESSAGE_INCOMPLETE); + + VerifyOrExit(CAPABILITIES_MSG_CHECK_BYTE_1 == nl::Weave::Encoding::Read8(p), err = BLE_ERROR_INVALID_MESSAGE); + VerifyOrExit(CAPABILITIES_MSG_CHECK_BYTE_2 == nl::Weave::Encoding::Read8(p), err = BLE_ERROR_INVALID_MESSAGE); + + msg.mSelectedProtocolVersion = nl::Weave::Encoding::Read8(p); + msg.mFragmentSize = nl::Weave::Encoding::LittleEndian::Read16(p); + msg.mWindowSize = nl::Weave::Encoding::Read8(p); + +exit: + return err; +} + +// BleLayer implementation: + +BleLayer::BleLayer() +{ + mState = kState_NotInitialized; +} + +BLE_ERROR BleLayer::Init(BlePlatformDelegate * platformDelegate, BleApplicationDelegate * appDelegate, + Weave::System::Layer * systemLayer) +{ + BLE_ERROR err = BLE_NO_ERROR; + + RegisterBleLayerErrorFormatter(); + + VerifyOrExit(platformDelegate != NULL, err = BLE_ERROR_BAD_ARGS); + VerifyOrExit(appDelegate != NULL, err = BLE_ERROR_BAD_ARGS); + VerifyOrExit(systemLayer != NULL, err = BLE_ERROR_BAD_ARGS); + + if (mState != kState_NotInitialized) + { + return BLE_ERROR_INCORRECT_STATE; + } + + mPlatformDelegate = platformDelegate; + mApplicationDelegate = appDelegate; + mSystemLayer = systemLayer; + + memset(&sBLEEndPointPool, 0, sizeof(sBLEEndPointPool)); + + mState = kState_Initialized; + +#if WEAVE_ENABLE_WOBLE_TEST + mTestBleEndPoint = NULL; +#endif + +exit: + return err; +} + +BLE_ERROR BleLayer::Shutdown() +{ + mState = kState_NotInitialized; + + // Close and free all BLE end points. + for (int i = 0; i < BLE_LAYER_NUM_BLE_ENDPOINTS; i++) + { + BLEEndPoint * elem = sBLEEndPointPool.Get(i); + + // If end point was initialized, and has not since been freed... + if (elem->mBle != NULL) + { + // If end point hasn't already been closed... + if (elem->mState != BLEEndPoint::kState_Closed) + { + // Close end point such that callbacks are suppressed and pending transmissions aborted. + elem->Abort(); + } + + // If end point was closed, but is still waiting for GATT unsubscribe to complete, free it anyway. + // This cancels the unsubscribe timer (plus all the end point's other timers). + if (elem->IsUnsubscribePending()) + { + elem->Free(); + } + } + } + + return BLE_NO_ERROR; +} + +BLE_ERROR BleLayer::NewBleEndPoint(BLEEndPoint ** retEndPoint, BLE_CONNECTION_OBJECT connObj, BleRole role, bool autoClose) +{ + *retEndPoint = NULL; + + if (mState != kState_Initialized) + { + return BLE_ERROR_INCORRECT_STATE; + } + + if (connObj == BLE_CONNECTION_UNINITIALIZED) + { + return BLE_ERROR_BAD_ARGS; + } + + *retEndPoint = sBLEEndPointPool.GetFree(); + if (*retEndPoint == NULL) + { + WeaveLogError(Ble, "%s endpoint pool FULL", "Ble"); + return BLE_ERROR_NO_ENDPOINTS; + } + + (*retEndPoint)->Init(this, connObj, role, autoClose); + +#if WEAVE_ENABLE_WOBLE_TEST + mTestBleEndPoint = *retEndPoint; +#endif + + return BLE_NO_ERROR; +} + +// Handle remote central's initiation of Weave over BLE protocol handshake. +BLE_ERROR BleLayer::HandleBleTransportConnectionInitiated(BLE_CONNECTION_OBJECT connObj, PacketBuffer * pBuf) +{ + BLE_ERROR err = BLE_NO_ERROR; + BLEEndPoint * newEndPoint = NULL; + + // Only BLE peripherals can receive GATT writes, so specify this role in our creation of the BLEEndPoint. + // Set autoClose = false. Peripherals only notify the application when an end point releases a BLE connection. + err = NewBleEndPoint(&newEndPoint, connObj, kBleRole_Peripheral, false); + SuccessOrExit(err); + + newEndPoint->mAppState = mAppState; + + err = newEndPoint->Receive(pBuf); + pBuf = NULL; + SuccessOrExit(err); // If we fail here, end point will have already released connection and freed itself. + +exit: + if (pBuf != NULL) + { + PacketBuffer::Free(pBuf); + } + + // If we failed to allocate a new end point, release underlying BLE connection. Central's handshake will time out + // if the application decides to keep the BLE connection open. + if (newEndPoint == NULL) + { + mApplicationDelegate->NotifyWeaveConnectionClosed(connObj); + } + + if (err != BLE_NO_ERROR) + { + WeaveLogError(Ble, "HandleWeaveConnectionReceived failed, err = %d", err); + } + + return err; +} + +bool BleLayer::HandleWriteReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + WeaveLogError(Ble, "ble write rcvd on unknown svc id"); + ExitNow(); + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_1_ID, charId)) + { + if (pBuf == NULL) + { + WeaveLogError(Ble, "rcvd null ble write"); + ExitNow(); + } + + // Find matching connection end point. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + BLE_ERROR status = endPoint->Receive(pBuf); + pBuf = NULL; + if (status != BLE_NO_ERROR) + { + WeaveLogError(Ble, "BLEEndPoint rcv failed, err = %d", status); + } + } + else + { + BLE_ERROR status = HandleBleTransportConnectionInitiated(connObj, pBuf); + pBuf = NULL; + if (status != BLE_NO_ERROR) + { + WeaveLogError(Ble, "failed handle new Weave BLE connection, status = %d", status); + } + } + } + else + { + WeaveLogError(Ble, "ble write rcvd on unknown char"); + } + +exit: + if (pBuf != NULL) + { + PacketBuffer::Free(pBuf); + } + + return true; +} + +bool BleLayer::HandleIndicationReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + if (pBuf == NULL) + { + WeaveLogError(Ble, "rcvd null ble indication"); + ExitNow(); + } + + // find matching connection end point. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + BLE_ERROR status = endPoint->Receive(pBuf); + pBuf = NULL; + if (status != BLE_NO_ERROR) + { + WeaveLogError(Ble, "BLEEndPoint rcv failed, err = %d", status); + } + } + else + { + WeaveLogDetail(Ble, "no endpoint for rcvd indication"); + } + } + else + { + WeaveLogError(Ble, "ble ind rcvd on unknown char"); + } + +exit: + if (pBuf != NULL) + { + PacketBuffer::Free(pBuf); + } + + return true; +} + +bool BleLayer::HandleWriteConfirmation(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_1_ID, charId)) + { + HandleAckReceived(connObj); + } + else + { + WeaveLogError(Ble, "ble write con rcvd on unknown char"); + } + + return true; +} + +bool BleLayer::HandleIndicationConfirmation(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + HandleAckReceived(connObj); + } + else + { + WeaveLogError(Ble, "ble ind con rcvd on unknown char"); + } + + return true; +} + +void BleLayer::HandleAckReceived(BLE_CONNECTION_OBJECT connObj) +{ + // find matching connection end point. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + BLE_ERROR status = endPoint->HandleGattSendConfirmationReceived(); + + if (status != BLE_NO_ERROR) + { + WeaveLogError(Ble, "endpoint conf recvd failed, err = %d", status); + } + } + else + { + WeaveLogError(Ble, "no endpoint for BLE sent data ack"); + } +} + +bool BleLayer::HandleSubscribeReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + // Find end point already associated with BLE connection, if any. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + endPoint->HandleSubscribeReceived(); + } + else + { + WeaveLogError(Ble, "no endpoint for sub recvd"); + } + } + + return true; +} + +bool BleLayer::HandleSubscribeComplete(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + endPoint->HandleSubscribeComplete(); + } + else + { + WeaveLogError(Ble, "no endpoint for sub complete"); + } + } + + return true; +} + +bool BleLayer::HandleUnsubscribeReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + // Find end point already associated with BLE connection, if any. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + endPoint->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_CENTRAL_UNSUBSCRIBED); + } + else + { + WeaveLogError(Ble, "no endpoint for unsub recvd"); + } + } + + return true; +} + +bool BleLayer::HandleUnsubscribeComplete(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId) +{ + if (!UUIDsMatch(&WEAVE_BLE_SVC_ID, svcId)) + { + return false; + } + + if (UUIDsMatch(&WEAVE_BLE_CHAR_2_ID, charId)) + { + // Find end point already associated with BLE connection, if any. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + endPoint->HandleUnsubscribeComplete(); + } + else + { + WeaveLogError(Ble, "no endpoint for unsub complete"); + } + } + + return true; +} + +void BleLayer::HandleConnectionError(BLE_CONNECTION_OBJECT connObj, BLE_ERROR err) +{ + // BLE connection has failed somehow, we must find and abort matching connection end point. + BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj); + + if (endPoint != NULL) + { + if (err == BLE_ERROR_GATT_UNSUBSCRIBE_FAILED && endPoint->IsUnsubscribePending()) + { + // If end point was already closed and just waiting for unsubscribe to complete, free it. Call to Free() + // stops unsubscribe timer. + endPoint->Free(); + } + else + { + endPoint->DoClose(kBleCloseFlag_AbortTransmission, err); + } + } +} + +BleTransportProtocolVersion BleLayer::GetHighestSupportedProtocolVersion(const BleTransportCapabilitiesRequestMessage & reqMsg) +{ + BleTransportProtocolVersion retVersion = kBleTransportProtocolVersion_None; + + uint8_t shift_width = 4; + + for (int i = 0; i < NUM_SUPPORTED_PROTOCOL_VERSIONS; i++) + { + shift_width ^= 4; + + uint8_t version = reqMsg.mSupportedProtocolVersions[(i / 2)]; + version = (version >> shift_width) & 0x0F; // Grab just the nibble we want. + + if ((version >= NL_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION) && + (version <= NL_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION) && (version > retVersion)) + { + retVersion = static_cast(version); + } + else if (version == kBleTransportProtocolVersion_None) // Signifies end of supported versions list + { + break; + } + } + + return retVersion; +} + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* CONFIG_NETWORK_LAYER_BLE */ diff --git a/src/ble/BleLayer.h b/src/ble/BleLayer.h new file mode 100644 index 00000000000000..56c1afe3c51b64 --- /dev/null +++ b/src/ble/BleLayer.h @@ -0,0 +1,373 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines objects that provide an abstraction layer between a + * platform's Bluetooth Low Energy (BLE) implementation and the Weave + * stack. + * + * The BleLayer obect accepts BLE data and control input from the + * application via a functional interface. It performs the fragmentation + * and reassembly required to transmit Weave message via a BLE GATT + * characteristic interface, and drives incoming messages up the Weave + * stack. + * + * During initialization, the BleLayer object requires a pointer to the + * platform's implementation of the BleAdapter object. This object is + * defined but not implemented by the Weave stack, and provides the + * BleLayer with a functional interface to drive outgoing GATT + * characteristic writes and indications. It also provides a mechanism + * for Weave to inform the application when it has finished using a given + * BLE connection, i.e., when the WeaveConnection object wrapping this + * connection has closed. + * + * To enable Weave over BLE for a new platform, the application developer + * must implement the BleAdapter class for their platform, pass it to the + * BleLayer on startup, pass a pointer to this BleLayer to their instance + * of WeaveMessageLayer, and ensure that the application calls the + * necessary BleLayer functions to drive BLE data and control input up the + * stack. + */ + +#ifndef BLELAYER_H_ +#define BLELAYER_H_ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#if BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES +#include +#endif // BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + +namespace nl { +namespace Ble { + +using ::nl::Weave::System::PacketBuffer; + +/** + * @def NUM_SUPPORTED_PROTOCOL_VERSIONS + * + * Number of unsigned 4-bit representations of supported transport protocol + * versions encapsulated in a BleTransportCapabilitiesRequest. Defined by Weave + * over BLE protocol specification. + */ +#define NUM_SUPPORTED_PROTOCOL_VERSIONS 8 +/// Version(s) of the Nest BLE Transport Protocol that this stack supports. +#define NL_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION kBleTransportProtocolVersion_V2 +#define NL_BLE_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION kBleTransportProtocolVersion_V3 + +/// Forward declarations. +class BleLayer; +class BLEEndPoint; + +/// Role of end points' associated BLE connections. Determines means used by end points to send and receive data. +typedef enum +{ + kBleRole_Central = 0, + kBleRole_Peripheral = 1 +} BleRole; + +/// Enum defining versions of Weave over BLE transport protocol. +typedef enum +{ + kBleTransportProtocolVersion_None = 0, + kBleTransportProtocolVersion_V1 = 1, // Prototype WoBLe version without ACKs or flow-control. + kBleTransportProtocolVersion_V2 = 2, // First WoBLE version with ACKs and flow-control. + kBleTransportProtocolVersion_V3 = 3 // First WoBLE version with asymetric fragement sizes. +} BleTransportProtocolVersion; + +class BleLayerObject +{ + friend class BleLayer; + +public: + // Public data members: + BleLayer * mBle; ///< [READ-ONLY] Pointer to the BleLayer object that owns this object. + void * mAppState; ///< Generic pointer to app-specific data associated with the object. + +protected: + uint32_t mRefCount; + + void AddRef(void) { mRefCount++; } + void Release(void); +}; + +class BleTransportCapabilitiesRequestMessage +{ +public: + /** + * An array of size NUM_SUPPORTED_PROTOCOL_VERSIONS listing versions of the + * BLE transport protocol that this node supports. Each protocol version is + * specified as a 4-bit unsigned integer. A zero-value represents unused + * array elements. Counting up from the zero-index, the first zero-value + * specifies the end of the list of supported protocol versions. + */ + uint8_t mSupportedProtocolVersions[(NUM_SUPPORTED_PROTOCOL_VERSIONS / 2) + (NUM_SUPPORTED_PROTOCOL_VERSIONS % 2)]; + + /** + * The MTU that has been negotiated for this BLE connection. Specified in + * the BleTransportCapabilitiesRequestMessage because the remote node may + * be unable to glean this info from its own BLE hardware/software stack, + * such as on older Android platforms. + * + * A value of 0 means that the central could not determine the negotiated + * BLE connection MTU. + */ + uint16_t mMtu; + + /** + * The initial and maximum receive window size offered by the central, + * defined in terms of GATT indication payloads. + */ + uint8_t mWindowSize; + + /** + * Set supported version value at given index in + * SupportedProtocolVersions. uint8_t version argument is truncated to 4 + * least-significant bits. Index shall be 0 through number of + * SupportedProtocolVersions elements - 1. + */ + void SetSupportedProtocolVersion(uint8_t index, uint8_t version); + + /// Must be able to reserve 20 byte data length in msgBuf. + BLE_ERROR Encode(PacketBuffer * msgBuf) const; + + static BLE_ERROR Decode(const PacketBuffer & msgBuf, BleTransportCapabilitiesRequestMessage & msg); +}; + +class BleTransportCapabilitiesResponseMessage +{ +public: + /** + * The lower 4 bits specify the BLE transport protocol version that the BLE + * peripheral has selected for this connection. + * + * A value of kBleTransportProtocolVersion_None means that no supported + * protocol version was found in the central's capabilities request. The + * central should unsubscribe after such a response has been sent to free + * up the peripheral for connections from devices with supported protocol + * versions. + */ + uint8_t mSelectedProtocolVersion; + + /** + * BLE transport fragment size selected by peripheral in response to MTU + * value in BleTransportCapabilitiesRequestMessage and its local + * observation of the BLE connection MTU. + */ + uint16_t mFragmentSize; + + /** + * The initial and maximum receive window size offered by the peripheral, + * defined in terms of GATT write payloads. + */ + uint8_t mWindowSize; + + /// Must be able to reserve 20 byte data length in msgBuf. + BLE_ERROR Encode(PacketBuffer * msgBuf) const; + + static BLE_ERROR Decode(const PacketBuffer & msgBuf, BleTransportCapabilitiesResponseMessage & msg); +}; + +/** + * @class BleLayer + * + * @brief + * This class provides an interface for a single thread to drive data + * either up the stack via the BleLayer platform interface functions, + * or down the stack via a WeaveConnection object associated with a + * BLEEndPoint. + * + * There are two ways to associate a WeaveConnection (defined by the + * WeaveMessageLayer) with a BLE connection: + * + * First, the application can passively receive an incoming BLE connection + * and hand the platform-specific BLE_CONNECTION_OBJECT that this receipt + * generates to BleLayer via the corresponding platform interface function. + * This causes BleLayer to wrap the BLE_CONNECTION_OBJECT in a BLEEndPoint, + * and notify WeaveMessageLayer that a new BLE conneciotn has been received. + * The message layer then wraps the new BLEEndPoint object in a + * WeaveConnection, and hands this object to the application via the message + * layer's OnConnectionReceived callback. + * + * Second, the application can actively form an outgoing BLE connection, e.g., + * by connecting to a BLE peripheral. It then creates a new WeaveConnection + * via the WeaveMessageLayer, assigns an authentication type to this + * connection, and binds it to the BLE_CONNECTION_OBJECT for the new BLE + * connection via WeaveConnection::ConnectBle. This function then + * establishes the secure session type specified by the WeaveConnection's + * authentication type member variable. + * + */ +class NL_DLL_EXPORT BleLayer +{ + friend class BLEEndPoint; +#if WEAVE_ENABLE_WOBLE_TEST + friend class WoBleTest; +#endif + +public: + // Public data members: + enum + { + kState_NotInitialized = 0, + kState_Initialized = 1 + } mState; ///< [READ-ONLY] Current state + + void * mAppState; + + typedef void (*BleConnectionReceivedFunct)(BLEEndPoint * newEndPoint); + BleConnectionReceivedFunct OnWeaveBleConnectReceived; + +public: + // Public functions: + BleLayer(void); + + BLE_ERROR Init(BlePlatformDelegate * platformDelegate, BleApplicationDelegate * appDelegate, + Weave::System::Layer * systemLayer); + +#if BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + BLE_ERROR Init(BlePlatformDelegate * platformDelegate, BleApplicationDelegate * appDelegate, Inet::InetLayer * inetLayer); +#endif // BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + + BLE_ERROR Shutdown(void); + + BLE_ERROR NewBleEndPoint(BLEEndPoint ** retEndPoint, BLE_CONNECTION_OBJECT connObj, BleRole role, bool autoClose); + + nl::Weave::System::Error ScheduleWork(nl::Weave::System::Layer::TimerCompleteFunct aComplete, void* aAppState) + { + return mSystemLayer->ScheduleWork(aComplete, aAppState); + } + + /**< Platform interface functions: + + * Calling conventions: + * Weave takes ownership of PacketBuffers received through these functions, + * and will free them or pass ownership up the stack. + * + * Beyond each call, no guarantees are provided as to the lifetime of UUID arguments. + * + * A 'true' return value means the Weave stack successfully handled the + * corresponding message or state indication. 'false' means the Weave stack either + * failed or chose not to handle this. In case of 'false,' the Weave stack will not + * have freed or taken ownership of any PacketBuffer arguments. This contract allows the + * platform to pass BLE events to Weave without needing to know which characteristics + * Weave cares about. + + * Platform must call this function when a GATT subscription has been established to any Weave service + * charateristic. + * + * If this function returns true, Weave has accepted the BLE connection and wrapped it + * in a WeaveConnection object. If Weave accepts a BLE connection, the platform MUST + * notify Weave if the subscription is canceled or the underlying BLE connection is + * closed, or the associated WeaveConnection will never be closed or freed. */ + bool HandleSubscribeReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /// Call when a GATT subscribe request succeeds. + bool HandleSubscribeComplete(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /**< Platform must call this function when a GATT unsubscribe is requested on any Weave + * service charateristic, that is, when an existing GATT subscription on a Weave service + * characteristic is canceled. */ + bool HandleUnsubscribeReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /// Call when a GATT unsubscribe request succeeds. + bool HandleUnsubscribeComplete(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /// Call when a GATT write request is received. + bool HandleWriteReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf); + + /// Call when a GATT indication is received. + bool HandleIndicationReceived(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf); + + /// Call when an outstanding GATT write request receives a positive receipt confirmation. + bool HandleWriteConfirmation(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /// Call when an oustanding GATT indication receives a positive receipt confirmation. + bool HandleIndicationConfirmation(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId); + + /// Call when a GATT read request is received. + bool HandleReadReceived(BLE_CONNECTION_OBJECT connObj, BLE_READ_REQUEST_CONTEXT requestContext, const WeaveBleUUID * svcId, + const WeaveBleUUID * charId); + + /**< Platform must call this function when any previous operation undertaken by the BleLayer via BleAdapter + * fails, such as a characteristic write request or subscribe attempt, or when a BLE connection is closed. + * + * In most cases, this will prompt Weave to close the associated WeaveConnection and notify that platform that + * it has abandoned the underlying BLE connection. + * + * NOTE: if the application explicitly closes a BLE connection with an associated WeaveConnection such that + * the BLE connection close will not generate an upcall to Weave, HandleConnectionError must be called with + * err = BLE_ERROR_APP_CLOSED_CONNECTION to prevent the leak of this WeaveConnection and its end point object. */ + void HandleConnectionError(BLE_CONNECTION_OBJECT connObj, BLE_ERROR err); + +#if WEAVE_ENABLE_WOBLE_TEST + BLEEndPoint * mTestBleEndPoint; +#endif + +private: + // Private data members: + + // UUID of Weave service characteristic used for central writes. + static const WeaveBleUUID WEAVE_BLE_CHAR_1_ID; + // UUID of Weave service characteristic used for peripheral indications. + static const WeaveBleUUID WEAVE_BLE_CHAR_2_ID; + + BlePlatformDelegate * mPlatformDelegate; + BleApplicationDelegate * mApplicationDelegate; + Weave::System::Layer * mSystemLayer; + +private: + // Private functions: + void HandleDataReceived(BLE_CONNECTION_OBJECT connObj, PacketBuffer * pBuf); + void HandleAckReceived(BLE_CONNECTION_OBJECT connObj); + void DriveSending(void); + BLE_ERROR HandleBleTransportConnectionInitiated(BLE_CONNECTION_OBJECT connObj, PacketBuffer * pBuf); + + static BleTransportProtocolVersion GetHighestSupportedProtocolVersion(const BleTransportCapabilitiesRequestMessage & reqMsg); +}; + +#if BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES +inline BLE_ERROR BleLayer::Init(BlePlatformDelegate * aPlatformDelegate, BleApplicationDelegate * aAppDelegate, + Inet::InetLayer * aInetLayer) +{ + return Init(aPlatformDelegate, aAppDelegate, aInetLayer->SystemLayer()); +} +#endif // BLE_CONFIG_PROVIDE_OBSOLESCENT_INTERFACES + +} /* namespace Ble */ +} /* namespace nl */ + +#include "BLEEndPoint.h" + +#endif /* BLELAYER_H_ */ diff --git a/src/ble/BlePlatformDelegate.h b/src/ble/BlePlatformDelegate.h new file mode 100644 index 00000000000000..4425cec6dc13fa --- /dev/null +++ b/src/ble/BlePlatformDelegate.h @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines the interface for downcalls from BleLayer + * to a platform's BLE framework. + */ + +#ifndef BLEPLATFORMDELEGATE_H_ +#define BLEPLATFORMDELEGATE_H_ + +#include +#include + +#include +#include + +namespace nl { +namespace Ble { + +using ::nl::Weave::System::PacketBuffer; + +// Platform-agnostic BLE interface +class NL_DLL_EXPORT BlePlatformDelegate +{ +public: + // Following APIs must be implemented by platform: + + // Subscribe to updates and indications on the specfied characteristic + virtual bool SubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, + const WeaveBleUUID * charId) = 0; + + // Unsubscribe from updates and indications on the specified characteristic + virtual bool UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, + const WeaveBleUUID * charId) = 0; + + // Close the underlying BLE connection. + virtual bool CloseConnection(BLE_CONNECTION_OBJECT connObj) = 0; + + // Get MTU size negotiated for specified BLE connection. Return value of 0 means MTU size could not be determined. + virtual uint16_t GetMTU(BLE_CONNECTION_OBJECT connObj) const = 0; + + // Data path calling convention: + // The Weave stack retains partial ownership of pBufs sent via the below functions. These buffers are freed by + // Weave after either they're acknowledged by the peer's BLE controller, or Weave shuts down the pBuf's + // associated BLEEndPoint. + // + // For its part, the platform MUST call PacketBuffer::Free on each pBuf it receives via a Send* function once it no + // longer requires a reference to this buffer, e.g. when a NL_CLIENT_EVENT_BLE_PBUF_CLEAR event is received on + // platforms with the Nest BLE SDK. + // + // On platforms such as iOS or Android where the contents of the pBuf PacketBuffer are copied into a separate + // buffer for transmission, pBuf may be freed on the downcall to the platform delegate once the copy completes. + // + // A 'true' return value from a Send* function indicates that the characteristic was written or updated + // successfully. A 'false' value indicates failure, and is used to report this failure to the user via the return + // value of WeaveConnection::SendMessage. + // + // If a Send* function returns false, it must release its reference to pBuf prior to return. + + // Send GATT characteristic indication request + virtual bool SendIndication(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf) = 0; + + // Send GATT characteristic write request + virtual bool SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf) = 0; + + // Send GATT characteristic read request + virtual bool SendReadRequest(BLE_CONNECTION_OBJECT connObj, const WeaveBleUUID * svcId, const WeaveBleUUID * charId, + PacketBuffer * pBuf) = 0; + + // Send response to remote host's GATT chacteristic read response + virtual bool SendReadResponse(BLE_CONNECTION_OBJECT connObj, BLE_READ_REQUEST_CONTEXT requestContext, + const WeaveBleUUID * svcId, const WeaveBleUUID * charId) = 0; +}; + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* BLEPLATFORMDELEGATE_H_ */ diff --git a/src/ble/BleUUID.cpp b/src/ble/BleUUID.cpp new file mode 100644 index 00000000000000..9831ed0d2c3189 --- /dev/null +++ b/src/ble/BleUUID.cpp @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#if CONFIG_NETWORK_LAYER_BLE + +#include +#include + +#include "BleUUID.h" + +namespace nl { +namespace Ble { + +const WeaveBleUUID WEAVE_BLE_SVC_ID = { { // 0000FEAF-0000-1000-8000-00805F9B34FB + 0x00, 0x00, 0xFE, 0xAF, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, + 0xFB } }; + +bool UUIDsMatch(const WeaveBleUUID * idOne, const WeaveBleUUID * idTwo) +{ + if ((idOne == NULL) || (idTwo == NULL)) + { + return false; + } + return (memcmp(idOne->bytes, idTwo->bytes, 16) == 0); +} + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* CONFIG_NETWORK_LAYER_BLE */ diff --git a/src/ble/BleUUID.h b/src/ble/BleUUID.h new file mode 100644 index 00000000000000..b0102af166e6f0 --- /dev/null +++ b/src/ble/BleUUID.h @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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. + */ +#ifndef BLEUUID_H_ +#define BLEUUID_H_ + +#include + +namespace nl { +namespace Ble { + +// Type to represent 128-bit BLE UUIDs. 16-bit short UUIDs may be combined with +// the Bluetooth Base UUID to form full 128-bit UUIDs as described in the +// Service Discovery Protocol (SDP) definition, part of the Bluetooth Core +// Specification. +typedef struct +{ + uint8_t bytes[16]; +} WeaveBleUUID; + +// UUID of Nest Weave BLE service. Exposed for use in scan filter. +extern const WeaveBleUUID WEAVE_BLE_SVC_ID; + +bool UUIDsMatch(const WeaveBleUUID * idOne, const WeaveBleUUID * idTwo); + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* BLEUUID_H_ */ diff --git a/src/ble/Makefile.am b/src/ble/Makefile.am new file mode 100644 index 00000000000000..40e2cd7847468f --- /dev/null +++ b/src/ble/Makefile.am @@ -0,0 +1,44 @@ +# +# +# Copyright (c) 2014-2017 Nest Labs, Inc. +# All rights reserved. +# +# 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. +# + +# +# Description: +# This file is the GNU automake template for the Nest BleLayer +# library. +# + +include $(abs_top_nlbuild_autotools_dir)/automake/pre.am + +include BleLayer.am + +lib_LIBRARIES = libBleLayer.a + +libBleLayer_a_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(LWIP_CPPFLAGS) \ + $(NULL) + +if WEAVE_ENABLE_WOBLE_TEST +libBleLayer_a_CPPFLAGS += \ + -I$(top_srcdir)/src/device-manager\ + $(NULL) +endif # WEAVE_ENABLE_WOBLE_TEST + +libBleLayer_a_SOURCES = $(nl_BleLayer_sources) + +include $(abs_top_nlbuild_autotools_dir)/automake/post.am diff --git a/src/ble/WeaveBleServiceData.h b/src/ble/WeaveBleServiceData.h new file mode 100644 index 00000000000000..4b654851b91a6a --- /dev/null +++ b/src/ble/WeaveBleServiceData.h @@ -0,0 +1,111 @@ +/* + * + * Copyright (c) 2019 Google LLC. + * All rights reserved. + * + * 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 + * Definitions for Weave BLE service advertisement data. + */ + +#ifndef WEAVE_BLE_SERVICE_DATA_H +#define WEAVE_BLE_SERVICE_DATA_H + +namespace nl { +namespace Ble { + +/** + * Weave data block types that may appear with Weave BLE service advertisement data. + */ +enum WeaveBLEServiceDataType +{ + kWeaveBLEServiceDataType_DeviceIdentificationInfo = 0x01, + kWeaveBLEServiceDataType_TokenIdentificationInfo = 0x02, +}; + +/** + * Weave BLE Device Identification Information Block + * + * Defines the over-the-air encoded format of the device identification information block that appears + * within Weave BLE service advertisement data. + */ +struct WeaveBLEDeviceIdentificationInfo +{ + enum + { + kMajorVersion = 0, + kMinorVersion = 1, + }; + + enum + { + kPairingStatus_Unpaired = 0, + kPairingStatus_Paired = 1, + }; + + uint8_t BlockLen; + uint8_t BlockType; + uint8_t MajorVersion; + uint8_t MinorVersion; + uint8_t DeviceVendorId[2]; + uint8_t DeviceProductId[2]; + uint8_t DeviceId[8]; + uint8_t PairingStatus; + + void Init() + { + memset(this, 0, sizeof(*this)); + BlockLen = sizeof(*this) - sizeof(BlockLen); // size of all fields EXCEPT BlockLen + BlockType = kWeaveBLEServiceDataType_DeviceIdentificationInfo; + MajorVersion = kMajorVersion; + MinorVersion = kMinorVersion; + } + + uint16_t GetVendorId(void) + { + return nl::Weave::Encoding::LittleEndian::Get16(DeviceVendorId); + } + + void SetVendorId(uint16_t vendorId) + { + nl::Weave::Encoding::LittleEndian::Put16(DeviceVendorId, vendorId); + } + + uint16_t GetProductId(void) + { + return nl::Weave::Encoding::LittleEndian::Get16(DeviceProductId); + } + + void SetProductId(uint16_t productId) + { + nl::Weave::Encoding::LittleEndian::Put16(DeviceProductId, productId); + } + + uint64_t GetDeviceId(void) + { + return nl::Weave::Encoding::LittleEndian::Get64(DeviceId); + } + + void SetDeviceId(uint64_t deviceId) + { + nl::Weave::Encoding::LittleEndian::Put64(DeviceId, deviceId); + } +} __attribute__((packed)); + +} /* namespace Ble */ +} /* namespace nl */ + +#endif // WEAVE_BLE_SERVICE_DATA_H diff --git a/src/ble/WoBle.cpp b/src/ble/WoBle.cpp new file mode 100644 index 00000000000000..f0442200777305 --- /dev/null +++ b/src/ble/WoBle.cpp @@ -0,0 +1,630 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 types and an object for the Weave over + * Bluetooth Low Energy (WoBLE) byte-stream, connection-oriented + * adaptation of Weave for point-to-point Bluetooth Low Energy + * (BLE) links. + * + */ + +#include + +#if CONFIG_NETWORK_LAYER_BLE + +#include +#if WEAVE_ENABLE_WOBLE_TEST +#include +#endif + +#include +#include + +// Define below to enable extremely verbose BLE-specific debug logging. +#undef NL_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED + +#ifdef NL_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED +#define WeaveLogDebugBtpEngine(MOD, MSG, ...) WeaveLogError(MOD, MSG, ##__VA_ARGS__) +#else +#define WeaveLogDebugBtpEngine(MOD, MSG, ...) +#endif + +#define NL_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE 1 // Size in bytes of enocded BTP fragment header flag bits +#define NL_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE 1 // Size in bytes of encoded BTP sequence number +#define NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE 1 // Size in bytes of encoded BTP fragment acknowledgement number +#define NL_BLE_TRANSFER_PROTOCOL_MSG_LEN_SIZE 2 // Size in byte of encoded BTP total fragmented message length + +#define NL_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE \ + (NL_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE + NL_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE + \ + NL_BLE_TRANSFER_PROTOCOL_MSG_LEN_SIZE) + +#define NL_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE \ + (NL_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE + NL_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE) + +#define NL_BLE_TRANSFER_PROTOCOL_STANDALONE_ACK_HEADER_SIZE \ + (NL_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE + NL_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE) + +namespace nl { +namespace Ble { + +static inline void IncSeqNum(SequenceNumber_t & a_seq_num) +{ + a_seq_num = 0xff & ((a_seq_num) + 1); +} + +static inline bool DidReceiveData(uint8_t rx_flags) +{ + return (GetFlag(rx_flags, WoBle::kHeaderFlag_StartMessage) || + GetFlag(rx_flags, WoBle::kHeaderFlag_ContinueMessage) || + GetFlag(rx_flags, WoBle::kHeaderFlag_EndMessage)); +} + +static void PrintBufDebug(PacketBuffer * buf) +{ +#ifdef NL_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED + uint8_t * b = buf->Start(); + + for (int i = 0; i < buf->DataLength(); i++) + { + WeaveLogError(Ble, "\t%02x", b[i]); + } +#endif +} + +const uint16_t WoBle::sDefaultFragmentSize = 20; // 23-byte minimum ATT_MTU - 3 bytes for ATT operation header +const uint16_t WoBle::sMaxFragmentSize = 128; // Size of write and indication characteristics + +BLE_ERROR WoBle::Init(void * an_app_state, bool expect_first_ack) +{ + mAppState = an_app_state; + mRxState = kState_Idle; + mRxBuf = NULL; + mRxNewestUnackedSeqNum = 0; + mRxOldestUnackedSeqNum = 0; + mRxFragmentSize = sDefaultFragmentSize; + mTxState = kState_Idle; + mTxBuf = NULL; + mTxFragmentSize = sDefaultFragmentSize; + mRxCharCount = 0; + mRxPacketCount = 0; + mTxCharCount = 0; + mTxPacketCount = 0; + mTxNewestUnackedSeqNum = 0; + mTxOldestUnackedSeqNum = 0; +#if WEAVE_ENABLE_WOBLE_TEST + mTxPacketType = kType_Data; // Default WoBle Data packet + mRxPacketType = kType_Data; // Default WoBle Data packet +#endif + + if (expect_first_ack) + { + mTxNextSeqNum = 1; + mExpectingAck = true; + mRxNextSeqNum = 0; + } + else + { + mTxNextSeqNum = 0; + mExpectingAck = false; + mRxNextSeqNum = 1; + } + + return BLE_NO_ERROR; +} + +SequenceNumber_t WoBle::GetAndIncrementNextTxSeqNum() +{ + SequenceNumber_t ret = mTxNextSeqNum; + + // If not already expecting ack... + if (!mExpectingAck) + { + mExpectingAck = true; + mTxOldestUnackedSeqNum = mTxNextSeqNum; + } + + // Update newest unacknowledged sequence number. + mTxNewestUnackedSeqNum = mTxNextSeqNum; + + // Increment mTxNextSeqNum. + IncSeqNum(mTxNextSeqNum); + + return ret; +} + +SequenceNumber_t WoBle::GetAndRecordRxAckSeqNum() +{ + SequenceNumber_t ret = mRxNewestUnackedSeqNum; + + mRxNewestUnackedSeqNum = mRxNextSeqNum; + mRxOldestUnackedSeqNum = mRxNextSeqNum; + + return ret; +} + +bool WoBle::HasUnackedData() const +{ + return (mRxOldestUnackedSeqNum != mRxNextSeqNum); +} + +bool WoBle::IsValidAck(SequenceNumber_t ack_num) const +{ + WeaveLogDebugBtpEngine(Ble, "entered IsValidAck, ack = %u, oldest = %u, newest = %u", ack_num, mTxOldestUnackedSeqNum, + mTxNewestUnackedSeqNum); + + // Return false if not awaiting any ack. + if (!mExpectingAck) + { + WeaveLogDebugBtpEngine(Ble, "unexpected ack is invalid"); + return false; + } + + // Assumption: maximum valid sequence number equals maximum value of SequenceNumber_t. + + if (mTxNewestUnackedSeqNum >= mTxOldestUnackedSeqNum) // If current unacked interval does NOT wrap... + { + return (ack_num <= mTxNewestUnackedSeqNum && ack_num >= mTxOldestUnackedSeqNum); + } + else // Else, if current unacked interval DOES wrap... + { + return (ack_num <= mTxNewestUnackedSeqNum || ack_num >= mTxOldestUnackedSeqNum); + } +} + +BLE_ERROR WoBle::HandleAckReceived(SequenceNumber_t ack_num) +{ + BLE_ERROR err = BLE_NO_ERROR; + + WeaveLogDebugBtpEngine(Ble, "entered HandleAckReceived, ack_num = %u", ack_num); + + // Ensure ack_num falls within range of ack values we're expecting. + VerifyOrExit(IsValidAck(ack_num), err = BLE_ERROR_INVALID_ACK); + + if (mTxNewestUnackedSeqNum == ack_num) // If ack is for newest outstanding unacknowledged fragment... + { + mTxOldestUnackedSeqNum = ack_num; + + // All oustanding fragments have been acknowledged. + mExpectingAck = false; + } + else // If ack is valid, but not for newest oustanding unacknowledged fragment... + { + // Update newest unacknowledged fragment to one past that which was just acknowledged. + mTxOldestUnackedSeqNum = ack_num; + IncSeqNum(mTxOldestUnackedSeqNum); + } + +exit: + return err; +} + +// Calling convention: +// EncodeStandAloneAck may only be called if data arg is commited for immediate, synchronous subsequent transmission. +BLE_ERROR WoBle::EncodeStandAloneAck(PacketBuffer * data) +{ + BLE_ERROR err = BLE_NO_ERROR; + uint8_t * characteristic; + + // Ensure enough headroom exists for the lower BLE layers. + VerifyOrExit(data->EnsureReservedSize(WEAVE_CONFIG_BLE_PKT_RESERVED_SIZE), err = BLE_ERROR_NO_MEMORY); + + // Ensure enough space for standalone ack payload. + VerifyOrExit(data->MaxDataLength() >= NL_BLE_TRANSFER_PROTOCOL_STANDALONE_ACK_HEADER_SIZE, err = BLE_ERROR_NO_MEMORY); + characteristic = data->Start(); + + // Since there's no preexisting message payload, we can write BTP header without adjusting data start pointer. + characteristic[0] = kHeaderFlag_FragmentAck; + + // Acknowledge most recently received sequence number. + characteristic[1] = GetAndRecordRxAckSeqNum(); + WeaveLogDebugBtpEngine(Ble, "===> encoded stand-alone ack = %u", characteristic[1]); + + // Include sequence number for stand-alone ack itself. + characteristic[2] = GetAndIncrementNextTxSeqNum(); + + // Set ack payload data length. + data->SetDataLength(NL_BLE_TRANSFER_PROTOCOL_STANDALONE_ACK_HEADER_SIZE); + +exit: + return err; +} + +// Calling convention: +// WoBle does not retain ownership of reassembled messages, layer above needs to free when done. +// +// WoBle does not reset itself on error. Upper layer should free outbound message and inbound reassembly buffers +// if there is a problem. + +// HandleCharacteristicReceived(): +// +// Non-NULL characteristic data arg is always either designated as or appended to the message reassembly buffer, +// or freed if it holds a stand-alone ack. In all cases, caller must clear its reference to data arg when this +// function returns. +// +// Upper layer must immediately clean up and reinitialize protocol engine if returned err != BLE_NO_ERROR. +BLE_ERROR WoBle::HandleCharacteristicReceived(PacketBuffer * data, SequenceNumber_t & receivedAck, bool & didReceiveAck) +{ + BLE_ERROR err = BLE_NO_ERROR; + uint8_t rx_flags = 0; + uint8_t cursor = 0; + uint8_t * characteristic = data->Start(); + + VerifyOrExit(data != NULL, err = BLE_ERROR_BAD_ARGS); + + mRxCharCount++; + + // Get header flags, always in first byte. + rx_flags = characteristic[cursor++]; +#if WEAVE_ENABLE_WOBLE_TEST + if (GetFlag(rx_flags, kHeaderFlag_CommandMessage)) + SetRxPacketType(kType_Control); + else + SetRxPacketType(kType_Data); +#endif + + didReceiveAck = GetFlag(rx_flags, kHeaderFlag_FragmentAck); + + // Get ack number, if any. + if (didReceiveAck) + { + receivedAck = characteristic[cursor++]; + + err = HandleAckReceived(receivedAck); + SuccessOrExit(err); + } + + // Get sequence number. + mRxNewestUnackedSeqNum = characteristic[cursor++]; + + // Verify that received sequence number is the next one we'd expect. + VerifyOrExit(mRxNewestUnackedSeqNum == mRxNextSeqNum, err = BLE_ERROR_INVALID_BTP_SEQUENCE_NUMBER); + + // Increment next expected rx sequence number. + IncSeqNum(mRxNextSeqNum); + + // If fragment was stand-alone ack, we're done here; no payload for message reassembler. + if (!DidReceiveData(rx_flags)) + { + // Free stand-alone ack buffer. + PacketBuffer::Free(data); + data = NULL; + + ExitNow(); + } + + // Truncate the incoming fragment length by the mRxFragmentSize as the negotiated + // mRxFragnentSize may be smaller than the characteristic size. + data->SetDataLength(nl::Weave::min(data->DataLength(), mRxFragmentSize)); + + WeaveLogDebugBtpEngine(Ble, ">>> BTP reassembler received data:"); + PrintBufDebug(data); + + if (mRxState == kState_Idle) + { + // Verify StartMessage header flag set. + VerifyOrExit(rx_flags & kHeaderFlag_StartMessage, err = BLE_ERROR_INVALID_BTP_HEADER_FLAGS); + + mRxLength = (characteristic[(cursor + 1)] << 8) | characteristic[cursor]; + cursor += 2; + + mRxState = kState_InProgress; + + data->SetStart(&(characteristic[cursor])); + + // Create a new buffer for use as the Rx re-assembly area. + mRxBuf = PacketBuffer::New(); + + VerifyOrExit(mRxBuf != NULL, err = BLE_ERROR_NO_MEMORY); + + mRxBuf->AddToEnd(data); + mRxBuf->CompactHead(); // will free 'data' and adjust rx buf's end/length + data = NULL; + } + else if (mRxState == kState_InProgress) + { + // Verify StartMessage header flag NOT set, since we're in the middle of receiving a message. + VerifyOrExit((rx_flags & kHeaderFlag_StartMessage) == 0, err = BLE_ERROR_INVALID_BTP_HEADER_FLAGS); + + // Verify ContinueMessage or EndMessage header flag set. + VerifyOrExit((rx_flags & kHeaderFlag_ContinueMessage) || (rx_flags & kHeaderFlag_EndMessage), + err = BLE_ERROR_INVALID_BTP_HEADER_FLAGS); + + // Add received fragment to reassembled message buffer. + data->SetStart(&(characteristic[cursor])); + mRxBuf->AddToEnd(data); + mRxBuf->CompactHead(); // will free 'data' and adjust rx buf's end/length + data = NULL; + + // For now, limit WoBle message size to max length of 1 pbuf, as we do for Weave messages sent via IP. + // TODO add support for WoBle messages longer than 1 pbuf + VerifyOrExit(mRxBuf->Next() == NULL, err = BLE_ERROR_RECEIVED_MESSAGE_TOO_BIG); + } + else + { + err = BLE_ERROR_REASSEMBLER_INCORRECT_STATE; + ExitNow(); + } + + if (rx_flags & kHeaderFlag_EndMessage) + { + // Trim remainder, if any, of received PacketBuffer based on sender-specified length of reassembled message. + int padding = mRxBuf->DataLength() - mRxLength; + + if (padding > 0) + { + mRxBuf->SetDataLength(mRxLength); + } + + // Ensure all received fragments add up to sender-specified total message size. + VerifyOrExit(mRxBuf->DataLength() == mRxLength, err = BLE_ERROR_REASSEMBLER_MISSING_DATA); + + // We've reassembled the entire message. + mRxState = kState_Complete; + mRxPacketCount++; + } + +exit: + if (err != BLE_NO_ERROR) + { + mRxState = kState_Error; + + // Dump protocol engine state, plus header flags and received data length. + WeaveLogError(Ble, "HandleCharacteristicReceived failed, err = %d, rx_flags = %u", err, rx_flags); + if (didReceiveAck) + { + WeaveLogError(Ble, "With rx'd ack = %u", receivedAck); + } + if (mRxBuf != NULL) + { + WeaveLogError(Ble, "With rx buf data length = %u", mRxBuf->DataLength()); + } + LogState(); + + if (data != NULL) + { + // Tack received data onto rx buffer, to be freed when end point resets protocol engine on close. + if (mRxBuf != NULL) + { + mRxBuf->AddToEnd(data); + } + else + { + mRxBuf = data; + } + } + } + + return err; +} + +PacketBuffer * WoBle::RxPacket() +{ + return mRxBuf; +} + +bool WoBle::ClearRxPacket() +{ + if (mRxState == kState_Complete) + { + mRxState = kState_Idle; + mRxBuf = NULL; + // do not reset mRxNextSeqNum + return true; + } + + return false; +} + +// Calling convention: +// May only be called if data arg is commited for immediate, synchronous subsequent transmission. +// Returns false on error. Caller must free data arg on error. +bool WoBle::HandleCharacteristicSend(PacketBuffer * data, bool send_ack) +{ + uint8_t * characteristic; + mTxCharCount++; + + if (send_ack && !HasUnackedData()) + { + WeaveLogError(Ble, "HandleCharacteristicSend: send_ack true, but nothing to acknowledge."); + return false; + } + + if (mTxState == kState_Idle) + { + if (data == NULL) + { + return false; + } + + mTxBuf = data; + mTxState = kState_InProgress; + mTxLength = mTxBuf->DataLength(); + + WeaveLogDebugBtpEngine(Ble, ">>> WoBle preparing to send whole message:"); + PrintBufDebug(data); + + // Determine fragment header size. + uint8_t header_size = send_ack ? NL_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE + : (NL_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE - NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE); + + // Ensure enough headroom exists for the BTP header, and any headroom needed by the lower BLE layers. + if (!mTxBuf->EnsureReservedSize(header_size + WEAVE_CONFIG_BLE_PKT_RESERVED_SIZE)) + { + // handle error + WeaveLogError(Ble, "HandleCharacteristicSend: not enough headroom"); + mTxState = kState_Error; + mTxBuf = NULL; // Avoid double-free after assignment above, as caller frees data on error. + + return false; + } + + // prepend header. + characteristic = mTxBuf->Start(); + characteristic -= header_size; + mTxBuf->SetStart(characteristic); + uint8_t cursor = 1; // first position past header flags byte + + characteristic[0] = kHeaderFlag_StartMessage; + +#if WEAVE_ENABLE_WOBLE_TEST + if (TxPacketType() == kType_Control) + SetFlag(characteristic[0], kHeaderFlag_CommandMessage, true); +#endif + + if (send_ack) + { + SetFlag(characteristic[0], kHeaderFlag_FragmentAck, true); + characteristic[cursor++] = GetAndRecordRxAckSeqNum(); + WeaveLogDebugBtpEngine(Ble, "===> encoded piggybacked ack, ack_num = %u", characteristic[cursor - 1]); + } + + characteristic[cursor++] = GetAndIncrementNextTxSeqNum(); + characteristic[cursor++] = mTxLength & 0xff; + characteristic[cursor++] = mTxLength >> 8; + + if ((mTxLength + cursor) <= mTxFragmentSize) + { + mTxBuf->SetDataLength(mTxLength + cursor); + mTxLength = 0; + SetFlag(characteristic[0], kHeaderFlag_EndMessage, true); + mTxState = kState_Complete; + mTxPacketCount++; + } + else + { + mTxBuf->SetDataLength(mTxFragmentSize); + mTxLength -= mTxFragmentSize - cursor; + } + + WeaveLogDebugBtpEngine(Ble, ">>> WoBle preparing to send first fragment:"); + PrintBufDebug(data); + } + else if (mTxState == kState_InProgress) + { + if (data != NULL) + { + return false; + } + + // advance past the previous fragment + characteristic = mTxBuf->Start(); + characteristic += mTxFragmentSize; + + // prepend header + characteristic -= send_ack + ? NL_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE + : (NL_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE - NL_BLE_TRANSFER_PROTOCOL_ACK_SIZE); + mTxBuf->SetStart(characteristic); + uint8_t cursor = 1; // first position past header flags byte + + characteristic[0] = kHeaderFlag_ContinueMessage; + +#if WEAVE_ENABLE_WOBLE_TEST + if (TxPacketType() == kType_Control) + SetFlag(characteristic[0], kHeaderFlag_CommandMessage, true); +#endif + + if (send_ack) + { + SetFlag(characteristic[0], kHeaderFlag_FragmentAck, true); + characteristic[cursor++] = GetAndRecordRxAckSeqNum(); + WeaveLogDebugBtpEngine(Ble, "===> encoded piggybacked ack, ack_num = %u", characteristic[cursor - 1]); + } + + characteristic[cursor++] = GetAndIncrementNextTxSeqNum(); + + if ((mTxLength + cursor) <= mTxFragmentSize) + { + mTxBuf->SetDataLength(mTxLength + cursor); + mTxLength = 0; + SetFlag(characteristic[0], kHeaderFlag_EndMessage, true); + mTxState = kState_Complete; + mTxPacketCount++; + } + else + { + mTxBuf->SetDataLength(mTxFragmentSize); + mTxLength -= mTxFragmentSize - cursor; + } + + WeaveLogDebugBtpEngine(Ble, ">>> WoBle preparing to send additional fragment:"); + PrintBufDebug(mTxBuf); + } + else + { + // Invalid tx state. + return false; + } + + return true; +} + +PacketBuffer * WoBle::TxPacket() +{ + return mTxBuf; +} + +bool WoBle::ClearTxPacket() +{ + if (mTxState == kState_Complete) + { + mTxState = kState_Idle; + mTxBuf = NULL; + // do not reset mTxNextSeqNum + return true; + } + + return false; +} + +void WoBle::LogState() const +{ + WeaveLogError(Ble, "mAppState: %p", mAppState); + + WeaveLogError(Ble, "mRxFragmentSize: %d", mRxFragmentSize); + WeaveLogError(Ble, "mRxState: %d", mRxState); + WeaveLogError(Ble, "mRxBuf: %p", mRxBuf); + WeaveLogError(Ble, "mRxNextSeqNum: %d", mRxNextSeqNum); + WeaveLogError(Ble, "mRxNewestUnackedSeqNum: %d", mRxNewestUnackedSeqNum); + WeaveLogError(Ble, "mRxOldestUnackedSeqNum: %d", mRxOldestUnackedSeqNum); + WeaveLogError(Ble, "mRxCharCount: %d", mRxCharCount); + WeaveLogError(Ble, "mRxPacketCount: %d", mRxPacketCount); + + WeaveLogError(Ble, "mTxFragmentSize: %d", mTxFragmentSize); + WeaveLogError(Ble, "mTxState: %d", mTxState); + WeaveLogError(Ble, "mTxBuf: %p", mTxBuf); + WeaveLogError(Ble, "mTxNextSeqNum: %d", mTxNextSeqNum); + WeaveLogError(Ble, "mTxNewestUnackedSeqNum: %d", mTxNewestUnackedSeqNum); + WeaveLogError(Ble, "mTxOldestUnackedSeqNum: %d", mTxOldestUnackedSeqNum); + WeaveLogError(Ble, "mTxCharCount: %d", mTxCharCount); + WeaveLogError(Ble, "mTxPacketCount: %d", mTxPacketCount); +} + +void WoBle::LogStateDebug() const +{ +#ifdef NL_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED + LogState(); +#endif +} + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* CONFIG_NETWORK_LAYER_BLE */ diff --git a/src/ble/WoBle.h b/src/ble/WoBle.h new file mode 100644 index 00000000000000..c8a7d048df5ea5 --- /dev/null +++ b/src/ble/WoBle.h @@ -0,0 +1,194 @@ +/* + * + * Copyright (c) 2014-2017 Nest Labs, Inc. + * All rights reserved. + * + * 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 defines types and an object for the Weave over + * Bluetooth Low Energy (WoBLE) byte-stream, connection-oriented + * adaptation of Weave for point-to-point Bluetooth Low Energy + * (BLE) links. + * + */ + +#ifndef WOBLE_H_ +#define WOBLE_H_ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include +#include + +#include +#include +#include +#include + +namespace nl { +namespace Ble { + +using ::nl::Weave::System::PacketBuffer; + +typedef uint8_t SequenceNumber_t; // If type changed from uint8_t, adjust assumptions in WoBle::IsValidAck and + // BLEEndPoint::AdjustReceiveWindow. + +#if WEAVE_ENABLE_WOBLE_TEST +class BLEEndPoint; +#endif + +// Public data members: +typedef enum +{ + kType_Data = 0, // Default 0 for data + kType_Control = 1, +} PacketType_t; // WoBle packet types + +class WoBle +{ +#if WEAVE_ENABLE_WOBLE_TEST + friend class BLEEndPoint; +#endif + +public: + // Public data members: + typedef enum + { + kState_Idle = 0, + kState_InProgress = 1, + kState_Complete = 2, + kState_Error = 3 + } State_t; // [READ-ONLY] Current state + + enum + { + kHeaderFlag_StartMessage = 0x01, + kHeaderFlag_ContinueMessage = 0x02, + kHeaderFlag_EndMessage = 0x04, + kHeaderFlag_FragmentAck = 0x08, +#if WEAVE_ENABLE_WOBLE_TEST + kHeaderFlag_CommandMessage = 0x10, +#endif + }; // Masks for BTP fragment header flag bits. + + static const uint16_t sDefaultFragmentSize; + static const uint16_t sMaxFragmentSize; + +public: + // Public functions: + WoBle(void) { }; + ~WoBle(void) { }; + + BLE_ERROR Init(void * an_app_state, bool expect_first_ack); + + inline void SetTxFragmentSize(uint8_t size) { mTxFragmentSize = size; }; + inline void SetRxFragmentSize(uint8_t size) { mRxFragmentSize = size; }; + + uint16_t GetRxFragmentSize(void) { return mRxFragmentSize; }; + uint16_t GetTxFragmentSize(void) { return mTxFragmentSize; }; + + SequenceNumber_t GetAndIncrementNextTxSeqNum(void); + SequenceNumber_t GetAndRecordRxAckSeqNum(void); + + inline SequenceNumber_t GetLastReceivedSequenceNumber(void) { return mRxNewestUnackedSeqNum; }; + inline SequenceNumber_t GetNewestUnackedSentSequenceNumber(void) { return mTxNewestUnackedSeqNum; }; + + inline bool ExpectingAck(void) const { return mExpectingAck; }; + + inline State_t RxState(void) { return mRxState; } + inline State_t TxState(void) { return mTxState; } +#if WEAVE_ENABLE_WOBLE_TEST + inline PacketType_t SetTxPacketType(PacketType_t type) { return (mTxPacketType = type); }; + inline PacketType_t SetRxPacketType(PacketType_t type) { return (mRxPacketType = type); }; + inline PacketType_t TxPacketType() { return mTxPacketType; }; + inline PacketType_t RxPacketType() { return mRxPacketType; }; + inline SequenceNumber_t SetTxPacketSeq(SequenceNumber_t seq) { return (mTxPacketSeq = seq); }; + inline SequenceNumber_t SetRxPacketSeq(SequenceNumber_t seq) { return (mRxPacketSeq = seq); }; + inline SequenceNumber_t TxPacketSeq() { return mTxPacketSeq; }; + inline SequenceNumber_t RxPacketSeq() { return mRxPacketSeq; }; + inline bool IsCommandPacket(PacketBuffer * p) { return GetFlag(*(p->Start()), kHeaderFlag_CommandMessage); } + inline void PushPacketTag(PacketBuffer * p, PacketType_t type) + { + p->SetStart(p->Start() - sizeof(type)); + memcpy(p->Start(), &type, sizeof(type)); + }; + inline PacketType_t PopPacketTag(PacketBuffer * p) + { + PacketType_t type; + memcpy(&type, p->Start(), sizeof(type)); + p->SetStart(p->Start() + sizeof(type)); + return type; + }; +#endif // WEAVE_ENABLE_WOBLE_TEST + + bool HasUnackedData(void) const; + + BLE_ERROR HandleCharacteristicReceived(PacketBuffer * data, SequenceNumber_t & receivedAck, bool & didReceiveAck); + bool HandleCharacteristicSend(PacketBuffer * data, bool send_ack); + BLE_ERROR EncodeStandAloneAck(PacketBuffer * data); + + PacketBuffer * RxPacket(void); + PacketBuffer * TxPacket(void); + + bool ClearRxPacket(void); + bool ClearTxPacket(void); + + void LogState(void) const; + void LogStateDebug(void) const; + +private: + // Private data members: +#if WEAVE_ENABLE_WOBLE_TEST + PacketType_t mTxPacketType; + PacketType_t mRxPacketType; + SequenceNumber_t mTxPacketSeq; + SequenceNumber_t mRxPacketSeq; +#endif + State_t mRxState; + uint16_t mRxLength; + void * mAppState; + PacketBuffer * mRxBuf; + SequenceNumber_t mRxNextSeqNum; + SequenceNumber_t mRxNewestUnackedSeqNum; + SequenceNumber_t mRxOldestUnackedSeqNum; + uint16_t mRxFragmentSize; + + State_t mTxState; + uint16_t mTxLength; + PacketBuffer * mTxBuf; + SequenceNumber_t mTxNextSeqNum; + SequenceNumber_t mTxNewestUnackedSeqNum; + SequenceNumber_t mTxOldestUnackedSeqNum; + bool mExpectingAck; + uint16_t mTxFragmentSize; + + uint16_t mRxCharCount; + uint16_t mRxPacketCount; + uint16_t mTxCharCount; + uint16_t mTxPacketCount; + +private: + // Private functions: + bool IsValidAck(SequenceNumber_t ack_num) const; + BLE_ERROR HandleAckReceived(SequenceNumber_t ack_num); +}; + +} /* namespace Ble */ +} /* namespace nl */ + +#endif /* WOBLE_H_ */