blob: 6798a7f11a38f6b4674546d15e090fdeb2d7db16 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2014-2017 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This module implements encode, decode, fragmentation and reassembly of
* Bluetooth Transport Layer (BTP) packet types for transport of a
* CHIP-over-Bluetooth Low Energy (CHIPoBLE) byte-stream over point-to-point
* Bluetooth Low Energy (BLE) links.
*
*/
#include <ble/BleConfig.h>
#if CONFIG_NETWORK_LAYER_BLE
#include <ble/BtpEngine.h>
#if CHIP_ENABLE_CHIPOBLE_TEST
#include <ble/BtpEngineTest.h>
#endif
#include <support/CodeUtils.h>
#include <support/logging/CHIPLogging.h>
// Define below to enable extremely verbose BLE-specific debug logging.
#undef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED
#ifdef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED
#define ChipLogDebugBtpEngine(MOD, MSG, ...) ChipLogError(MOD, MSG, ##__VA_ARGS__)
#else
#define ChipLogDebugBtpEngine(MOD, MSG, ...)
#endif
#define CHIP_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE 1 // Size in bytes of enocded BTP fragment header flag bits
#define CHIP_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE 1 // Size in bytes of encoded BTP sequence number
#define CHIP_BLE_TRANSFER_PROTOCOL_ACK_SIZE 1 // Size in bytes of encoded BTP fragment acknowledgement number
#define CHIP_BLE_TRANSFER_PROTOCOL_MSG_LEN_SIZE 2 // Size in byte of encoded BTP total fragmented message length
#define CHIP_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE \
(CHIP_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + CHIP_BLE_TRANSFER_PROTOCOL_ACK_SIZE + \
CHIP_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE + CHIP_BLE_TRANSFER_PROTOCOL_MSG_LEN_SIZE)
#define CHIP_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE \
(CHIP_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + CHIP_BLE_TRANSFER_PROTOCOL_ACK_SIZE + \
CHIP_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE)
#define CHIP_BLE_TRANSFER_PROTOCOL_STANDALONE_ACK_HEADER_SIZE \
(CHIP_BLE_TRANSFER_PROTOCOL_HEADER_FLAGS_SIZE + CHIP_BLE_TRANSFER_PROTOCOL_ACK_SIZE + \
CHIP_BLE_TRANSFER_PROTOCOL_SEQUENCE_NUM_SIZE)
namespace chip {
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, BtpEngine::kHeaderFlag_StartMessage) || GetFlag(rx_flags, BtpEngine::kHeaderFlag_ContinueMessage) ||
GetFlag(rx_flags, BtpEngine::kHeaderFlag_EndMessage));
}
static void PrintBufDebug(PacketBuffer * buf)
{
#ifdef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED
uint8_t * b = buf->Start();
for (int i = 0; i < buf->DataLength(); i++)
{
ChipLogError(Ble, "\t%02x", b[i]);
}
#endif
}
const uint16_t BtpEngine::sDefaultFragmentSize = 20; // 23-byte minimum ATT_MTU - 3 bytes for ATT operation header
const uint16_t BtpEngine::sMaxFragmentSize = 128; // Size of write and indication characteristics
BLE_ERROR BtpEngine::Init(void * an_app_state, bool expect_first_ack)
{
mAppState = an_app_state;
mRxState = kState_Idle;
mRxBuf = nullptr;
mRxNewestUnackedSeqNum = 0;
mRxOldestUnackedSeqNum = 0;
mRxFragmentSize = sDefaultFragmentSize;
mTxState = kState_Idle;
mTxBuf = nullptr;
mTxFragmentSize = sDefaultFragmentSize;
mRxCharCount = 0;
mRxPacketCount = 0;
mTxCharCount = 0;
mTxPacketCount = 0;
mTxNewestUnackedSeqNum = 0;
mTxOldestUnackedSeqNum = 0;
#if CHIP_ENABLE_CHIPOBLE_TEST
mTxPacketType = kType_Data; // Default BtpEngine Data packet
mRxPacketType = kType_Data; // Default BtpEngine 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 BtpEngine::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 BtpEngine::GetAndRecordRxAckSeqNum()
{
SequenceNumber_t ret = mRxNewestUnackedSeqNum;
mRxNewestUnackedSeqNum = mRxNextSeqNum;
mRxOldestUnackedSeqNum = mRxNextSeqNum;
return ret;
}
bool BtpEngine::HasUnackedData() const
{
return (mRxOldestUnackedSeqNum != mRxNextSeqNum);
}
bool BtpEngine::IsValidAck(SequenceNumber_t ack_num) const
{
ChipLogDebugBtpEngine(Ble, "entered IsValidAck, ack = %u, oldest = %u, newest = %u", ack_num, mTxOldestUnackedSeqNum,
mTxNewestUnackedSeqNum);
// Return false if not awaiting any ack.
if (!mExpectingAck)
{
ChipLogDebugBtpEngine(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, if current unacked interval DOES wrap...
return (ack_num <= mTxNewestUnackedSeqNum || ack_num >= mTxOldestUnackedSeqNum);
}
BLE_ERROR BtpEngine::HandleAckReceived(SequenceNumber_t ack_num)
{
BLE_ERROR err = BLE_NO_ERROR;
ChipLogDebugBtpEngine(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 BtpEngine::EncodeStandAloneAck(PacketBuffer * data)
{
BLE_ERROR err = BLE_NO_ERROR;
uint8_t * characteristic;
// Ensure enough headroom exists for the lower BLE layers.
VerifyOrExit(data->EnsureReservedSize(CHIP_CONFIG_BLE_PKT_RESERVED_SIZE), err = BLE_ERROR_NO_MEMORY);
// Ensure enough space for standalone ack payload.
VerifyOrExit(data->MaxDataLength() >= CHIP_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();
ChipLogDebugBtpEngine(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(CHIP_BLE_TRANSFER_PROTOCOL_STANDALONE_ACK_HEADER_SIZE);
exit:
return err;
}
// Calling convention:
// BtpEngine does not retain ownership of reassembled messages, layer above needs to free when done.
//
// BtpEngine 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 BtpEngine::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 != nullptr, err = BLE_ERROR_BAD_ARGS);
mRxCharCount++;
// Get header flags, always in first byte.
rx_flags = characteristic[cursor++];
#if CHIP_ENABLE_CHIPOBLE_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 = nullptr;
ExitNow();
}
// Truncate the incoming fragment length by the mRxFragmentSize as the negotiated
// mRxFragnentSize may be smaller than the characteristic size.
data->SetDataLength(chip::min(data->DataLength(), mRxFragmentSize));
ChipLogDebugBtpEngine(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().Release_ForNow();
VerifyOrExit(mRxBuf != nullptr, err = BLE_ERROR_NO_MEMORY);
mRxBuf->AddToEnd(data);
mRxBuf->CompactHead(); // will free 'data' and adjust rx buf's end/length
data = nullptr;
}
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 = nullptr;
// For now, limit BtpEngine message size to max length of 1 pbuf, as we do for chip messages sent via IP.
// TODO add support for BtpEngine messages longer than 1 pbuf
VerifyOrExit(mRxBuf->Next() == nullptr, 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.
ChipLogError(Ble, "HandleCharacteristicReceived failed, err = %d, rx_flags = %u", err, rx_flags);
if (didReceiveAck)
{
ChipLogError(Ble, "With rx'd ack = %u", receivedAck);
}
if (mRxBuf != nullptr)
{
ChipLogError(Ble, "With rx buf data length = %u", mRxBuf->DataLength());
}
LogState();
if (data != nullptr)
{
// Tack received data onto rx buffer, to be freed when end point resets protocol engine on close.
if (mRxBuf != nullptr)
{
mRxBuf->AddToEnd(data);
}
else
{
mRxBuf = data;
}
}
}
return err;
}
PacketBuffer * BtpEngine::RxPacket()
{
return mRxBuf;
}
bool BtpEngine::ClearRxPacket()
{
if (mRxState == kState_Complete)
{
mRxState = kState_Idle;
mRxBuf = nullptr;
// 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 BtpEngine::HandleCharacteristicSend(PacketBuffer * data, bool send_ack)
{
uint8_t * characteristic;
mTxCharCount++;
if (send_ack && !HasUnackedData())
{
ChipLogError(Ble, "HandleCharacteristicSend: send_ack true, but nothing to acknowledge.");
return false;
}
if (mTxState == kState_Idle)
{
if (data == nullptr)
{
return false;
}
mTxBuf = data;
mTxState = kState_InProgress;
mTxLength = mTxBuf->DataLength();
ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send whole message:");
PrintBufDebug(data);
// Determine fragment header size.
uint8_t header_size = send_ack ? CHIP_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE
: (CHIP_BLE_TRANSFER_PROTOCOL_MAX_HEADER_SIZE - CHIP_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 + CHIP_CONFIG_BLE_PKT_RESERVED_SIZE))
{
// handle error
ChipLogError(Ble, "HandleCharacteristicSend: not enough headroom");
mTxState = kState_Error;
mTxBuf = nullptr; // 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 CHIP_ENABLE_CHIPOBLE_TEST
if (TxPacketType() == kType_Control)
SetFlag(characteristic[0], kHeaderFlag_CommandMessage, true);
#endif
if (send_ack)
{
SetFlag(characteristic[0], kHeaderFlag_FragmentAck, true);
characteristic[cursor++] = GetAndRecordRxAckSeqNum();
ChipLogDebugBtpEngine(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;
}
ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send first fragment:");
PrintBufDebug(data);
}
else if (mTxState == kState_InProgress)
{
if (data != nullptr)
{
return false;
}
// advance past the previous fragment
characteristic = mTxBuf->Start();
characteristic += mTxFragmentSize;
// prepend header
characteristic -= send_ack
? CHIP_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE
: (CHIP_BLE_TRANSFER_PROTOCOL_MID_FRAGMENT_MAX_HEADER_SIZE - CHIP_BLE_TRANSFER_PROTOCOL_ACK_SIZE);
mTxBuf->SetStart(characteristic);
uint8_t cursor = 1; // first position past header flags byte
characteristic[0] = kHeaderFlag_ContinueMessage;
#if CHIP_ENABLE_CHIPOBLE_TEST
if (TxPacketType() == kType_Control)
SetFlag(characteristic[0], kHeaderFlag_CommandMessage, true);
#endif
if (send_ack)
{
SetFlag(characteristic[0], kHeaderFlag_FragmentAck, true);
characteristic[cursor++] = GetAndRecordRxAckSeqNum();
ChipLogDebugBtpEngine(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;
}
ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send additional fragment:");
PrintBufDebug(mTxBuf);
}
else
{
// Invalid tx state.
return false;
}
return true;
}
PacketBuffer * BtpEngine::TxPacket()
{
return mTxBuf;
}
bool BtpEngine::ClearTxPacket()
{
if (mTxState == kState_Complete)
{
mTxState = kState_Idle;
mTxBuf = nullptr;
// do not reset mTxNextSeqNum
return true;
}
return false;
}
void BtpEngine::LogState() const
{
ChipLogError(Ble, "mAppState: %p", mAppState);
ChipLogError(Ble, "mRxFragmentSize: %d", mRxFragmentSize);
ChipLogError(Ble, "mRxState: %d", mRxState);
ChipLogError(Ble, "mRxBuf: %p", mRxBuf);
ChipLogError(Ble, "mRxNextSeqNum: %d", mRxNextSeqNum);
ChipLogError(Ble, "mRxNewestUnackedSeqNum: %d", mRxNewestUnackedSeqNum);
ChipLogError(Ble, "mRxOldestUnackedSeqNum: %d", mRxOldestUnackedSeqNum);
ChipLogError(Ble, "mRxCharCount: %d", mRxCharCount);
ChipLogError(Ble, "mRxPacketCount: %d", mRxPacketCount);
ChipLogError(Ble, "mTxFragmentSize: %d", mTxFragmentSize);
ChipLogError(Ble, "mTxState: %d", mTxState);
ChipLogError(Ble, "mTxBuf: %p", mTxBuf);
ChipLogError(Ble, "mTxNextSeqNum: %d", mTxNextSeqNum);
ChipLogError(Ble, "mTxNewestUnackedSeqNum: %d", mTxNewestUnackedSeqNum);
ChipLogError(Ble, "mTxOldestUnackedSeqNum: %d", mTxOldestUnackedSeqNum);
ChipLogError(Ble, "mTxCharCount: %d", mTxCharCount);
ChipLogError(Ble, "mTxPacketCount: %d", mTxPacketCount);
}
void BtpEngine::LogStateDebug() const
{
#ifdef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED
LogState();
#endif
}
} /* namespace Ble */
} /* namespace chip */
#endif /* CONFIG_NETWORK_LAYER_BLE */