Unit tests for BLE layer class (#33359)

* Unit tests for BLE layer class

* Check for mBleTransport NULL before dereference

* Return false in case of handle error in BLE layer

* Check for NULL UUIDs match

* Test for exceeding max number of BLE connections

* Fix compilation on ESP32

* More compilation fixes

* Mark SetUp and TearDown as overrides

* Update includes
diff --git a/src/ble/BLEEndPoint.cpp b/src/ble/BLEEndPoint.cpp
index ba6400f..0e69e59 100644
--- a/src/ble/BLEEndPoint.cpp
+++ b/src/ble/BLEEndPoint.cpp
@@ -346,7 +346,7 @@
                 DoCloseCallback(oldState, flags, err);
             }
 
-            if ((flags & kBleCloseFlag_SuppressCallback) != 0)
+            if (mBleTransport != nullptr && (flags & kBleCloseFlag_SuppressCallback) != 0)
             {
                 mBleTransport->OnEndPointConnectionClosed(this, err);
             }
@@ -367,7 +367,7 @@
         DoCloseCallback(oldState, flags, err);
     }
 
-    if ((flags & kBleCloseFlag_SuppressCallback) != 0)
+    if (mBleTransport != nullptr && (flags & kBleCloseFlag_SuppressCallback) != 0)
     {
         mBleTransport->OnEndPointConnectionClosed(this, err);
     }
@@ -1290,7 +1290,7 @@
         // Take ownership of message buffer
         System::PacketBufferHandle full_packet = mBtpEngine.TakeRxPacket();
 
-        ChipLogDebugBleEndPoint(Ble, "reassembled whole msg, len = %d", full_packet->DataLength());
+        ChipLogDebugBleEndPoint(Ble, "reassembled whole msg, len = %u", static_cast<unsigned>(full_packet->DataLength()));
 
         // If we have a message received callback, and end point is not closing...
         if (mBleTransport != nullptr && mState != kState_Closing)
diff --git a/src/ble/BleLayer.cpp b/src/ble/BleLayer.cpp
index d4a1e32..de1f845 100644
--- a/src/ble/BleLayer.cpp
+++ b/src/ble/BleLayer.cpp
@@ -22,7 +22,7 @@
  *      a platform's Bluetooth Low Energy (BLE) implementation and the CHIP
  *      stack.
  *
- *      The BleLayer obect accepts BLE data and control input from the
+ *      The BleLayer object accepts BLE data and control input from the
  *      application via a functional interface. It performs the fragmentation
  *      and reassembly required to transmit CHIP message via a BLE GATT
  *      characteristic interface, and drives incoming messages up the CHIP
@@ -485,43 +485,24 @@
 bool BleLayer::HandleWriteReceived(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId,
                                    PacketBufferHandle && pBuf)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Write received on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_1_ID, charId), false, ChipLogError(Ble, "Write received on unknown char"));
+    VerifyOrReturnError(!pBuf.IsNull(), false, ChipLogError(Ble, "Write received null buffer"));
+
+    // Find matching connection end point.
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+
+    if (endPoint != nullptr)
     {
-        ChipLogError(Ble, "ble write rcvd on unknown svc id");
-        return true;
-    }
-
-    if (UUIDsMatch(&CHIP_BLE_CHAR_1_ID, charId))
-    {
-        if (pBuf.IsNull())
-        {
-            ChipLogError(Ble, "rcvd null ble write");
-            return true;
-        }
-
-        // Find matching connection end point.
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
-
-        if (endPoint != nullptr)
-        {
-            CHIP_ERROR status = endPoint->Receive(std::move(pBuf));
-            if (status != CHIP_NO_ERROR)
-            {
-                ChipLogError(Ble, "BLEEndPoint rcv failed, err = %" CHIP_ERROR_FORMAT, status.Format());
-            }
-        }
-        else
-        {
-            CHIP_ERROR status = HandleBleTransportConnectionInitiated(connObj, std::move(pBuf));
-            if (status != CHIP_NO_ERROR)
-            {
-                ChipLogError(Ble, "failed handle new chip BLE connection, status = %" CHIP_ERROR_FORMAT, status.Format());
-            }
-        }
+        CHIP_ERROR err = endPoint->Receive(std::move(pBuf));
+        VerifyOrReturnError(err == CHIP_NO_ERROR, false,
+                            ChipLogError(Ble, "Receive failed, err = %" CHIP_ERROR_FORMAT, err.Format()));
     }
     else
     {
-        ChipLogError(Ble, "ble write rcvd on unknown char");
+        CHIP_ERROR err = HandleBleTransportConnectionInitiated(connObj, std::move(pBuf));
+        VerifyOrReturnError(err == CHIP_NO_ERROR, false,
+                            ChipLogError(Ble, "Handle new BLE connection failed, err = %" CHIP_ERROR_FORMAT, err.Format()));
     }
 
     return true;
@@ -530,197 +511,102 @@
 bool BleLayer::HandleIndicationReceived(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId,
                                         PacketBufferHandle && pBuf)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Indication received on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId), false, ChipLogError(Ble, "Indication received on unknown char"));
+    VerifyOrReturnError(!pBuf.IsNull(), false, ChipLogError(Ble, "Indication received null buffer"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId))
-    {
-        if (pBuf.IsNull())
-        {
-            ChipLogError(Ble, "rcvd null ble indication");
-            return true;
-        }
+    // Find matching connection end point.
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(Ble, "No endpoint for received indication"));
 
-        // find matching connection end point.
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
-
-        if (endPoint != nullptr)
-        {
-            CHIP_ERROR status = endPoint->Receive(std::move(pBuf));
-            if (status != CHIP_NO_ERROR)
-            {
-                ChipLogError(Ble, "BLEEndPoint rcv failed, err = %" CHIP_ERROR_FORMAT, status.Format());
-            }
-        }
-        else
-        {
-            ChipLogDetail(Ble, "no endpoint for rcvd indication");
-        }
-    }
-    else
-    {
-        ChipLogError(Ble, "ble ind rcvd on unknown char");
-    }
+    CHIP_ERROR err = endPoint->Receive(std::move(pBuf));
+    VerifyOrReturnError(err == CHIP_NO_ERROR, false, ChipLogError(Ble, "Receive failed, err = %" CHIP_ERROR_FORMAT, err.Format()));
 
     return true;
 }
 
 bool BleLayer::HandleWriteConfirmation(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Write confirmation on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_1_ID, charId), false, ChipLogError(Ble, "Write confirmation on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_1_ID, charId))
-    {
-        HandleAckReceived(connObj);
-    }
-    else
-    {
-        ChipLogError(Ble, "ble write con rcvd on unknown char");
-    }
-
+    HandleAckReceived(connObj);
     return true;
 }
 
 bool BleLayer::HandleIndicationConfirmation(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Indication confirmation on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId), false,
+                        ChipLogError(Ble, "Indication confirmation on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId))
-    {
-        HandleAckReceived(connObj);
-    }
-    else
-    {
-        ChipLogError(Ble, "ble ind con rcvd on unknown char");
-    }
-
+    HandleAckReceived(connObj);
     return true;
 }
 
 void BleLayer::HandleAckReceived(BLE_CONNECTION_OBJECT connObj)
 {
-    // find matching connection end point.
+    // Find matching connection end point.
     BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturn(endPoint != nullptr, ChipLogDetail(Ble, "No endpoint for received ack"));
 
-    if (endPoint != nullptr)
-    {
-        CHIP_ERROR status = endPoint->HandleGattSendConfirmationReceived();
-
-        if (status != CHIP_NO_ERROR)
-        {
-            ChipLogError(Ble, "endpoint conf recvd failed, err = %" CHIP_ERROR_FORMAT, status.Format());
-        }
-    }
-    else
-    {
-        ChipLogError(Ble, "no endpoint for BLE sent data ack");
-    }
+    CHIP_ERROR err = endPoint->HandleGattSendConfirmationReceived();
+    VerifyOrReturn(err == CHIP_NO_ERROR,
+                   ChipLogError(Ble, "Send ack confirmation failed, err = %" CHIP_ERROR_FORMAT, err.Format()));
 }
 
 bool BleLayer::HandleSubscribeReceived(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Subscribe received on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId), false,
+                        ChipLogError(Ble, "Subscribe received on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId))
-    {
-        // Find end point already associated with BLE connection, if any.
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    // Find end point already associated with BLE connection, if any.
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(Ble, "No endpoint for received subscribe"));
 
-        if (endPoint != nullptr)
-        {
-            endPoint->HandleSubscribeReceived();
-        }
-        else
-        {
-            ChipLogError(Ble, "no endpoint for sub recvd");
-        }
-    }
-
+    endPoint->HandleSubscribeReceived();
     return true;
 }
 
 bool BleLayer::HandleSubscribeComplete(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Subscribe complete on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId), false,
+                        ChipLogError(Ble, "Subscribe complete on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId))
-    {
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(Ble, "No endpoint for subscribe complete"));
 
-        if (endPoint != nullptr)
-        {
-            endPoint->HandleSubscribeComplete();
-        }
-        else
-        {
-            ChipLogError(Ble, "no endpoint for sub complete");
-        }
-    }
-
+    endPoint->HandleSubscribeComplete();
     return true;
 }
 
 bool BleLayer::HandleUnsubscribeReceived(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Unsubscribe received on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId), false,
+                        ChipLogError(Ble, "Unsubscribe received on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId))
-    {
-        // Find end point already associated with BLE connection, if any.
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    // Find end point already associated with BLE connection, if any.
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(Ble, "No endpoint for unsubscribe received"));
 
-        if (endPoint != nullptr)
-        {
-            endPoint->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_CENTRAL_UNSUBSCRIBED);
-        }
-        else
-        {
-            ChipLogError(Ble, "no endpoint for unsub recvd");
-        }
-    }
-
+    endPoint->DoClose(kBleCloseFlag_AbortTransmission, BLE_ERROR_CENTRAL_UNSUBSCRIBED);
     return true;
 }
 
 bool BleLayer::HandleUnsubscribeComplete(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    if (!UUIDsMatch(&CHIP_BLE_SVC_ID, svcId))
-    {
-        return false;
-    }
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_SVC_ID, svcId), false, ChipLogError(Ble, "Unsubscribe complete on unknown svc"));
+    VerifyOrReturnError(UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId), false,
+                        ChipLogError(Ble, "Unsubscribe complete on unknown char"));
 
-    if (UUIDsMatch(&CHIP_BLE_CHAR_2_ID, charId) || UUIDsMatch(&CHIP_BLE_CHAR_3_ID, charId))
-    {
-        // Find end point already associated with BLE connection, if any.
-        BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    // Find end point already associated with BLE connection, if any.
+    BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(Ble, "No endpoint for unsubscribe complete"));
 
-        if (endPoint != nullptr)
-        {
-            endPoint->HandleUnsubscribeComplete();
-        }
-        else
-        {
-            ChipLogError(Ble, "no endpoint for unsub complete");
-        }
-    }
-
+    endPoint->HandleUnsubscribeComplete();
     return true;
 }
 
@@ -728,19 +614,17 @@
 {
     // BLE connection has failed somehow, we must find and abort matching connection end point.
     BLEEndPoint * endPoint = sBLEEndPointPool.Find(connObj);
+    VerifyOrReturn(endPoint != nullptr, ChipLogDetail(Ble, "No endpoint for connection error"));
 
-    if (endPoint != nullptr)
+    if (err == BLE_ERROR_GATT_UNSUBSCRIBE_FAILED && endPoint->IsUnsubscribePending())
     {
-        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);
-        }
+        // 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);
     }
 }
 
diff --git a/src/ble/BleLayer.h b/src/ble/BleLayer.h
index bad3ab3..e7d619f 100644
--- a/src/ble/BleLayer.h
+++ b/src/ble/BleLayer.h
@@ -327,7 +327,6 @@
 
     // Private functions:
     void HandleAckReceived(BLE_CONNECTION_OBJECT connObj);
-    void DriveSending();
     CHIP_ERROR HandleBleTransportConnectionInitiated(BLE_CONNECTION_OBJECT connObj, System::PacketBufferHandle && pBuf);
 
     static BleTransportProtocolVersion GetHighestSupportedProtocolVersion(const BleTransportCapabilitiesRequestMessage & reqMsg);
diff --git a/src/ble/tests/BUILD.gn b/src/ble/tests/BUILD.gn
index 687ad7b..ac897ab 100644
--- a/src/ble/tests/BUILD.gn
+++ b/src/ble/tests/BUILD.gn
@@ -22,11 +22,15 @@
 
   test_sources = [
     "TestBleErrorStr.cpp",
+    "TestBleLayer.cpp",
     "TestBleUUID.cpp",
     "TestBtpEngine.cpp",
   ]
 
   cflags = [ "-Wconversion" ]
 
-  public_deps = [ "${chip_root}/src/ble" ]
+  public_deps = [
+    "${chip_root}/src/ble",
+    "${chip_root}/src/platform",
+  ]
 }
diff --git a/src/ble/tests/TestBleLayer.cpp b/src/ble/tests/TestBleLayer.cpp
new file mode 100644
index 0000000..9b79037
--- /dev/null
+++ b/src/ble/tests/TestBleLayer.cpp
@@ -0,0 +1,400 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+#include <lib/core/CHIPError.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/Span.h>
+#include <lib/support/TypeTraits.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <system/SystemLayer.h>
+#include <system/SystemPacketBuffer.h>
+
+#define _CHIP_BLE_BLE_H
+#include <ble/BleApplicationDelegate.h>
+#include <ble/BleLayer.h>
+#include <ble/BleLayerDelegate.h>
+#include <ble/BlePlatformDelegate.h>
+
+namespace chip {
+namespace Ble {
+
+namespace {
+
+constexpr ChipBleUUID uuidZero{};
+constexpr ChipBleUUID uuidSvc   = { { 0x00, 0x00, 0xFF, 0xF6, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34,
+                                      0xFB } };
+constexpr ChipBleUUID uuidChar1 = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, 0x9D,
+                                      0x11 } };
+constexpr ChipBleUUID uuidChar2 = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, 0x9D,
+                                      0x12 } };
+constexpr ChipBleUUID uuidChar3 = { { 0x64, 0x63, 0x02, 0x38, 0x87, 0x72, 0x45, 0xF2, 0xB8, 0x7D, 0x74, 0x8A, 0x83, 0x21, 0x8F,
+                                      0x04 } };
+
+}; // namespace
+
+class TestBleLayer : public BleLayer,
+                     private BleApplicationDelegate,
+                     private BleLayerDelegate,
+                     private BlePlatformDelegate,
+                     public ::testing::Test
+{
+public:
+    static void SetUpTestSuite()
+    {
+        ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
+        ASSERT_EQ(DeviceLayer::SystemLayer().Init(), CHIP_NO_ERROR);
+    }
+
+    static void TearDownTestSuite()
+    {
+        DeviceLayer::SystemLayer().Shutdown();
+        chip::Platform::MemoryShutdown();
+    }
+
+    void SetUp() override
+    {
+        ASSERT_EQ(Init(this, this, &DeviceLayer::SystemLayer()), CHIP_NO_ERROR);
+        mBleTransport = this;
+    }
+
+    void TearDown() override
+    {
+        mBleTransport = nullptr;
+        Shutdown();
+    }
+
+    // Return unique BLE connection object for each call.
+    template <typename T = BLE_CONNECTION_OBJECT>
+    BLE_CONNECTION_OBJECT GetConnectionObject()
+    {
+        T conn = BLE_CONNECTION_UNINITIALIZED;
+
+        if constexpr (std::is_pointer_v<T>)
+        {
+            conn = reinterpret_cast<T>(&mNumConnection + mNumConnection);
+        }
+        else
+        {
+            conn = static_cast<T>(mNumConnection);
+        }
+
+        mNumConnection++;
+        return conn;
+    }
+
+    // Passing capabilities request message to HandleWriteReceived should create
+    // new BLE endpoint which later can be used to receive more data.
+    bool HandleWriteReceivedCapabilitiesRequest(BLE_CONNECTION_OBJECT connObj)
+    {
+        constexpr uint8_t capReq[] = { 0x65, 0x6c, 0x54, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x06 };
+        auto buf                   = System::PacketBufferHandle::NewWithData(capReq, sizeof(capReq));
+        return HandleWriteReceived(connObj, &uuidSvc, &uuidChar1, std::move(buf));
+    }
+
+    // Processing subscription request after capabilities request should finalize
+    // connection establishment.
+    bool HandleSubscribeReceivedOnChar2(BLE_CONNECTION_OBJECT connObj)
+    {
+        return HandleSubscribeReceived(connObj, &uuidSvc, &uuidChar2);
+    }
+
+    ///
+    // Implementation of BleApplicationDelegate
+
+    void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj) override {}
+
+    ///
+    // Implementation of BleLayerDelegate
+
+    void OnBleConnectionComplete(BLEEndPoint * endpoint) override {}
+    void OnBleConnectionError(CHIP_ERROR err) override {}
+    void OnEndPointConnectComplete(BLEEndPoint * endPoint, CHIP_ERROR err) override {}
+    void OnEndPointMessageReceived(BLEEndPoint * endPoint, System::PacketBufferHandle && msg) override {}
+    void OnEndPointConnectionClosed(BLEEndPoint * endPoint, CHIP_ERROR err) override {}
+    CHIP_ERROR SetEndPoint(BLEEndPoint * endPoint) override { return CHIP_NO_ERROR; }
+
+    ///
+    // Implementation of BlePlatformDelegate
+
+    bool SubscribeCharacteristic(BLE_CONNECTION_OBJECT, const ChipBleUUID *, const ChipBleUUID *) override { return true; }
+    bool UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT, const ChipBleUUID *, const ChipBleUUID *) override { return true; }
+    bool CloseConnection(BLE_CONNECTION_OBJECT connObj) override { return true; }
+    uint16_t GetMTU(BLE_CONNECTION_OBJECT connObj) const override { return 0; }
+    bool SendIndication(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId,
+                        PacketBufferHandle pBuf) override
+    {
+        return true;
+    }
+    bool SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId,
+                          PacketBufferHandle pBuf) override
+    {
+        return true;
+    }
+    bool SendReadRequest(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId,
+                         PacketBufferHandle pBuf) override
+    {
+        return true;
+    }
+    bool SendReadResponse(BLE_CONNECTION_OBJECT connObj, BLE_READ_REQUEST_CONTEXT requestContext, const ChipBleUUID * svcId,
+                          const ChipBleUUID * charId) override
+    {
+        return true;
+    }
+
+private:
+    unsigned int mNumConnection = 0;
+};
+
+TEST_F(TestBleLayer, CheckBleTransportCapabilitiesRequestMessage)
+{
+    auto buf = System::PacketBufferHandle::New(100);
+    ASSERT_FALSE(buf.IsNull());
+
+    BleTransportCapabilitiesRequestMessage msg{};
+    msg.SetSupportedProtocolVersion(0, CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION);
+    msg.SetSupportedProtocolVersion(1, CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION + 1);
+    msg.mMtu        = 200;
+    msg.mWindowSize = BLE_MAX_RECEIVE_WINDOW_SIZE;
+
+    ASSERT_EQ(msg.Encode(buf), CHIP_NO_ERROR);
+    ChipLogByteSpan(Test, ByteSpan(buf->Start(), buf->DataLength()));
+
+    BleTransportCapabilitiesRequestMessage msgVerify;
+    ASSERT_EQ(BleTransportCapabilitiesRequestMessage::Decode(buf, msgVerify), CHIP_NO_ERROR);
+    EXPECT_EQ(memcmp(msg.mSupportedProtocolVersions, msgVerify.mSupportedProtocolVersions, sizeof(msg.mSupportedProtocolVersions)),
+              0);
+    EXPECT_EQ(msg.mMtu, msgVerify.mMtu);
+    EXPECT_EQ(msg.mWindowSize, msgVerify.mWindowSize);
+}
+
+TEST_F(TestBleLayer, CheckBleTransportCapabilitiesResponseMessage)
+{
+    auto buf = System::PacketBufferHandle::New(100);
+    ASSERT_FALSE(buf.IsNull());
+
+    BleTransportCapabilitiesResponseMessage msg{};
+    msg.mSelectedProtocolVersion = CHIP_BLE_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION;
+    msg.mFragmentSize            = 200;
+    msg.mWindowSize              = BLE_MAX_RECEIVE_WINDOW_SIZE;
+
+    EXPECT_EQ(msg.Encode(buf), CHIP_NO_ERROR);
+    ChipLogByteSpan(Test, ByteSpan(buf->Start(), buf->DataLength()));
+
+    BleTransportCapabilitiesResponseMessage msgVerify;
+    ASSERT_EQ(BleTransportCapabilitiesResponseMessage::Decode(buf, msgVerify), CHIP_NO_ERROR);
+    EXPECT_EQ(msg.mSelectedProtocolVersion, msgVerify.mSelectedProtocolVersion);
+    EXPECT_EQ(msg.mFragmentSize, msgVerify.mFragmentSize);
+    EXPECT_EQ(msg.mWindowSize, msgVerify.mWindowSize);
+}
+
+TEST_F(TestBleLayer, HandleWriteReceivedCapabilitiesRequest)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+}
+
+TEST_F(TestBleLayer, HandleSubscribeReceivedInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleSubscribeReceived(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleSubscribeReceived(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleSubscribeReceived)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    EXPECT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+}
+
+TEST_F(TestBleLayer, HandleSubscribeCompleteInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleSubscribeComplete(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleSubscribeComplete(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleSubscribeComplete)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    ASSERT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+
+    EXPECT_TRUE(HandleSubscribeComplete(connObj, &uuidSvc, &uuidChar2));
+}
+
+TEST_F(TestBleLayer, HandleUnsubscribeReceivedInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleUnsubscribeReceived(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleUnsubscribeReceived(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleUnsubscribeReceived)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    ASSERT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+
+    EXPECT_TRUE(HandleUnsubscribeReceived(connObj, &uuidSvc, &uuidChar2));
+}
+
+TEST_F(TestBleLayer, HandleUnsubscribeCompleteInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleUnsubscribeComplete(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleUnsubscribeComplete(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleUnsubscribeComplete)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    ASSERT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+
+    EXPECT_TRUE(HandleUnsubscribeComplete(connObj, &uuidSvc, &uuidChar2));
+}
+
+TEST_F(TestBleLayer, HandleWriteReceivedInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    auto buf     = System::PacketBufferHandle::New(0);
+    ASSERT_FALSE(buf.IsNull());
+
+    EXPECT_FALSE(HandleWriteReceived(connObj, &uuidZero, &uuidZero, buf.Retain()));
+    EXPECT_FALSE(HandleWriteReceived(connObj, &uuidSvc, &uuidChar3, std::move(buf)));
+}
+
+TEST_F(TestBleLayer, HandleWriteReceived)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    ASSERT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+
+    constexpr uint8_t data[] = { to_underlying(BtpEngine::HeaderFlags::kStartMessage) |
+                                     to_underlying(BtpEngine::HeaderFlags::kEndMessage),
+                                 0x00, 0x01, 0x00, 0xff };
+    auto buf                 = System::PacketBufferHandle::NewWithData(data, sizeof(data));
+    ASSERT_FALSE(buf.IsNull());
+
+    EXPECT_TRUE(HandleWriteReceived(connObj, &uuidSvc, &uuidChar1, std::move(buf)));
+}
+
+TEST_F(TestBleLayer, HandleWriteConfirmationInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleWriteConfirmation(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleWriteConfirmation(connObj, &uuidSvc, &uuidChar2));
+}
+
+TEST_F(TestBleLayer, HandleWriteConfirmationUninitialized)
+{
+    ASSERT_FALSE(HandleWriteReceivedCapabilitiesRequest(BLE_CONNECTION_UNINITIALIZED));
+}
+
+TEST_F(TestBleLayer, HandleWriteConfirmation)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+
+    EXPECT_TRUE(HandleWriteConfirmation(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleIndicationReceivedInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    auto buf     = System::PacketBufferHandle::New(0);
+    ASSERT_FALSE(buf.IsNull());
+
+    EXPECT_FALSE(HandleIndicationReceived(connObj, &uuidZero, &uuidZero, buf.Retain()));
+    EXPECT_FALSE(HandleIndicationReceived(connObj, &uuidSvc, &uuidChar1, std::move(buf)));
+}
+
+TEST_F(TestBleLayer, HandleIndicationReceived)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+    ASSERT_TRUE(HandleSubscribeReceivedOnChar2(connObj));
+
+    constexpr uint8_t data[] = { to_underlying(BtpEngine::HeaderFlags::kStartMessage) |
+                                     to_underlying(BtpEngine::HeaderFlags::kEndMessage),
+                                 0x00, 0x01, 0x00, 0xff };
+    auto buf                 = System::PacketBufferHandle::NewWithData(data, sizeof(data));
+    ASSERT_FALSE(buf.IsNull());
+
+    EXPECT_TRUE(HandleIndicationReceived(connObj, &uuidSvc, &uuidChar2, std::move(buf)));
+}
+
+TEST_F(TestBleLayer, HandleIndicationConfirmationInvalidUUID)
+{
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleIndicationConfirmation(connObj, &uuidZero, &uuidZero));
+    EXPECT_FALSE(HandleIndicationConfirmation(connObj, &uuidSvc, &uuidChar1));
+}
+
+TEST_F(TestBleLayer, HandleIndicationConfirmation)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+
+    EXPECT_TRUE(HandleIndicationConfirmation(connObj, &uuidSvc, &uuidChar2));
+}
+
+TEST_F(TestBleLayer, HandleConnectionError)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+
+    HandleConnectionError(connObj, CHIP_ERROR_ACCESS_DENIED);
+}
+
+TEST_F(TestBleLayer, CloseBleConnectionUninitialized)
+{
+    CloseBleConnection(BLE_CONNECTION_UNINITIALIZED);
+}
+
+TEST_F(TestBleLayer, CloseBleConnection)
+{
+    auto connObj = GetConnectionObject();
+    ASSERT_TRUE(HandleWriteReceivedCapabilitiesRequest(connObj));
+
+    CloseBleConnection(connObj);
+}
+
+TEST_F(TestBleLayer, ExceedBleConnectionEndPointLimit)
+{
+    for (size_t i = 0; i < BLE_LAYER_NUM_BLE_ENDPOINTS; i++)
+    {
+        // Saturate BLE end-point pool
+        EXPECT_TRUE(HandleWriteReceivedCapabilitiesRequest(GetConnectionObject()));
+    }
+
+    auto connObj = GetConnectionObject();
+    EXPECT_FALSE(HandleWriteReceivedCapabilitiesRequest(connObj));
+}
+
+}; // namespace Ble
+}; // namespace chip
diff --git a/src/ble/tests/TestBleUUID.cpp b/src/ble/tests/TestBleUUID.cpp
index 40e425d..4dbc6dd 100644
--- a/src/ble/tests/TestBleUUID.cpp
+++ b/src/ble/tests/TestBleUUID.cpp
@@ -34,6 +34,12 @@
 
 namespace {
 
+TEST(TestBleUUID, CheckUUIDsMatch_NULL)
+{
+    // Test that NULL pointer UUIDs are not equal
+    EXPECT_FALSE(UUIDsMatch(nullptr, nullptr));
+}
+
 TEST(TestBleUUID, CheckStringToUUID_ChipUUID)
 {
     // Test positive scenario - CHIP Service UUID