Enable CASE session establishment (#7666)

* Enable CASE session establishment

* persist counter if device is connected

* enable CASE sessions for chip-tool

* address review comments

* add tests for SessionIDAllocator

* address review comments

* address review comments

* add ReserveUpTo

* release resouces on cleanup, and reduce message timeout

* release resources on cleanup and init.

* Remove sleep from ModelCommand

* some cleanup
diff --git a/examples/chip-tool/commands/clusters/ModelCommand.cpp b/examples/chip-tool/commands/clusters/ModelCommand.cpp
index 6323045..f6ecc96 100644
--- a/examples/chip-tool/commands/clusters/ModelCommand.cpp
+++ b/examples/chip-tool/commands/clusters/ModelCommand.cpp
@@ -51,11 +51,10 @@
     {
         chip::DeviceLayer::StackLock lock;
 
-        err = GetExecContext()->commissioner->GetDevice(remoteId, &mDevice);
-        VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(chipTool, "Init failure! No pairing for device: %" PRIu64, localId));
-
-        err = SendCommand(mDevice, mEndPointId);
-        VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(chipTool, "Failed to send message: %s", ErrorStr(err)));
+        err = GetExecContext()->commissioner->GetConnectedDevice(remoteId, &mOnDeviceConnectedCallback,
+                                                                 &mOnDeviceConnectionFailureCallback);
+        VerifyOrExit(err == CHIP_NO_ERROR,
+                     ChipLogError(chipTool, "Failed in initiating connection to the device: %" PRIu64 ", error %d", remoteId, err));
     }
 
     WaitForResponse(kWaitDurationInSeconds);
@@ -65,3 +64,19 @@
 exit:
     return err;
 }
+
+void ModelCommand::OnDeviceConnectedFn(void * context, chip::Controller::Device * device)
+{
+    ModelCommand * command = reinterpret_cast<ModelCommand *>(context);
+    VerifyOrReturn(command != nullptr,
+                   ChipLogError(chipTool, "Device connected, but cannot send the command, as the context is null"));
+    command->SendCommand(device, command->mEndPointId);
+}
+
+void ModelCommand::OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error)
+{
+    ModelCommand * command = reinterpret_cast<ModelCommand *>(context);
+    ChipLogError(chipTool, "Failed in connecting to the device %" PRIu64 ". Error %d", deviceId, error);
+    VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "ModelCommand context is null"));
+    command->SetCommandExitStatus(false);
+}
diff --git a/examples/chip-tool/commands/clusters/ModelCommand.h b/examples/chip-tool/commands/clusters/ModelCommand.h
index f35f162..3583a3f 100644
--- a/examples/chip-tool/commands/clusters/ModelCommand.h
+++ b/examples/chip-tool/commands/clusters/ModelCommand.h
@@ -31,7 +31,10 @@
 class ModelCommand : public Command
 {
 public:
-    ModelCommand(const char * commandName) : Command(commandName) {}
+    ModelCommand(const char * commandName) :
+        Command(commandName), mOnDeviceConnectedCallback(OnDeviceConnectedFn, this),
+        mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
+    {}
 
     void AddArguments() { AddArgument("endpoint-id", CHIP_ZCL_ENDPOINT_MIN, CHIP_ZCL_ENDPOINT_MAX, &mEndPointId); }
 
@@ -41,6 +44,11 @@
     virtual CHIP_ERROR SendCommand(ChipDevice * device, uint8_t endPointId) = 0;
 
 private:
-    ChipDevice * mDevice;
     uint8_t mEndPointId;
+
+    static void OnDeviceConnectedFn(void * context, chip::Controller::Device * device);
+    static void OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error);
+
+    chip::Callback::Callback<chip::Controller::OnDeviceConnected> mOnDeviceConnectedCallback;
+    chip::Callback::Callback<chip::Controller::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
 };
diff --git a/examples/chip-tool/commands/discover/Commands.h b/examples/chip-tool/commands/discover/Commands.h
index 9b81043..ed743f9 100644
--- a/examples/chip-tool/commands/discover/Commands.h
+++ b/examples/chip-tool/commands/discover/Commands.h
@@ -66,10 +66,8 @@
     /////////// DiscoverCommand Interface /////////
     CHIP_ERROR RunCommand(NodeId remoteId, uint64_t fabricId) override
     {
-        ChipDevice * device;
-        ReturnErrorOnFailure(GetExecContext()->commissioner->GetDevice(remoteId, &device));
         ChipLogProgress(chipTool, "Mdns: Updating NodeId: %" PRIx64 " FabricId: %" PRIx64 " ...", remoteId, fabricId);
-        return GetExecContext()->commissioner->UpdateDevice(device, fabricId);
+        return GetExecContext()->commissioner->UpdateDevice(remoteId, fabricId);
     }
 
     /////////// DeviceAddressUpdateDelegate Interface /////////
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp
index 21fc0cc..cc2c664 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.cpp
+++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp
@@ -162,6 +162,20 @@
     SetCommandExitStatus(err == CHIP_NO_ERROR);
 }
 
+void PairingCommand::OnCommissioningComplete(NodeId nodeId, CHIP_ERROR err)
+{
+    if (err == CHIP_NO_ERROR)
+    {
+        ChipLogProgress(chipTool, "Device commissioning completed with success");
+    }
+    else
+    {
+        ChipLogProgress(chipTool, "Device commissioning Failure: %s", ErrorStr(err));
+    }
+
+    SetCommandExitStatus(err == CHIP_NO_ERROR);
+}
+
 CHIP_ERROR PairingCommand::SetupNetwork()
 {
 
@@ -316,13 +330,17 @@
 
 CHIP_ERROR PairingCommand::UpdateNetworkAddress()
 {
-    ReturnErrorOnFailure(GetExecContext()->commissioner->GetDevice(mRemoteId, &mDevice));
     ChipLogProgress(chipTool, "Mdns: Updating NodeId: %" PRIx64 " FabricId: %" PRIx64 " ...", mRemoteId, mFabricId);
-    return GetExecContext()->commissioner->UpdateDevice(mDevice, mFabricId);
+    return GetExecContext()->commissioner->UpdateDevice(mRemoteId, mFabricId);
 }
 
 void PairingCommand::OnAddressUpdateComplete(NodeId nodeId, CHIP_ERROR err)
 {
     ChipLogProgress(chipTool, "OnAddressUpdateComplete: %s", ErrorStr(err));
-    SetCommandExitStatus(CHIP_NO_ERROR == err);
+    if (err != CHIP_NO_ERROR)
+    {
+        // Set exit status only if the address update failed.
+        // Otherwise wait for OnCommissioningComplete() callback.
+        SetCommandExitStatus(false);
+    }
 }
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h
index 7fdc137..abb33af 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.h
+++ b/examples/chip-tool/commands/pairing/PairingCommand.h
@@ -103,6 +103,7 @@
     void OnStatusUpdate(chip::Controller::DevicePairingDelegate::Status status) override;
     void OnPairingComplete(CHIP_ERROR error) override;
     void OnPairingDeleted(CHIP_ERROR error) override;
+    void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) override;
 
     /////////// DeviceAddressUpdateDelegate Interface /////////
     void OnAddressUpdateComplete(NodeId nodeId, CHIP_ERROR error) override;
diff --git a/src/app/server/RendezvousServer.cpp b/src/app/server/RendezvousServer.cpp
index 3055f24..25aa9b2 100644
--- a/src/app/server/RendezvousServer.cpp
+++ b/src/app/server/RendezvousServer.cpp
@@ -100,15 +100,18 @@
     ReturnErrorOnFailure(mExchangeManager->RegisterUnsolicitedMessageHandlerForType(
         Protocols::SecureChannel::MsgType::PBKDFParamRequest, &mPairingSession));
 
+    uint16_t keyID = 0;
+    ReturnErrorOnFailure(mIDAllocator->Allocate(keyID));
+
     if (params.HasPASEVerifier())
     {
-        ReturnErrorOnFailure(mPairingSession.WaitForPairing(params.GetPASEVerifier(), mNextKeyId++, this));
+        ReturnErrorOnFailure(mPairingSession.WaitForPairing(params.GetPASEVerifier(), keyID, this));
     }
     else
     {
         ReturnErrorOnFailure(mPairingSession.WaitForPairing(params.GetSetupPINCode(), kSpake2p_Iteration_Count,
                                                             reinterpret_cast<const unsigned char *>(kSpake2pKeyExchangeSalt),
-                                                            strlen(kSpake2pKeyExchangeSalt), mNextKeyId++, this));
+                                                            strlen(kSpake2pKeyExchangeSalt), keyID, this));
     }
 
     ReturnErrorOnFailure(mPairingSession.MessageDispatch().Init(transportMgr));
@@ -181,6 +184,12 @@
     VerifyOrReturn(connection.StoreIntoKVS(*mStorage) == CHIP_NO_ERROR,
                    ChipLogError(AppServer, "Failed to store the connection state"));
 
-    mStorage->SyncSetKeyValue(kStorablePeerConnectionCountKey, &mNextKeyId, sizeof(mNextKeyId));
+    // The Peek() is used to find the smallest key ID that's not been assigned to any session.
+    // This value is persisted, and on reboot, it is used to revive any previously
+    // active secure sessions.
+    // We support one active PASE session at any time. So the key ID should not be updated
+    // in another thread, while we retrieve it here.
+    uint16_t keyID = mIDAllocator->Peek();
+    mStorage->SyncSetKeyValue(kStorablePeerConnectionCountKey, &keyID, sizeof(keyID));
 }
 } // namespace chip
diff --git a/src/app/server/RendezvousServer.h b/src/app/server/RendezvousServer.h
index 4ab2c21..845dcd5 100644
--- a/src/app/server/RendezvousServer.h
+++ b/src/app/server/RendezvousServer.h
@@ -22,6 +22,7 @@
 #include <messaging/ExchangeMgr.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <protocols/secure_channel/RendezvousParameters.h>
+#include <protocols/secure_channel/SessionIDAllocator.h>
 
 namespace chip {
 
@@ -31,11 +32,17 @@
     CHIP_ERROR WaitForPairing(const RendezvousParameters & params, Messaging::ExchangeManager * exchangeManager,
                               TransportMgrBase * transportMgr, SecureSessionMgr * sessionMgr, Transport::AdminPairingInfo * admin);
 
-    CHIP_ERROR Init(AppDelegate * delegate, PersistentStorageDelegate * storage)
+    CHIP_ERROR Init(AppDelegate * delegate, PersistentStorageDelegate * storage, SessionIDAllocator * idAllocator)
     {
         VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+        mStorage = storage;
+
+        VerifyOrReturnError(idAllocator != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+        mIDAllocator = idAllocator;
+
+        // The caller may chose to not provide a delegate object. The RendezvousServer checks for null delegate before calling
+        // its methods.
         mDelegate = delegate;
-        mStorage  = storage;
         return CHIP_NO_ERROR;
     }
 
@@ -45,8 +52,6 @@
 
     void Cleanup();
 
-    uint16_t GetNextKeyId() const { return mNextKeyId; }
-    void SetNextKeyId(uint16_t id) { mNextKeyId = id; }
     void OnPlatformEvent(const DeviceLayer::ChipDeviceEvent * event);
 
 private:
@@ -55,11 +60,12 @@
     Messaging::ExchangeManager * mExchangeManager = nullptr;
 
     PASESession mPairingSession;
-    uint16_t mNextKeyId            = 0;
     SecureSessionMgr * mSessionMgr = nullptr;
 
     Transport::AdminPairingInfo * mAdmin = nullptr;
 
+    SessionIDAllocator * mIDAllocator = nullptr;
+
     const RendezvousAdvertisementDelegate * mAdvDelegate;
 
     bool HasAdvertisementDelegate() const { return mAdvDelegate != nullptr; }
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index 4198882..b1beb04 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -98,6 +98,7 @@
 };
 
 ServerStorageDelegate gServerStorage;
+SessionIDAllocator gSessionIDAllocator;
 
 CHIP_ERROR PersistAdminPairingToKVS(AdminPairingInfo * admin, AdminId nextAvailableId)
 {
@@ -149,7 +150,7 @@
     }
 }
 
-static CHIP_ERROR RestoreAllSessionsFromKVS(SecureSessionMgr & sessionMgr, RendezvousServer & server)
+static CHIP_ERROR RestoreAllSessionsFromKVS(SecureSessionMgr & sessionMgr)
 {
     uint16_t nextSessionKeyId = 0;
     // It's not an error if the key doesn't exist. Just return right away.
@@ -170,16 +171,22 @@
 
             ChipLogProgress(AppServer, "Fetched the session information: from 0x" ChipLogFormatX64,
                             ChipLogValueX64(session->PeerConnection().GetPeerNodeId()));
-            sessionMgr.NewPairing(Optional<Transport::PeerAddress>::Value(session->PeerConnection().GetPeerAddress()),
-                                  session->PeerConnection().GetPeerNodeId(), session, SecureSession::SessionRole::kResponder,
-                                  connection.GetAdminId(), nullptr);
+            if (gSessionIDAllocator.Reserve(keyId) == CHIP_NO_ERROR)
+            {
+                sessionMgr.NewPairing(Optional<Transport::PeerAddress>::Value(session->PeerConnection().GetPeerAddress()),
+                                      session->PeerConnection().GetPeerNodeId(), session, SecureSession::SessionRole::kResponder,
+                                      connection.GetAdminId(), nullptr);
+            }
+            else
+            {
+                ChipLogProgress(AppServer, "Session Key ID  %" PRIu16 " cannot be used. Skipping over this session", keyId);
+            }
             session->Clear();
         }
     }
 
     chip::Platform::Delete(session);
 
-    server.SetNextKeyId(nextSessionKeyId);
     return CHIP_NO_ERROR;
 }
 
@@ -189,6 +196,7 @@
 
     for (uint16_t keyId = 0; keyId < nextSessionKeyId; keyId++)
     {
+        gSessionIDAllocator.Free(keyId);
         StorablePeerConnection::DeleteFromKVS(gServerStorage, keyId);
     }
 }
@@ -427,9 +435,8 @@
 
     if (resetAdmins == ResetAdmins::kYes)
     {
-        uint16_t nextKeyId = gRendezvousServer.GetNextKeyId();
         EraseAllAdminPairingsUpTo(gNextAvailableAdminId);
-        EraseAllSessionsUpTo(nextKeyId);
+        EraseAllSessionsUpTo(gSessionIDAllocator.Peek());
         // Only resetting gNextAvailableAdminId at reboot otherwise previously paired device with adminID 0
         // can continue sending messages to accessory as next available admin will also be 0.
         // This logic is not up to spec, will be implemented up to spec once AddOptCert is implemented.
@@ -462,7 +469,7 @@
     PersistedStorage::KeyValueStoreMgrImpl().Init("/tmp/chip_server_kvs");
 #endif
 
-    err = gRendezvousServer.Init(delegate, &gServerStorage);
+    err = gRendezvousServer.Init(delegate, &gServerStorage, &gSessionIDAllocator);
     SuccessOrExit(err);
 
     gAdvDelegate.SetDelegate(delegate);
@@ -517,7 +524,7 @@
         VerifyOrExit(CHIP_NO_ERROR == RestoreAllAdminPairingsFromKVS(gAdminPairings, gNextAvailableAdminId),
                      ChipLogError(AppServer, "Could not restore admin table"));
 
-        VerifyOrExit(CHIP_NO_ERROR == RestoreAllSessionsFromKVS(gSessions, gRendezvousServer),
+        VerifyOrExit(CHIP_NO_ERROR == RestoreAllSessionsFromKVS(gSessions),
                      ChipLogError(AppServer, "Could not restore previous sessions"));
     }
     else
@@ -542,7 +549,8 @@
     err = gExchangeMgr.RegisterUnsolicitedMessageHandlerForProtocol(Protocols::ServiceProvisioning::Id, &gCallbacks);
     VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER);
 
-    err = gCASEServer.ListenForSessionEstablishment(&gExchangeMgr, &gTransports, &gSessions, &GetGlobalAdminPairingTable());
+    err = gCASEServer.ListenForSessionEstablishment(&gExchangeMgr, &gTransports, &gSessions, &GetGlobalAdminPairingTable(),
+                                                    &gSessionIDAllocator);
     SuccessOrExit(err);
 
 exit:
diff --git a/src/controller/CHIPDevice.cpp b/src/controller/CHIPDevice.cpp
index e4f5e28..b08d9d1 100644
--- a/src/controller/CHIPDevice.cpp
+++ b/src/controller/CHIPDevice.cpp
@@ -125,8 +125,7 @@
     }
     else
     {
-        Transport::PeerConnectionState * connectionState = nullptr;
-        connectionState                                  = mSessionManager->GetPeerConnectionState(mSecureSession);
+        Transport::PeerConnectionState * connectionState = mSessionManager->GetPeerConnectionState(mSecureSession);
 
         // Check if the connection state has the correct transport information
         if (connectionState == nullptr || connectionState->GetPeerAddress().GetTransportType() == Transport::Type::kUndefined ||
@@ -165,15 +164,26 @@
     serializable.mAdminId    = Encoding::LittleEndian::HostSwap16(mAdminId);
 
     Transport::PeerConnectionState * connectionState = mSessionManager->GetPeerConnectionState(mSecureSession);
-    VerifyOrReturnError(connectionState != nullptr, CHIP_ERROR_INCORRECT_STATE);
-    const uint32_t localMessageCounter = connectionState->GetSessionMessageCounter().GetLocalMessageCounter().Value();
-    const uint32_t peerMessageCounter  = connectionState->GetSessionMessageCounter().GetPeerMessageCounter().GetCounter();
 
-    serializable.mLocalMessageCounter = Encoding::LittleEndian::HostSwap32(localMessageCounter);
-    serializable.mPeerMessageCounter  = Encoding::LittleEndian::HostSwap32(peerMessageCounter);
+    // The connection state could be null if the device is moving from PASE connection to CASE connection.
+    // The device parameters (e.g. mDeviceOperationalCertProvisioned) are updated during this transition.
+    // The state during this transistion is being persisted so that the next access of the device will
+    // trigger the CASE based secure session.
+    if (connectionState != nullptr)
+    {
+        const uint32_t localMessageCounter = connectionState->GetSessionMessageCounter().GetLocalMessageCounter().Value();
+        const uint32_t peerMessageCounter  = connectionState->GetSessionMessageCounter().GetPeerMessageCounter().GetCounter();
 
-    serializable.mCASESessionKeyId           = Encoding::LittleEndian::HostSwap16(mCASESessionKeyId);
-    serializable.mDeviceProvisioningComplete = (mDeviceProvisioningComplete) ? 1 : 0;
+        serializable.mLocalMessageCounter = Encoding::LittleEndian::HostSwap32(localMessageCounter);
+        serializable.mPeerMessageCounter  = Encoding::LittleEndian::HostSwap32(peerMessageCounter);
+    }
+    else
+    {
+        serializable.mLocalMessageCounter = 0;
+        serializable.mPeerMessageCounter  = 0;
+    }
+
+    serializable.mDeviceOperationalCertProvisioned = (mDeviceOperationalCertProvisioned) ? 1 : 0;
 
     static_assert(std::is_same<std::underlying_type<decltype(mDeviceAddress.GetTransportType())>::type, uint8_t>::value,
                   "The underlying type of Transport::Type is not uint8_t.");
@@ -232,8 +242,7 @@
     // the old counter value (which is 1 less than the updated counter).
     mLocalMessageCounter++;
 
-    mCASESessionKeyId           = Encoding::LittleEndian::HostSwap16(serializable.mCASESessionKeyId);
-    mDeviceProvisioningComplete = (serializable.mDeviceProvisioningComplete != 0);
+    mDeviceOperationalCertProvisioned = (serializable.mDeviceOperationalCertProvisioned != 0);
 
     // The InterfaceNameToId() API requires initialization of mInterface, and lock/unlock of
     // LwIP stack.
@@ -309,6 +318,8 @@
 
 void Device::OnConnectionExpired(SecureSessionHandle session)
 {
+    VerifyOrReturn(session == mSecureSession,
+                   ChipLogDetail(Controller, "Connection expired, but it doesn't match the current session"));
     mState         = ConnectionState::NotConnected;
     mSecureSession = SecureSessionHandle{};
 }
@@ -426,8 +437,10 @@
         ExitNow(err = CHIP_ERROR_INCORRECT_STATE);
     }
 
-    err = pairingSession.FromSerializable(mPairing);
-    SuccessOrExit(err);
+    if (mState == ConnectionState::Connecting)
+    {
+        ExitNow(err = CHIP_NO_ERROR);
+    }
 
     if (resetNeeded == ResetTransport::kYes)
     {
@@ -445,16 +458,20 @@
         SuccessOrExit(err);
     }
 
-    err = mSessionManager->NewPairing(Optional<Transport::PeerAddress>::Value(mDeviceAddress), mDeviceId, &pairingSession,
-                                      SecureSession::SessionRole::kInitiator, mAdminId);
-    SuccessOrExit(err);
+    if (IsOperationalCertProvisioned())
+    {
+        err = WarmupCASESession();
+        SuccessOrExit(err);
+    }
+    else
+    {
+        err = pairingSession.FromSerializable(mPairing);
+        SuccessOrExit(err);
 
-    // TODO - Enable CASE Session setup before message is sent to a fully provisioned device
-    // if (IsProvisioningComplete())
-    // {
-    //     err = EstablishCASESession();
-    //     SuccessOrExit(err);
-    // }
+        err = mSessionManager->NewPairing(Optional<Transport::PeerAddress>::Value(mDeviceAddress), mDeviceId, &pairingSession,
+                                          SecureSession::SessionRole::kInitiator, mAdminId);
+        SuccessOrExit(err);
+    }
 
 exit:
 
@@ -475,35 +492,118 @@
     return true;
 }
 
-CHIP_ERROR Device::EstablishCASESession()
+void Device::OperationalCertProvisioned()
 {
+    VerifyOrReturn(!mDeviceOperationalCertProvisioned,
+                   ChipLogDetail(Controller, "Operational certificates already provisioned for this device"));
+
+    ChipLogDetail(Controller, "Enabling CASE session establishment for the device");
+    mDeviceOperationalCertProvisioned = true;
+
+    Persist();
+
+    if (mState == ConnectionState::SecureConnected)
+    {
+        mSessionManager->ExpirePairing(mSecureSession);
+        mState = ConnectionState::NotConnected;
+    }
+}
+
+CHIP_ERROR Device::WarmupCASESession()
+{
+    VerifyOrReturnError(mDeviceOperationalCertProvisioned, CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrReturnError(mState == ConnectionState::NotConnected, CHIP_NO_ERROR);
+
     Messaging::ExchangeContext * exchange = mExchangeMgr->NewContext(SecureSessionHandle(), &mCASESession);
     VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_INTERNAL);
 
     ReturnErrorOnFailure(mCASESession.MessageDispatch().Init(mSessionManager->GetTransportManager()));
     mCASESession.MessageDispatch().SetPeerAddress(mDeviceAddress);
 
-    ReturnErrorOnFailure(mCASESession.EstablishSession(mDeviceAddress, mCredentials, mDeviceId, 0, exchange, this));
+    uint16_t keyID = 0;
+    ReturnErrorOnFailure(mIDAllocator->Allocate(keyID));
+
+    mLocalMessageCounter = 0;
+    mPeerMessageCounter  = 0;
+
+    ReturnErrorOnFailure(mCASESession.EstablishSession(mDeviceAddress, mCredentials, mDeviceId, keyID, exchange, this));
+
+    mState = ConnectionState::Connecting;
 
     return CHIP_NO_ERROR;
 }
 
-void Device::OnSessionEstablishmentError(CHIP_ERROR error) {}
+void Device::OnSessionEstablishmentError(CHIP_ERROR error)
+{
+    mState = ConnectionState::NotConnected;
+    mIDAllocator->Free(mCASESession.GetLocalKeyId());
+
+    Cancelable ready;
+    mConnectionFailure.DequeueAll(ready);
+    while (ready.mNext != &ready)
+    {
+        Callback::Callback<OnDeviceConnectionFailure> * cb =
+            Callback::Callback<OnDeviceConnectionFailure>::FromCancelable(ready.mNext);
+
+        cb->Cancel();
+        cb->mCall(cb->mContext, GetDeviceId(), error);
+    }
+}
 
 void Device::OnSessionEstablished()
 {
     mCASESession.PeerConnection().SetPeerNodeId(mDeviceId);
 
-    // TODO - Enable keys derived from CASE Session
-    // CHIP_ERROR err = mSessionManager->NewPairing(Optional<Transport::PeerAddress>::Value(mDeviceAddress), mDeviceId,
-    // &mCASESession,
-    //                                              SecureSession::SessionRole::kInitiator, mAdminId, nullptr);
-    // if (err != CHIP_NO_ERROR)
-    // {
-    //     ChipLogError(Controller, "Failed in setting up CASE secure channel: err %s", ErrorStr(err));
-    //     OnSessionEstablishmentError(err);
-    //     return;
-    // }
+    CHIP_ERROR err = mSessionManager->NewPairing(Optional<Transport::PeerAddress>::Value(mDeviceAddress), mDeviceId, &mCASESession,
+                                                 SecureSession::SessionRole::kInitiator, mAdminId, nullptr);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Controller, "Failed in setting up CASE secure channel: err %s", ErrorStr(err));
+        OnSessionEstablishmentError(err);
+        return;
+    }
+
+    Cancelable ready;
+    mConnectionSuccess.DequeueAll(ready);
+    while (ready.mNext != &ready)
+    {
+        Callback::Callback<OnDeviceConnected> * cb = Callback::Callback<OnDeviceConnected>::FromCancelable(ready.mNext);
+
+        cb->Cancel();
+        cb->mCall(cb->mContext, this);
+    }
+}
+
+CHIP_ERROR Device::EstablishConnectivity(Callback::Callback<OnDeviceConnected> * onConnection,
+                                         Callback::Callback<OnDeviceConnectionFailure> * onFailure)
+{
+    bool loadedSecureSession = false;
+    ReturnErrorOnFailure(LoadSecureSessionParametersIfNeeded(loadedSecureSession));
+
+    if (loadedSecureSession)
+    {
+        if (IsOperationalCertProvisioned())
+        {
+            if (onConnection != nullptr)
+            {
+                mConnectionSuccess.Enqueue(onConnection->Cancel());
+            }
+
+            if (onFailure != nullptr)
+            {
+                mConnectionFailure.Enqueue(onFailure->Cancel());
+            }
+        }
+        else
+        {
+            if (onConnection != nullptr)
+            {
+                onConnection->mCall(onConnection->mContext, this);
+            }
+        }
+    }
+
+    return CHIP_NO_ERROR;
 }
 
 void Device::AddResponseHandler(uint8_t seqNum, Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback)
diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h
index 9d683a7..e5f8eda 100644
--- a/src/controller/CHIPDevice.h
+++ b/src/controller/CHIPDevice.h
@@ -38,6 +38,7 @@
 #include <messaging/ExchangeMgr.h>
 #include <protocols/secure_channel/CASESession.h>
 #include <protocols/secure_channel/PASESession.h>
+#include <protocols/secure_channel/SessionIDAllocator.h>
 #include <setup_payload/SetupPayload.h>
 #include <support/Base64.h>
 #include <support/DLLUtil.h>
@@ -80,11 +81,17 @@
     Inet::InetLayer * inetLayer                         = nullptr;
     PersistentStorageDelegate * storageDelegate         = nullptr;
     Credentials::OperationalCredentialSet * credentials = nullptr;
+    SessionIDAllocator * idAllocator                    = nullptr;
 #if CONFIG_NETWORK_LAYER_BLE
     Ble::BleLayer * bleLayer = nullptr;
 #endif
 };
 
+class Device;
+
+typedef void (*OnDeviceConnected)(void * context, Device * device);
+typedef void (*OnDeviceConnectionFailure)(void * context, NodeId deviceId, CHIP_ERROR error);
+
 class DLL_EXPORT Device : public Messaging::ExchangeDelegate, public SessionEstablishmentDelegate
 {
 public:
@@ -175,6 +182,7 @@
         mAdminId         = admin;
         mStorageDelegate = params.storageDelegate;
         mCredentials     = params.credentials;
+        mIDAllocator     = params.idAllocator;
 #if CONFIG_NETWORK_LAYER_BLE
         mBleLayer = params.bleLayer;
 #endif
@@ -311,6 +319,8 @@
 
     bool IsSecureConnected() const { return IsActive() && mState == ConnectionState::SecureConnected; }
 
+    bool IsSessionSetupInProgress() const { return IsActive() && mState == ConnectionState::Connecting; }
+
     void Reset();
 
     NodeId GetDeviceId() const { return mDeviceId; }
@@ -334,12 +344,8 @@
                               Callback::Cancelable * onFailureCallback);
     void CancelIMResponseHandler(app::Command * commandObj);
 
-    void ProvisioningComplete(uint16_t caseKeyId)
-    {
-        mDeviceProvisioningComplete = true;
-        mCASESessionKeyId           = caseKeyId;
-    }
-    bool IsProvisioningComplete() const { return mDeviceProvisioningComplete; }
+    void OperationalCertProvisioned();
+    bool IsOperationalCertProvisioned() const { return mDeviceOperationalCertProvisioned; }
 
     //////////// SessionEstablishmentDelegate Implementation ///////////////
     void OnSessionEstablishmentError(CHIP_ERROR error) override;
@@ -351,6 +357,23 @@
 
     ByteSpan GetCSRNonce() const { return ByteSpan(mCSRNonce, sizeof(mCSRNonce)); }
 
+    /*
+     * This function can be called to establish a secure session with the device.
+     *
+     * If the device doesn't have operational credentials, and is under commissioning process,
+     * PASE keys will be used for secure session.
+     *
+     * If the device has been commissioned and has operational credentials, CASE session
+     * setup will be triggered.
+     *
+     * On establishing the session, the callback function `onConnection` will be called. If the
+     * session setup fails, `onFailure` will be called.
+     *
+     * If the session already exists, `onConnection` will be called immediately.
+     */
+    CHIP_ERROR EstablishConnectivity(Callback::Callback<OnDeviceConnected> * onConnection,
+                                     Callback::Callback<OnDeviceConnectionFailure> * onFailure);
+
 private:
     enum class ConnectionState
     {
@@ -419,22 +442,31 @@
      */
     CHIP_ERROR LoadSecureSessionParametersIfNeeded(bool & didLoad);
 
-    CHIP_ERROR EstablishCASESession();
+    /**
+     *   This function triggers CASE session setup if the device has been provisioned with
+     *   operational credentials, and there is no currently active session.
+     */
+
+    CHIP_ERROR WarmupCASESession();
 
     uint16_t mListenPort;
 
     Transport::AdminId mAdminId = Transport::kUndefinedAdminId;
 
-    bool mDeviceProvisioningComplete = false;
+    bool mDeviceOperationalCertProvisioned = false;
 
     CASESession mCASESession;
-    uint16_t mCASESessionKeyId = 0;
 
     Credentials::OperationalCredentialSet * mCredentials = nullptr;
 
     PersistentStorageDelegate * mStorageDelegate = nullptr;
 
     uint8_t mCSRNonce[kOpCSRNonceLength];
+
+    SessionIDAllocator * mIDAllocator = nullptr;
+
+    Callback::CallbackDeque mConnectionSuccess;
+    Callback::CallbackDeque mConnectionFailure;
 };
 
 /**
@@ -483,11 +515,10 @@
     PASESessionSerializable mOpsCreds;
     uint64_t mDeviceId; /* This field is serialized in LittleEndian byte order */
     uint8_t mDeviceAddr[INET6_ADDRSTRLEN];
-    uint16_t mDevicePort;       /* This field is serialized in LittleEndian byte order */
-    uint16_t mAdminId;          /* This field is serialized in LittleEndian byte order */
-    uint16_t mCASESessionKeyId; /* This field is serialized in LittleEndian byte order */
+    uint16_t mDevicePort; /* This field is serialized in LittleEndian byte order */
+    uint16_t mAdminId;    /* This field is serialized in LittleEndian byte order */
     uint8_t mDeviceTransport;
-    uint8_t mDeviceProvisioningComplete;
+    uint8_t mDeviceOperationalCertProvisioned;
     uint8_t mInterfaceName[kMaxInterfaceName];
     uint32_t mLocalMessageCounter; /* This field is serialized in LittleEndian byte order */
     uint32_t mPeerMessageCounter;  /* This field is serialized in LittleEndian byte order */
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index 64b4a7f..8fb1b76 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -96,8 +96,12 @@
 
 constexpr uint32_t kSessionEstablishmentTimeout = 30 * kMillisecondPerSecond;
 
-constexpr uint32_t kMaxCHIPOpCertLength = 1024;
-constexpr uint32_t kMaxCHIPCSRLength    = 1024;
+// TODO - Reduce memory requirement for generating x509 certificates
+// As per specifications (section 6.3.7. Trusted Root CA Certificates), DER certs
+// should require 600 bytes. Currently, due to ASN writer overheads, a larger buffer
+// is needed, even though the generated certificate fits in 600 bytes limit.
+constexpr uint32_t kMaxCHIPDERCertLength = 1024;
+constexpr uint32_t kMaxCHIPCSRLength     = 1024;
 
 DeviceController::DeviceController()
 {
@@ -211,6 +215,46 @@
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR DeviceController::GenerateOperationalCertificates(const ByteSpan & CSR, NodeId deviceId, MutableByteSpan & cert)
+{
+    // This code requires about 2K RAM to generate the certificates.
+    // The code would run as part of commissioner applications, so RAM requirements should be fine.
+    // Need to analyze if this requirement could be better managed by using static memory pools.
+    chip::Platform::ScopedMemoryBuffer<uint8_t> noc;
+    ReturnErrorCodeIf(!noc.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
+
+    uint32_t nocLen = 0;
+    ChipLogProgress(Controller, "Generating operational certificate for device " ChipLogFormatX64, ChipLogValueX64(deviceId));
+    ReturnErrorOnFailure(mOperationalCredentialsDelegate->GenerateNodeOperationalCertificate(
+        PeerId().SetNodeId(deviceId), CSR, 1, noc.Get(), kMaxCHIPDERCertLength, nocLen));
+
+    ReturnErrorCodeIf(nocLen == 0, CHIP_ERROR_CERT_NOT_FOUND);
+
+    chip::Platform::ScopedMemoryBuffer<uint8_t> ica;
+    ReturnErrorCodeIf(!ica.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
+
+    uint32_t icaLen = 0;
+    ChipLogProgress(Controller, "Getting intermediate CA certificate from the issuer");
+    CHIP_ERROR err = mOperationalCredentialsDelegate->GetIntermediateCACertificate(0, ica.Get(), kMaxCHIPDERCertLength, icaLen);
+    ChipLogProgress(Controller, "GetIntermediateCACertificate returned %" PRId32, err);
+    if (err == CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED)
+    {
+        // This implies that the commissioner application uses root CA to sign the operational
+        // certificates, and an intermediate CA is not needed. It's not an error condition, so
+        // let's just send operational certificate and root CA certificate to the device.
+        icaLen = 0;
+        ChipLogProgress(Controller, "Intermediate CA is not needed");
+    }
+    else if (err != CHIP_NO_ERROR)
+    {
+        return err;
+    }
+
+    ReturnErrorOnFailure(ConvertX509CertsToChipCertArray(ByteSpan(noc.Get(), nocLen), ByteSpan(ica.Get(), icaLen), cert));
+
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR DeviceController::LoadLocalCredentials(Transport::AdminPairingInfo * admin)
 {
     ChipLogProgress(Controller, "Getting operational keys");
@@ -220,38 +264,41 @@
 
     if (!admin->AreCredentialsAvailable())
     {
-        chip::Platform::ScopedMemoryBuffer<uint8_t> buffer1;
-        ReturnErrorCodeIf(!buffer1.Alloc(kMaxCHIPCSRLength), CHIP_ERROR_NO_MEMORY);
-
-        chip::Platform::ScopedMemoryBuffer<uint8_t> buffer2;
-        ReturnErrorCodeIf(!buffer2.Alloc(kMaxCHIPOpCertLength), CHIP_ERROR_NO_MEMORY);
-
-        uint8_t * CSR    = buffer1.Get();
-        size_t csrLength = kMaxCHIPCSRLength;
-        ReturnErrorOnFailure(keypair->NewCertificateSigningRequest(CSR, csrLength));
-
-        uint8_t * cert   = buffer2.Get();
-        uint32_t certLen = 0;
-
-        // TODO - Match the generated cert against CSR and operational keypair
-        //        Make sure it chains back to the trusted root.
-        ChipLogProgress(Controller, "Generating operational certificate for the controller");
-        ReturnErrorOnFailure(mOperationalCredentialsDelegate->GenerateNodeOperationalCertificate(
-            PeerId().SetNodeId(mLocalDeviceId), ByteSpan(CSR, csrLength), 1, cert, kMaxCHIPOpCertLength, certLen));
-
-        uint8_t * chipCert   = buffer1.Get();
+        chip::Platform::ScopedMemoryBuffer<uint8_t> chipCert;
+        uint32_t chipCertAllocatedLen = kMaxCHIPCertLength * 2;
+        ReturnErrorCodeIf(!chipCert.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
         uint32_t chipCertLen = 0;
-        ReturnErrorOnFailure(ConvertX509CertToChipCert(cert, certLen, chipCert, kMaxCHIPOpCertLength, chipCertLen));
 
-        ReturnErrorOnFailure(admin->SetOperationalCert(ByteSpan(chipCert, chipCertLen)));
+        // Get root CA certificate
+        {
+            ChipLogProgress(Controller, "Getting root certificate for the controller from the issuer");
+            chip::Platform::ScopedMemoryBuffer<uint8_t> rootCert;
+            ReturnErrorCodeIf(!rootCert.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
+            uint32_t rootCertLen = 0;
 
-        ChipLogProgress(Controller, "Getting root certificate for the controller from the issuer");
-        ReturnErrorOnFailure(mOperationalCredentialsDelegate->GetRootCACertificate(0, cert, kMaxCHIPOpCertLength, certLen));
+            ReturnErrorOnFailure(
+                mOperationalCredentialsDelegate->GetRootCACertificate(0, rootCert.Get(), kMaxCHIPDERCertLength, rootCertLen));
+            ReturnErrorOnFailure(
+                ConvertX509CertToChipCert(rootCert.Get(), rootCertLen, chipCert.Get(), chipCertAllocatedLen, chipCertLen));
 
-        chipCertLen = 0;
-        ReturnErrorOnFailure(ConvertX509CertToChipCert(cert, certLen, chipCert, kMaxCHIPOpCertLength, chipCertLen));
+            ReturnErrorOnFailure(admin->SetRootCert(ByteSpan(chipCert.Get(), chipCertLen)));
+        }
 
-        ReturnErrorOnFailure(admin->SetRootCert(ByteSpan(chipCert, chipCertLen)));
+        // Generate Operational Certificates (NOC and ICAC)
+        {
+            chip::Platform::ScopedMemoryBuffer<uint8_t> CSR;
+            size_t csrLength = kMaxCHIPCSRLength;
+            ReturnErrorCodeIf(!CSR.Alloc(csrLength), CHIP_ERROR_NO_MEMORY);
+
+            ReturnErrorOnFailure(keypair->NewCertificateSigningRequest(CSR.Get(), csrLength));
+
+            // TODO - Match the generated cert against CSR and operational keypair
+            //        Make sure it chains back to the trusted root.
+            ChipLogProgress(Controller, "Generating operational certificate for the controller");
+            MutableByteSpan chipCertSpan(chipCert.Get(), chipCertAllocatedLen);
+            ReturnErrorOnFailure(GenerateOperationalCertificates(ByteSpan(CSR.Get(), csrLength), mLocalDeviceId, chipCertSpan));
+            ReturnErrorOnFailure(admin->SetOperationalCert(chipCertSpan));
+        }
 
         ReturnErrorOnFailure(mAdmins.Store(admin->GetAdminId()));
     }
@@ -361,39 +408,6 @@
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR DeviceController::GetDevice(NodeId deviceId, const SerializedDevice & deviceInfo, Device ** out_device)
-{
-    Device * device = nullptr;
-
-    VerifyOrReturnError(out_device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
-    uint16_t index = FindDeviceIndex(deviceId);
-
-    if (index < kNumMaxActiveDevices)
-    {
-        device = &mActiveDevices[index];
-    }
-    else
-    {
-        VerifyOrReturnError(mPairedDevices.Contains(deviceId), CHIP_ERROR_NOT_CONNECTED);
-
-        index = GetInactiveDeviceIndex();
-        VerifyOrReturnError(index < kNumMaxActiveDevices, CHIP_ERROR_NO_MEMORY);
-        device = &mActiveDevices[index];
-
-        CHIP_ERROR err = device->Deserialize(deviceInfo);
-        if (err != CHIP_NO_ERROR)
-        {
-            ReleaseDevice(device);
-            ReturnErrorOnFailure(err);
-        }
-
-        device->Init(GetControllerDeviceInitParams(), mListenPort, mAdminId);
-    }
-
-    *out_device = device;
-    return CHIP_NO_ERROR;
-}
-
 CHIP_ERROR DeviceController::GetDevice(NodeId deviceId, Device ** out_device)
 {
     CHIP_ERROR err  = CHIP_NO_ERROR;
@@ -444,14 +458,47 @@
     return err;
 }
 
-CHIP_ERROR DeviceController::UpdateDevice(Device * device, uint64_t fabricId)
+bool DeviceController::DoesDevicePairingExist(const PeerId & deviceId)
 {
-    // TODO - Detect when the device is fully provisioned, instead of relying on UpdateDevice()
-    device->ProvisioningComplete(mNextKeyId++);
-    PersistDevice(device);
-    PersistNextKeyId();
+    if (InitializePairedDeviceList() == CHIP_NO_ERROR)
+    {
+        return mPairedDevices.Contains(deviceId.GetNodeId());
+    }
+
+    return false;
+}
+
+CHIP_ERROR DeviceController::GetConnectedDevice(NodeId deviceId, Callback::Callback<OnDeviceConnected> * onConnection,
+                                                Callback::Callback<OnDeviceConnectionFailure> * onFailure)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+
+    err = GetDevice(deviceId, &device);
+    SuccessOrExit(err);
+
+    if (device->IsSecureConnected())
+    {
+        onConnection->mCall(onConnection->mContext, device);
+        return CHIP_NO_ERROR;
+    }
+
+    err = device->EstablishConnectivity(onConnection, onFailure);
+    SuccessOrExit(err);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        onFailure->mCall(onFailure->mContext, deviceId, err);
+    }
+
+    return err;
+}
+
+CHIP_ERROR DeviceController::UpdateDevice(NodeId deviceId, uint64_t fabricId)
+{
 #if CHIP_DEVICE_CONFIG_ENABLE_MDNS
-    return Mdns::Resolver::Instance().ResolveNodeId(chip::PeerId().SetNodeId(device->GetDeviceId()).SetFabricId(fabricId),
+    return Mdns::Resolver::Instance().ResolveNodeId(chip::PeerId().SetNodeId(deviceId).SetFabricId(fabricId),
                                                     chip::Inet::kIPAddressType_Any);
 #else
     return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
@@ -690,7 +737,8 @@
 {
     if (mStorageDelegate != nullptr && mState == State::Initialized)
     {
-        mStorageDelegate->SyncSetKeyValue(kNextAvailableKeyID, &mNextKeyId, sizeof(mNextKeyId));
+        uint16_t nextKeyID = mIDAllocator.Peek();
+        mStorageDelegate->SyncSetKeyValue(kNextAvailableKeyID, &nextKeyID, sizeof(nextKeyID));
     }
 }
 
@@ -738,6 +786,7 @@
         .inetLayer       = mInetLayer,
         .storageDelegate = mStorageDelegate,
         .credentials     = &mCredentials,
+        .idAllocator     = &mIDAllocator,
     };
 }
 
@@ -746,7 +795,8 @@
     mOpCSRResponseCallback(OnOperationalCertificateSigningRequest, this),
     mOpCertResponseCallback(OnOperationalCertificateAddResponse, this), mRootCertResponseCallback(OnRootCertSuccessResponse, this),
     mOnCSRFailureCallback(OnCSRFailureResponse, this), mOnCertFailureCallback(OnAddOpCertFailureResponse, this),
-    mOnRootCertFailureCallback(OnRootCertFailureResponse, this)
+    mOnRootCertFailureCallback(OnRootCertFailureResponse, this), mOnDeviceConnectedCallback(OnDeviceConnectedFn, this),
+    mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
 {
     mPairingDelegate      = nullptr;
     mDeviceBeingPaired    = kNumMaxActiveDevices;
@@ -757,12 +807,14 @@
 {
     ReturnErrorOnFailure(DeviceController::Init(localDeviceId, params));
 
-    uint16_t size    = sizeof(mNextKeyId);
-    CHIP_ERROR error = mStorageDelegate->SyncGetKeyValue(kNextAvailableKeyID, &mNextKeyId, size);
-    if (error || (size != sizeof(mNextKeyId)))
+    uint16_t nextKeyID = 0;
+    uint16_t size      = sizeof(nextKeyID);
+    CHIP_ERROR error   = mStorageDelegate->SyncGetKeyValue(kNextAvailableKeyID, &nextKeyID, size);
+    if (error || (size != sizeof(nextKeyID)))
     {
-        mNextKeyId = 0;
+        nextKeyID = 0;
     }
+    ReturnErrorOnFailure(mIDAllocator.ReserveUpTo(nextKeyID));
     mPairingDelegate = params.pairingDelegate;
 
     return CHIP_NO_ERROR;
@@ -790,6 +842,8 @@
 
     Messaging::ExchangeContext * exchangeCtxt = nullptr;
 
+    uint16_t keyID = 0;
+
     Transport::AdminPairingInfo * admin = mAdmins.FindAdminWithId(mAdminId);
 
     VerifyOrExit(remoteDeviceId != kAnyNodeId && remoteDeviceId != kUndefinedNodeId, err = CHIP_ERROR_INVALID_ARGUMENT);
@@ -858,7 +912,10 @@
     exchangeCtxt = mExchangeMgr->NewContext(SecureSessionHandle(), &mPairingSession);
     VerifyOrExit(exchangeCtxt != nullptr, err = CHIP_ERROR_INTERNAL);
 
-    err = mPairingSession.Pair(params.GetPeerAddress(), params.GetSetupPINCode(), mNextKeyId++, exchangeCtxt, this);
+    err = mIDAllocator.Allocate(keyID);
+    SuccessOrExit(err);
+
+    err = mPairingSession.Pair(params.GetPeerAddress(), params.GetSetupPINCode(), keyID, exchangeCtxt, this);
     // Immediately persist the updted mNextKeyID value
     // TODO maybe remove FreeRendezvousSession() since mNextKeyID is always persisted immediately
     PersistNextKeyId();
@@ -996,6 +1053,20 @@
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR DeviceCommissioner::OperationalDiscoveryComplete(NodeId remoteDeviceId)
+{
+    ChipLogProgress(Controller, "OperationalDiscoveryComplete for device ID %" PRIu64, remoteDeviceId);
+    VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
+
+    Device * device = nullptr;
+    ReturnErrorOnFailure(GetDevice(remoteDeviceId, &device));
+    device->OperationalCertProvisioned();
+    PersistDevice(device);
+    PersistNextKeyId();
+
+    return GetConnectedDevice(remoteDeviceId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
+}
+
 void DeviceCommissioner::FreeRendezvousSession()
 {
     PersistNextKeyId();
@@ -1058,7 +1129,7 @@
         return;
     }
 
-    ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake\n");
+    ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake");
 
     // TODO: Add code to receive OpCSR from the device, and process the signing request
     // For IP rendezvous, this is sent as part of the state machine.
@@ -1146,43 +1217,15 @@
     VerifyOrReturnError(CSRNonce.size() == nonce.size(), CHIP_ERROR_INVALID_ARGUMENT);
     VerifyOrReturnError(memcmp(CSRNonce.data(), nonce.data(), CSRNonce.size()) == 0, CHIP_ERROR_INVALID_ARGUMENT);
 
-    chip::Platform::ScopedMemoryBuffer<uint8_t> noc;
-    ReturnErrorCodeIf(!noc.Alloc(kMaxCHIPOpCertLength), CHIP_ERROR_NO_MEMORY);
-
-    uint32_t nocLen = 0;
-    ChipLogProgress(Controller, "Generating operational certificate for device " ChipLogFormatX64,
-                    ChipLogValueX64(device->GetDeviceId()));
-    ReturnErrorOnFailure(mOperationalCredentialsDelegate->GenerateNodeOperationalCertificate(
-        PeerId().SetNodeId(device->GetDeviceId()), CSR, 1, noc.Get(), kMaxCHIPOpCertLength, nocLen));
-
-    ReturnErrorCodeIf(nocLen == 0, CHIP_ERROR_CERT_NOT_FOUND);
-
-    chip::Platform::ScopedMemoryBuffer<uint8_t> ica;
-    ReturnErrorCodeIf(!ica.Alloc(kMaxCHIPOpCertLength), CHIP_ERROR_NO_MEMORY);
-
-    uint32_t icaLen = 0;
-    ChipLogProgress(Controller, "Getting intermediate CA certificate from the issuer");
-    CHIP_ERROR err = mOperationalCredentialsDelegate->GetIntermediateCACertificate(0, ica.Get(), kMaxCHIPOpCertLength, icaLen);
-    ChipLogProgress(Controller, "GetIntermediateCACertificate returned %" PRId32, err);
-    if (err == CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED)
-    {
-        // This implies that the commissioner application uses root CA to sign the operational
-        // certificates, and an intermediate CA is not needed. It's not an error condition, so
-        // let's just send operational certificate and root CA certificate to the device.
-        err    = CHIP_NO_ERROR;
-        icaLen = 0;
-        ChipLogProgress(Controller, "Intermediate CA is not needed");
-    }
-    ReturnErrorOnFailure(err);
-
     chip::Platform::ScopedMemoryBuffer<uint8_t> chipOpCert;
-    ReturnErrorCodeIf(!chipOpCert.Alloc(kMaxCHIPOpCertLength * 2), CHIP_ERROR_NO_MEMORY);
-    uint32_t chipOpCertLen = 0;
-    ReturnErrorOnFailure(ConvertX509CertsToChipCertArray(ByteSpan(noc.Get(), nocLen), ByteSpan(ica.Get(), icaLen), chipOpCert.Get(),
-                                                         kMaxCHIPOpCertLength * 2, chipOpCertLen));
+    ReturnErrorCodeIf(!chipOpCert.Alloc(kMaxCHIPCertLength * 2), CHIP_ERROR_NO_MEMORY);
 
-    ChipLogProgress(Controller, "Sending operational certificate to the device. Op Cert Len %" PRIu32, chipOpCertLen);
-    ReturnErrorOnFailure(SendOperationalCertificate(device, ByteSpan(chipOpCert.Get(), chipOpCertLen)));
+    MutableByteSpan chipCertSpan(chipOpCert.Get(), kMaxCHIPCertLength * 2);
+
+    ReturnErrorOnFailure(GenerateOperationalCertificates(CSR, device->GetDeviceId(), chipCertSpan));
+
+    ChipLogProgress(Controller, "Sending operational certificate to the device");
+    ReturnErrorOnFailure(SendOperationalCertificate(device, chipCertSpan));
 
     return CHIP_NO_ERROR;
 }
@@ -1489,6 +1532,7 @@
         }
     }
     DeviceController::OnNodeIdResolved(nodeData);
+    OperationalDiscoveryComplete(nodeData.mPeerId.GetNodeId());
 }
 
 void DeviceCommissioner::OnNodeIdResolutionFailed(const chip::PeerId & peer, CHIP_ERROR error)
@@ -1506,6 +1550,26 @@
 
 #endif
 
+void DeviceCommissioner::OnDeviceConnectedFn(void * context, Device * device)
+{
+    DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
+    VerifyOrReturn(commissioner != nullptr, ChipLogProgress(Controller, "Device connected callback with null context. Ignoring"));
+    VerifyOrReturn(commissioner->mPairingDelegate != nullptr,
+                   ChipLogProgress(Controller, "Device connected callback with null pairing delegate. Ignoring"));
+    commissioner->mPairingDelegate->OnCommissioningComplete(device->GetDeviceId(), CHIP_NO_ERROR);
+}
+
+void DeviceCommissioner::OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error)
+{
+    DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
+    ChipLogProgress(Controller, "Device connection failed. Error %s", ErrorStr(error));
+    VerifyOrReturn(commissioner != nullptr,
+                   ChipLogProgress(Controller, "Device connection failure callback with null context. Ignoring"));
+    VerifyOrReturn(commissioner->mPairingDelegate != nullptr,
+                   ChipLogProgress(Controller, "Device connection failure callback with null pairing delegate. Ignoring"));
+    commissioner->mPairingDelegate->OnCommissioningComplete(deviceId, error);
+}
+
 CommissioningStage DeviceCommissioner::GetNextCommissioningStage()
 {
     switch (mCommissioningStage)
diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h
index c57caa7..30273a5 100644
--- a/src/controller/CHIPDeviceController.h
+++ b/src/controller/CHIPDeviceController.h
@@ -143,6 +143,11 @@
      * @param error Error cause, if any
      */
     virtual void OnPairingDeleted(CHIP_ERROR error) {}
+
+    /**
+     *   Called when the commissioning process is complete (with success or error)
+     */
+    virtual void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) {}
 };
 
 struct CommissionerInitParams : public ControllerInitParams
@@ -204,21 +209,6 @@
 
     /**
      * @brief
-     *   This function deserializes the provided deviceInfo object, and initializes and outputs the
-     *   corresponding Device object. The lifetime of the output object is tied to that of the DeviceController
-     *   object. The caller must not use the Device object If they free the DeviceController object, or
-     *   after they call ReleaseDevice() on the returned device object.
-     *
-     * @param[in] deviceId   Node ID for the CHIP device
-     * @param[in] deviceInfo Serialized device info for the device
-     * @param[out] device    The output device object
-     *
-     * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
-     */
-    CHIP_ERROR GetDevice(NodeId deviceId, const SerializedDevice & deviceInfo, Device ** device);
-
-    /**
-     * @brief
      *   This function is similar to the other GetDevice object, except it reads the serialized object from
      *   the persistent storage.
      *
@@ -230,16 +220,30 @@
     CHIP_ERROR GetDevice(NodeId deviceId, Device ** device);
 
     /**
+     *   This function returns true if the device corresponding to `deviceId` has previously been commissioned
+     *   on the fabric.
+     */
+    bool DoesDevicePairingExist(const PeerId & deviceId);
+
+    /**
+     *   This function finds the device corresponding to deviceId, and establishes a secure connection with it.
+     *   Once the connection is successfully establishes (or if it's already connected), it calls `onConnectedDevice`
+     *   callback. If it fails to establish the connection, it calls `onError` callback.
+     */
+    CHIP_ERROR GetConnectedDevice(NodeId deviceId, Callback::Callback<OnDeviceConnected> * onConnection,
+                                  Callback::Callback<OnDeviceConnectionFailure> * onFailure);
+
+    /**
      * @brief
      *   This function update the device informations asynchronously using mdns.
      *   If new device informations has been found, it will be persisted.
      *
-     * @param[in] device    The input device object to update
+     * @param[in] deviceId  Node ID for the CHIP devicex
      * @param[in] fabricId  The fabricId used for mdns resolution
      *
      * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
      */
-    CHIP_ERROR UpdateDevice(Device * device, uint64_t fabricId);
+    CHIP_ERROR UpdateDevice(NodeId deviceId, uint64_t fabricId);
 
     void PersistDevice(Device * device);
 
@@ -334,7 +338,7 @@
     Credentials::OperationalCredentialSet mCredentials;
     Credentials::CertificateKeyId mRootKeyId;
 
-    uint16_t mNextKeyId = 0;
+    SessionIDAllocator mIDAllocator;
 
 #if CHIP_DEVICE_CONFIG_ENABLE_MDNS
     //////////// ResolverDelegate Implementation ///////////////
@@ -343,6 +347,13 @@
     Mdns::DiscoveredNodeData * GetDiscoveredNodes() override { return mCommissionableNodes; }
 #endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS
 
+    // This function uses `OperationalCredentialsDelegate` to generate the operational certificates
+    // for the given device. The output is a TLV encoded array of compressed CHIP certificates. The
+    // array can contain up to two certificates (node operational certificate, and ICA certificate).
+    // If the certificate issuer doesn't require an ICA (i.e. NOC is signed by the root CA), the array
+    // will have only one certificate (node operational certificate).
+    CHIP_ERROR GenerateOperationalCertificates(const ByteSpan & CSR, NodeId deviceId, MutableByteSpan & cert);
+
 private:
     //////////// ExchangeDelegate Implementation ///////////////
     void OnMessageReceived(Messaging::ExchangeContext * ec, const PacketHeader & packetHeader, const PayloadHeader & payloadHeader,
@@ -445,6 +456,16 @@
      */
     CHIP_ERROR UnpairDevice(NodeId remoteDeviceId);
 
+    /**
+     *   This function call indicates that the device has been provisioned with operational
+     *   credentials, and is reachable on operational network. At this point, the device is
+     *   available for CASE session establishment.
+     *
+     *   The function updates the state of device proxy object such that all subsequent messages
+     *   will use secure session established via CASE handshake.
+     */
+    CHIP_ERROR OperationalDiscoveryComplete(NodeId remoteDeviceId);
+
     //////////// SessionEstablishmentDelegate Implementation ///////////////
     void OnSessionEstablishmentError(CHIP_ERROR error) override;
     void OnSessionEstablished() override;
@@ -584,6 +605,9 @@
     /* Callback called when adding root cert to device results in failure */
     static void OnRootCertFailureResponse(void * context, uint8_t status);
 
+    static void OnDeviceConnectedFn(void * context, Device * device);
+    static void OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error);
+
     /**
      * @brief
      *   This function processes the CSR sent by the device.
@@ -612,6 +636,9 @@
     Callback::Callback<DefaultFailureCallback> mOnCertFailureCallback;
     Callback::Callback<DefaultFailureCallback> mOnRootCertFailureCallback;
 
+    Callback::Callback<OnDeviceConnected> mOnDeviceConnectedCallback;
+    Callback::Callback<OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
+
     PASESession mPairingSession;
 };
 
diff --git a/src/controller/ExampleOperationalCredentialsIssuer.cpp b/src/controller/ExampleOperationalCredentialsIssuer.cpp
index 84bf3b6..c007e1a 100644
--- a/src/controller/ExampleOperationalCredentialsIssuer.cpp
+++ b/src/controller/ExampleOperationalCredentialsIssuer.cpp
@@ -18,6 +18,7 @@
 
 #include <controller/ExampleOperationalCredentialsIssuer.h>
 #include <credentials/CHIPCert.h>
+#include <support/CHIPMem.h>
 
 namespace chip {
 namespace Controller {
@@ -29,6 +30,16 @@
 
 CHIP_ERROR ExampleOperationalCredentialsIssuer::Initialize(PersistentStorageDelegate & storage)
 {
+    using namespace ASN1;
+    ASN1UniversalTime effectiveTime;
+
+    // Initializing the default start validity to start of 2021. The default validity duration is 10 years.
+    CHIP_ZERO_AT(effectiveTime);
+    effectiveTime.Year  = 2021;
+    effectiveTime.Month = 1;
+    effectiveTime.Day   = 1;
+    ReturnErrorOnFailure(ASN1ToChipEpochTime(effectiveTime, mNow));
+
     Crypto::P256SerializedKeypair serializedKey;
     uint16_t keySize = static_cast<uint16_t>(sizeof(serializedKey));
 
diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h
index 0dfaa4e..abf7a17 100755
--- a/src/credentials/CHIPCert.h
+++ b/src/credentials/CHIPCert.h
@@ -44,6 +44,9 @@
 static constexpr uint32_t kChip64bitAttrUTF8Length             = 16;
 static constexpr uint16_t kX509NoWellDefinedExpirationDateYear = 9999;
 
+// As per specifications (section 6.3.7. Trusted Root CA Certificates)
+static constexpr uint32_t kMaxCHIPCertLength = 400;
+
 /** Data Element Tags for the CHIP Certificate
  */
 enum
@@ -642,16 +645,13 @@
  *
  *        The API enforces that the NOC is issued by ICA (if ICA is provided).
  *
- * @param x509NOC              Node operational credentials certificate in X.509 DER encoding.
- * @param x509ICAC             Intermediate CA certificate in X.509 DER encoding.
- * @param chipCertArrayBuf     Buffer to store converted certificates in CHIP format.
- * @param chipCertArrayBufSize The size of the buffer to store converted certificates.
- * @param chipCertBufLen       The length of the converted certificates.
+ * @param x509NOC           Node operational credentials certificate in X.509 DER encoding.
+ * @param x509ICAC          Intermediate CA certificate in X.509 DER encoding.
+ * @param chipCertArray     Buffer to store converted certificates in CHIP format.
  *
  * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
  **/
-CHIP_ERROR ConvertX509CertsToChipCertArray(const ByteSpan & x509NOC, const ByteSpan & x509ICAC, uint8_t * chipCertArrayBuf,
-                                           uint32_t chipCertArrayBufSize, uint32_t & chipCertBufLen);
+CHIP_ERROR ConvertX509CertsToChipCertArray(const ByteSpan & x509NOC, const ByteSpan & x509ICAC, MutableByteSpan & chipCertArray);
 
 /**
  * @brief Convert CHIP certificate to the standard X.509 DER encoded certificate.
diff --git a/src/credentials/CHIPCertFromX509.cpp b/src/credentials/CHIPCertFromX509.cpp
index 6cd0e6e..f2f9fa1 100644
--- a/src/credentials/CHIPCertFromX509.cpp
+++ b/src/credentials/CHIPCertFromX509.cpp
@@ -728,14 +728,17 @@
     return err;
 }
 
-CHIP_ERROR ConvertX509CertsToChipCertArray(const ByteSpan & x509NOC, const ByteSpan & x509ICAC, uint8_t * chipCertArrayBuf,
-                                           uint32_t chipCertArrayBufSize, uint32_t & chipCertBufLen)
+CHIP_ERROR ConvertX509CertsToChipCertArray(const ByteSpan & x509NOC, const ByteSpan & x509ICAC, MutableByteSpan & chipCertArray)
 {
     // NOC is mandatory
     VerifyOrReturnError(x509NOC.size() > 0, CHIP_ERROR_INVALID_ARGUMENT);
 
     TLVWriter writer;
-    writer.Init(chipCertArrayBuf, chipCertArrayBufSize);
+
+    // We can still generate the certificate if the output chip cert buffer is bigger than UINT32_MAX,
+    // since generated cert needs less space than UINT32_MAX.
+    uint32_t chipCertBufLen = (chipCertArray.size() > UINT32_MAX) ? UINT32_MAX : static_cast<uint32_t>(chipCertArray.size());
+    writer.Init(chipCertArray.data(), chipCertBufLen);
 
     TLVType outerContainer;
     ReturnErrorOnFailure(writer.StartContainer(AnonymousTag, kTLVType_Array, outerContainer));
@@ -760,7 +763,8 @@
     ReturnErrorOnFailure(writer.EndContainer(outerContainer));
     ReturnErrorOnFailure(writer.Finalize());
 
-    chipCertBufLen = writer.GetLengthWritten();
+    ReturnErrorCodeIf(writer.GetLengthWritten() > chipCertBufLen, CHIP_ERROR_INTERNAL);
+    chipCertArray.reduce_size(writer.GetLengthWritten());
 
     return CHIP_NO_ERROR;
 }
diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp
index a404afa..544936f 100644
--- a/src/credentials/tests/TestChipCert.cpp
+++ b/src/credentials/tests/TestChipCert.cpp
@@ -1009,20 +1009,22 @@
                                               sizeof(noc_cert), noc_len) == CHIP_NO_ERROR);
 
     uint8_t outCertBuf[kTestCertBufSize * 2];
-    uint32_t outCertLen;
+    MutableByteSpan outCert(outCertBuf, sizeof(outCertBuf));
     NL_TEST_ASSERT(inSuite,
-                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(ica_cert, ica_len), outCertBuf,
-                                                   sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR);
+                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(ica_cert, ica_len), outCert) ==
+                       CHIP_NO_ERROR);
+    NL_TEST_ASSERT(inSuite, outCert.size() <= sizeof(outCertBuf));
 
     ChipCertificateSet certSet;
     NL_TEST_ASSERT(inSuite, certSet.Init(3, kTestCertBufSize * 3) == CHIP_NO_ERROR);
 
     NL_TEST_ASSERT(inSuite,
-                   certSet.LoadCerts(outCertBuf, outCertLen, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)) ==
-                       CHIP_NO_ERROR);
+                   certSet.LoadCerts(outCert.data(), static_cast<uint32_t>(outCert.size()),
+                                     BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)) == CHIP_NO_ERROR);
 
     static uint8_t rootCertBuf[kTestCertBufSize];
 
+    uint32_t outCertLen;
     NL_TEST_ASSERT(inSuite,
                    ConvertX509CertToChipCert(root_cert, root_len, rootCertBuf, sizeof(rootCertBuf), outCertLen) == CHIP_NO_ERROR);
     NL_TEST_ASSERT(
@@ -1071,16 +1073,17 @@
 
     uint8_t outCertBuf[kTestCertBufSize * 2];
     uint32_t outCertLen;
+    MutableByteSpan outCert(outCertBuf, sizeof(outCertBuf));
     NL_TEST_ASSERT(inSuite,
-                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(nullptr, 0), outCertBuf,
-                                                   sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR);
+                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(nullptr, 0), outCert) == CHIP_NO_ERROR);
+    NL_TEST_ASSERT(inSuite, outCert.size() <= sizeof(outCertBuf));
 
     ChipCertificateSet certSet;
     NL_TEST_ASSERT(inSuite, certSet.Init(3, kTestCertBufSize * 3) == CHIP_NO_ERROR);
 
     NL_TEST_ASSERT(inSuite,
-                   certSet.LoadCerts(outCertBuf, outCertLen, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)) ==
-                       CHIP_NO_ERROR);
+                   certSet.LoadCerts(outCert.data(), static_cast<uint32_t>(outCert.size()),
+                                     BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)) == CHIP_NO_ERROR);
 
     static uint8_t rootCertBuf[kTestCertBufSize];
 
@@ -1142,16 +1145,16 @@
                                               sizeof(noc_cert), noc_len) == CHIP_NO_ERROR);
 
     uint8_t outCertBuf[kTestCertBufSize * 2];
-    uint32_t outCertLen;
+    MutableByteSpan outCert(outCertBuf, sizeof(outCertBuf));
     // Test that NOC is mandatory
     NL_TEST_ASSERT(inSuite,
-                   ConvertX509CertsToChipCertArray(ByteSpan(nullptr, 0), ByteSpan(ica_cert, ica_len), outCertBuf,
-                                                   sizeof(outCertBuf), outCertLen) == CHIP_ERROR_INVALID_ARGUMENT);
+                   ConvertX509CertsToChipCertArray(ByteSpan(nullptr, 0), ByteSpan(ica_cert, ica_len), outCert) ==
+                       CHIP_ERROR_INVALID_ARGUMENT);
 
     // Test that NOC issuer must match ICA
     NL_TEST_ASSERT(inSuite,
-                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(root_cert, root_len), outCertBuf,
-                                                   sizeof(outCertBuf), outCertLen) == CHIP_ERROR_INVALID_ARGUMENT);
+                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(root_cert, root_len), outCert) ==
+                       CHIP_ERROR_INVALID_ARGUMENT);
 
     X509CertRequestParams ica_params_wrong_fabric = { 1234, 0xabcdabcd, 631161876, 729942000, true, 0x9999, false, 0 };
 
@@ -1160,8 +1163,8 @@
                                   ica_len) == CHIP_NO_ERROR);
     // Test that NOC fabric must match ICA fabric
     NL_TEST_ASSERT(inSuite,
-                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(ica_cert, ica_len), outCertBuf,
-                                                   sizeof(outCertBuf), outCertLen) == CHIP_ERROR_INVALID_ARGUMENT);
+                   ConvertX509CertsToChipCertArray(ByteSpan(noc_cert, noc_len), ByteSpan(ica_cert, ica_len), outCert) ==
+                       CHIP_ERROR_INVALID_ARGUMENT);
 }
 
 /**
diff --git a/src/darwin/Framework/CHIP/CHIPDeviceController.mm b/src/darwin/Framework/CHIP/CHIPDeviceController.mm
index 47d96c7..0d92975 100644
--- a/src/darwin/Framework/CHIP/CHIPDeviceController.mm
+++ b/src/darwin/Framework/CHIP/CHIPDeviceController.mm
@@ -383,17 +383,10 @@
         return;
     }
     dispatch_sync(_chipWorkQueue, ^{
-        chip::Controller::Device * device = nullptr;
-
         if ([self isRunning]) {
-            errorCode = self.cppCommissioner->GetDevice(deviceID, &device);
+            errorCode = self.cppCommissioner->UpdateDevice(deviceID, fabricId);
+            CHIP_LOG_ERROR("Update device address returned: %d", errorCode);
         }
-
-        if (errorCode != CHIP_NO_ERROR) {
-            return;
-        }
-
-        errorCode = self.cppCommissioner->UpdateDevice(device, fabricId);
     });
 }
 
diff --git a/src/protocols/secure_channel/BUILD.gn b/src/protocols/secure_channel/BUILD.gn
index e499545..525406a 100644
--- a/src/protocols/secure_channel/BUILD.gn
+++ b/src/protocols/secure_channel/BUILD.gn
@@ -14,6 +14,8 @@
     "SessionEstablishmentDelegate.h",
     "SessionEstablishmentExchangeDispatch.cpp",
     "SessionEstablishmentExchangeDispatch.h",
+    "SessionIDAllocator.cpp",
+    "SessionIDAllocator.h",
     "StatusReport.cpp",
     "StatusReport.h",
   ]
diff --git a/src/protocols/secure_channel/CASEServer.cpp b/src/protocols/secure_channel/CASEServer.cpp
index 25adca9..74c6d09 100644
--- a/src/protocols/secure_channel/CASEServer.cpp
+++ b/src/protocols/secure_channel/CASEServer.cpp
@@ -30,7 +30,8 @@
 namespace chip {
 
 CHIP_ERROR CASEServer::ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, TransportMgrBase * transportMgr,
-                                                     SecureSessionMgr * sessionMgr, Transport::AdminPairingTable * admins)
+                                                     SecureSessionMgr * sessionMgr, Transport::AdminPairingTable * admins,
+                                                     SessionIDAllocator * idAllocator)
 {
     VerifyOrReturnError(transportMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
@@ -40,6 +41,7 @@
     mSessionMgr      = sessionMgr;
     mAdmins          = admins;
     mExchangeManager = exchangeManager;
+    mIDAllocator     = idAllocator;
 
     ReturnErrorOnFailure(mPairingSession.MessageDispatch().Init(transportMgr));
 
@@ -53,6 +55,8 @@
 {
     ReturnErrorCodeIf(ec == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
 
+    Cleanup();
+
     // TODO - Use PK of the root CA for the initiator to figure out the admin.
     mAdminId = ec->GetSecureSession().GetAdminId();
 
@@ -60,15 +64,21 @@
     //    ReturnErrorCodeIf(mAdminId == Transport::kUndefinedAdminId, CHIP_ERROR_INVALID_ARGUMENT);
     mAdminId = 0;
 
-    mAdmins->LoadFromStorage(mAdminId);
-
     Transport::AdminPairingInfo * admin = mAdmins->FindAdminWithId(mAdminId);
+
+    if (admin == nullptr)
+    {
+        ReturnErrorOnFailure(mAdmins->LoadFromStorage(mAdminId));
+        admin = mAdmins->FindAdminWithId(mAdminId);
+    }
     ReturnErrorCodeIf(admin == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
 
     ReturnErrorOnFailure(admin->GetCredentials(mCredentials, mCertificates, mRootKeyId));
 
+    ReturnErrorOnFailure(mIDAllocator->Allocate(mSessionKeyId));
+
     // Setup CASE state machine using the credentials for the current admin.
-    ReturnErrorOnFailure(mPairingSession.ListenForSessionEstablishment(&mCredentials, mNextKeyId++, this));
+    ReturnErrorOnFailure(mPairingSession.ListenForSessionEstablishment(&mCredentials, mSessionKeyId, this));
 
     // Hand over the exchange context to the CASE session.
     ec->SetDelegate(&mPairingSession);
@@ -85,39 +95,39 @@
     mPairingSession.OnMessageReceived(ec, packetHeader, payloadHeader, std::move(payload));
 
     // TODO - Enable multiple concurrent CASE session establishment
-    // This will prevent CASEServer to process another CASE session establishment request until the current
-    // one completes (successfully or failed)
-    mExchangeManager->UnregisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::CASE_SigmaR1);
 }
 
 void CASEServer::Cleanup()
 {
     // Let's re-register for CASE SigmaR1 message, so that the next CASE session setup request can be processed.
-    mExchangeManager->RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::CASE_SigmaR1, this);
     mAdminId = Transport::kUndefinedAdminId;
     mCredentials.Release();
+    mCertificates.Release();
+    mPairingSession.Clear();
 }
 
 void CASEServer::OnSessionEstablishmentError(CHIP_ERROR err)
 {
     ChipLogProgress(Inet, "CASE Session establishment failed: %s", ErrorStr(err));
+    mIDAllocator->Free(mSessionKeyId);
     Cleanup();
 }
 
 void CASEServer::OnSessionEstablished()
 {
     ChipLogProgress(Inet, "CASE Session established. Setting up the secure channel.");
-    // TODO - enable use of secure session established via CASE
-    // CHIP_ERROR err =
-    //     mSessionMgr->NewPairing(Optional<Transport::PeerAddress>::Value(mPairingSession.PeerConnection().GetPeerAddress()),
-    //                             mPairingSession.PeerConnection().GetPeerNodeId(), &mPairingSession,
-    //                             SecureSession::SessionRole::kResponder, mAdminId, nullptr);
-    // if (err != CHIP_NO_ERROR)
-    // {
-    //     ChipLogError(Inet, "Failed in setting up secure channel: err %s", ErrorStr(err));
-    //     OnSessionEstablishmentError(err);
-    //     return;
-    // }
+    mSessionMgr->ExpireAllPairings(mPairingSession.PeerConnection().GetPeerNodeId(), mAdminId);
+
+    CHIP_ERROR err =
+        mSessionMgr->NewPairing(Optional<Transport::PeerAddress>::Value(mPairingSession.PeerConnection().GetPeerAddress()),
+                                mPairingSession.PeerConnection().GetPeerNodeId(), &mPairingSession,
+                                SecureSession::SessionRole::kResponder, mAdminId, nullptr);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Inet, "Failed in setting up secure channel: err %s", ErrorStr(err));
+        OnSessionEstablishmentError(err);
+        return;
+    }
 
     ChipLogProgress(Inet, "CASE secure channel is available now.");
     Cleanup();
diff --git a/src/protocols/secure_channel/CASEServer.h b/src/protocols/secure_channel/CASEServer.h
index 5a976b7..54c1f5d 100644
--- a/src/protocols/secure_channel/CASEServer.h
+++ b/src/protocols/secure_channel/CASEServer.h
@@ -20,6 +20,7 @@
 #include <messaging/ExchangeDelegate.h>
 #include <messaging/ExchangeMgr.h>
 #include <protocols/secure_channel/CASESession.h>
+#include <protocols/secure_channel/SessionIDAllocator.h>
 
 namespace chip {
 
@@ -38,7 +39,8 @@
     }
 
     CHIP_ERROR ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, TransportMgrBase * transportMgr,
-                                             SecureSessionMgr * sessionMgr, Transport::AdminPairingTable * admins);
+                                             SecureSessionMgr * sessionMgr, Transport::AdminPairingTable * admins,
+                                             SessionIDAllocator * idAllocator);
 
     //////////// SessionEstablishmentDelegate Implementation ///////////////
     void OnSessionEstablishmentError(CHIP_ERROR error) override;
@@ -58,7 +60,7 @@
     Messaging::ExchangeManager * mExchangeManager = nullptr;
 
     CASESession mPairingSession;
-    uint16_t mNextKeyId            = 0;
+    uint16_t mSessionKeyId         = 0;
     SecureSessionMgr * mSessionMgr = nullptr;
 
     Transport::AdminId mAdminId = Transport::kUndefinedAdminId;
@@ -70,6 +72,8 @@
 
     CHIP_ERROR InitCASEHandshake(Messaging::ExchangeContext * ec);
 
+    SessionIDAllocator * mIDAllocator = nullptr;
+
     void Cleanup();
 };
 
diff --git a/src/protocols/secure_channel/CASESession.cpp b/src/protocols/secure_channel/CASESession.cpp
index 99c38f1..be3bf16 100644
--- a/src/protocols/secure_channel/CASESession.cpp
+++ b/src/protocols/secure_channel/CASESession.cpp
@@ -67,10 +67,10 @@
 using HKDF_sha_crypto = HKDF_sha;
 #endif
 
-// Wait at most 30 seconds for the response from the peer.
+// Wait at most 10 seconds for the response from the peer.
 // This timeout value assumes the underlying transport is reliable.
 // The session establishment fails if the response is not received within timeout window.
-static constexpr ExchangeContext::Timeout kSigma_Response_Timeout = 30000;
+static constexpr ExchangeContext::Timeout kSigma_Response_Timeout = 10000;
 
 CASESession::CASESession()
 {
@@ -969,6 +969,11 @@
 
         if (mOpCredSet->IsTrustedRootIn(trustedRoot[i]))
         {
+            if (mTrustedRootId.mId != nullptr)
+            {
+                chip::Platform::MemoryFree(const_cast<uint8_t *>(mTrustedRootId.mId));
+                mTrustedRootId.mId = nullptr;
+            }
             mTrustedRootId.mId = reinterpret_cast<const uint8_t *>(chip::Platform::MemoryAlloc(kTrustedRootIdSize));
             VerifyOrReturnError(mTrustedRootId.mId != nullptr, CHIP_ERROR_NO_MEMORY);
 
@@ -1021,30 +1026,34 @@
 CHIP_ERROR CASESession::Validate_and_RetrieveResponderID(const uint8_t ** msgIterator, P256PublicKey & responderID,
                                                          const uint8_t ** responderOpCert, uint16_t & responderOpCertLen)
 {
-    ChipCertificateData chipCertData;
     ChipCertificateData * resultCert = nullptr;
 
+    ChipCertificateSet certSet;
+    // Certificate set can contain up to 3 certs (NOC, ICA cert, and Root CA cert)
+    ReturnErrorOnFailure(certSet.Init(3, kMaxCHIPCertLength * 3));
+
     responderOpCertLen = chip::Encoding::LittleEndian::Read16(*msgIterator);
     *responderOpCert   = *msgIterator;
     *msgIterator += responderOpCertLen;
 
     Encoding::LittleEndian::BufferWriter bbuf(responderID, responderID.Length());
-    ReturnErrorOnFailure(DecodeChipCert(*responderOpCert, responderOpCertLen, chipCertData));
+    ReturnErrorOnFailure(
+        certSet.LoadCerts(*responderOpCert, responderOpCertLen, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)));
 
-    bbuf.Put(chipCertData.mPublicKey, chipCertData.mPublicKeyLen);
+    bbuf.Put(certSet.GetCertSet()[0].mPublicKey, certSet.GetCertSet()[0].mPublicKeyLen);
 
     VerifyOrReturnError(bbuf.Fit(), CHIP_ERROR_NO_MEMORY);
 
     // Validate responder identity located in msg_r2_encrypted
     ReturnErrorOnFailure(
         mOpCredSet->FindCertSet(mTrustedRootId)
-            ->LoadCert(*responderOpCert, responderOpCertLen, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)));
+            ->LoadCerts(*responderOpCert, responderOpCertLen, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)));
 
     ReturnErrorOnFailure(SetEffectiveTime());
     // Locate the subject DN and key id that will be used as input the FindValidCert() method.
     {
-        const ChipDN & subjectDN              = chipCertData.mSubjectDN;
-        const CertificateKeyId & subjectKeyId = chipCertData.mSubjectKeyId;
+        const ChipDN & subjectDN              = certSet.GetCertSet()[0].mSubjectDN;
+        const CertificateKeyId & subjectKeyId = certSet.GetCertSet()[0].mSubjectKeyId;
 
         ReturnErrorOnFailure(mOpCredSet->FindValidCert(mTrustedRootId, subjectDN, subjectKeyId, mValidContext, resultCert));
     }
diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h
index 41b38e6..67e9bf7 100644
--- a/src/protocols/secure_channel/CASESession.h
+++ b/src/protocols/secure_channel/CASESession.h
@@ -188,6 +188,10 @@
         return &mMessageDispatch;
     }
 
+    /** @brief This function zeroes out and resets the memory used by the object.
+     **/
+    void Clear();
+
 private:
     enum SigmaErrorType : uint8_t
     {
@@ -235,8 +239,6 @@
     // TODO: Remove this and replace with system method to retrieve current time
     CHIP_ERROR SetEffectiveTime(void);
 
-    void Clear();
-
     CHIP_ERROR ValidateReceivedMessage(Messaging::ExchangeContext * ec, const PacketHeader & packetHeader,
                                        const PayloadHeader & payloadHeader, System::PacketBufferHandle & msg);
 
diff --git a/src/protocols/secure_channel/SessionIDAllocator.cpp b/src/protocols/secure_channel/SessionIDAllocator.cpp
new file mode 100644
index 0000000..eadb736
--- /dev/null
+++ b/src/protocols/secure_channel/SessionIDAllocator.cpp
@@ -0,0 +1,79 @@
+/*
+ *
+ *    Copyright (c) 2021 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 <protocols/secure_channel/SessionIDAllocator.h>
+
+#include <support/CodeUtils.h>
+
+namespace chip {
+
+CHIP_ERROR SessionIDAllocator::Allocate(uint16_t & id)
+{
+    VerifyOrReturnError(mNextAvailable < kMaxSessionID, CHIP_ERROR_NO_MEMORY);
+    id = mNextAvailable;
+
+    // TODO - Update SessionID allocator to use freed session IDs
+    mNextAvailable++;
+
+    return CHIP_NO_ERROR;
+}
+
+void SessionIDAllocator::Free(uint16_t id)
+{
+    if (mNextAvailable > 0 && (mNextAvailable - 1) == id)
+    {
+        mNextAvailable--;
+    }
+}
+
+CHIP_ERROR SessionIDAllocator::Reserve(uint16_t id)
+{
+    VerifyOrReturnError(id < kMaxSessionID, CHIP_ERROR_NO_MEMORY);
+    if (id >= mNextAvailable)
+    {
+        mNextAvailable = id;
+        mNextAvailable++;
+    }
+
+    // TODO - Check if ID is already allocated in SessionIDAllocator::Reserve()
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR SessionIDAllocator::ReserveUpTo(uint16_t id)
+{
+    VerifyOrReturnError(id < kMaxSessionID, CHIP_ERROR_NO_MEMORY);
+    if (id >= mNextAvailable)
+    {
+        mNextAvailable = id;
+        mNextAvailable++;
+    }
+
+    // TODO - Update ReserveUpTo to mark all IDs in use
+    // Current SessionIDAllocator only tracks the smallest unused session ID.
+    // If/when we change it to track all in use IDs, we should also update ReserveUpTo
+    // to reserve all individual session IDs, instead of just setting the mNextAvailable.
+
+    return CHIP_NO_ERROR;
+}
+
+uint16_t SessionIDAllocator::Peek()
+{
+    return mNextAvailable;
+}
+
+} // namespace chip
diff --git a/src/protocols/secure_channel/SessionIDAllocator.h b/src/protocols/secure_channel/SessionIDAllocator.h
new file mode 100644
index 0000000..577d6d3
--- /dev/null
+++ b/src/protocols/secure_channel/SessionIDAllocator.h
@@ -0,0 +1,42 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <core/CHIPError.h>
+
+namespace chip {
+
+class SessionIDAllocator
+{
+public:
+    SessionIDAllocator() {}
+    ~SessionIDAllocator() {}
+
+    CHIP_ERROR Allocate(uint16_t & id);
+    void Free(uint16_t id);
+    CHIP_ERROR Reserve(uint16_t id);
+    CHIP_ERROR ReserveUpTo(uint16_t id);
+    uint16_t Peek();
+
+private:
+    // Session ID is a 15 bit value (16th bit indicates unicast/group key)
+    static constexpr uint16_t kMaxSessionID = (1 << 15) - 1;
+    uint16_t mNextAvailable                 = 0;
+};
+
+} // namespace chip
diff --git a/src/protocols/secure_channel/tests/BUILD.gn b/src/protocols/secure_channel/tests/BUILD.gn
index 6bbcfb3..a48aa9a 100644
--- a/src/protocols/secure_channel/tests/BUILD.gn
+++ b/src/protocols/secure_channel/tests/BUILD.gn
@@ -12,6 +12,7 @@
     "TestCASESession.cpp",
     "TestMessageCounterManager.cpp",
     "TestPASESession.cpp",
+    "TestSessionIDAllocator.cpp",
     "TestStatusReport.cpp",
   ]
 
diff --git a/src/protocols/secure_channel/tests/TestSessionIDAllocator.cpp b/src/protocols/secure_channel/tests/TestSessionIDAllocator.cpp
new file mode 100644
index 0000000..be12be9
--- /dev/null
+++ b/src/protocols/secure_channel/tests/TestSessionIDAllocator.cpp
@@ -0,0 +1,157 @@
+/*
+ *
+ *    Copyright (c) 2021 Project CHIP Authors
+ *    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 <protocols/secure_channel/SessionIDAllocator.h>
+#include <support/CHIPMem.h>
+#include <support/UnitTestRegistration.h>
+
+#include <nlunit-test.h>
+
+using namespace chip;
+
+void TestSessionIDAllocator_Allocate(nlTestSuite * inSuite, void * inContext)
+{
+    SessionIDAllocator allocator;
+
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 0);
+
+    uint16_t id;
+
+    for (uint16_t i = 0; i < 16; i++)
+    {
+        CHIP_ERROR err = allocator.Allocate(id);
+        NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+        NL_TEST_ASSERT(inSuite, id == i);
+        NL_TEST_ASSERT(inSuite, allocator.Peek() == i + 1);
+    }
+}
+
+void TestSessionIDAllocator_Free(nlTestSuite * inSuite, void * inContext)
+{
+    SessionIDAllocator allocator;
+
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 0);
+
+    uint16_t id;
+
+    for (uint16_t i = 0; i < 16; i++)
+    {
+        CHIP_ERROR err = allocator.Allocate(id);
+        NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+        NL_TEST_ASSERT(inSuite, id == i);
+        NL_TEST_ASSERT(inSuite, allocator.Peek() == i + 1);
+    }
+
+    // Free an intermediate ID
+    allocator.Free(10);
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 16);
+
+    // Free the last allocated ID
+    allocator.Free(15);
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 15);
+
+    // Free some random unallocated ID
+    allocator.Free(100);
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 15);
+}
+
+void TestSessionIDAllocator_Reserve(nlTestSuite * inSuite, void * inContext)
+{
+    SessionIDAllocator allocator;
+
+    uint16_t id;
+
+    for (uint16_t i = 0; i < 16; i++)
+    {
+        CHIP_ERROR err = allocator.Allocate(id);
+        NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+        NL_TEST_ASSERT(inSuite, id == i);
+        NL_TEST_ASSERT(inSuite, allocator.Peek() == i + 1);
+    }
+
+    allocator.Reserve(100);
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 101);
+}
+
+void TestSessionIDAllocator_ReserveUpTo(nlTestSuite * inSuite, void * inContext)
+{
+    SessionIDAllocator allocator;
+
+    allocator.ReserveUpTo(100);
+    NL_TEST_ASSERT(inSuite, allocator.Peek() == 101);
+}
+
+// Test Suite
+
+/**
+ *  Test Suite that lists all the test functions.
+ */
+// clang-format off
+static const nlTest sTests[] =
+{
+    NL_TEST_DEF("SessionIDAllocator_Allocate", TestSessionIDAllocator_Allocate),
+    NL_TEST_DEF("SessionIDAllocator_Free", TestSessionIDAllocator_Free),
+    NL_TEST_DEF("SessionIDAllocator_Reserve", TestSessionIDAllocator_Reserve),
+    NL_TEST_DEF("SessionIDAllocator_ReserveUpTo", TestSessionIDAllocator_ReserveUpTo),
+
+    NL_TEST_SENTINEL()
+};
+// clang-format on
+
+/**
+ *  Set up the test suite.
+ */
+static int TestSetup(void * inContext)
+{
+    CHIP_ERROR error = chip::Platform::MemoryInit();
+    if (error != CHIP_NO_ERROR)
+        return FAILURE;
+    return SUCCESS;
+}
+
+/**
+ *  Tear down the test suite.
+ */
+static int TestTeardown(void * inContext)
+{
+    chip::Platform::MemoryShutdown();
+    return SUCCESS;
+}
+
+// clang-format off
+static nlTestSuite sSuite =
+{
+    "Test-CHIP-SessionIDAllocator",
+    &sTests[0],
+    TestSetup,
+    TestTeardown,
+};
+// clang-format on
+
+/**
+ *  Main
+ */
+int TestSessionIDAllocator()
+{
+    // Run test suit against one context
+    nlTestRunner(&sSuite, nullptr);
+
+    return (nlTestRunnerStats(&sSuite));
+}
+
+CHIP_REGISTER_TEST_SUITE(TestSessionIDAllocator)
diff --git a/src/transport/AdminPairingTable.cpp b/src/transport/AdminPairingTable.cpp
index 3ff3946..ceb554f 100644
--- a/src/transport/AdminPairingTable.cpp
+++ b/src/transport/AdminPairingTable.cpp
@@ -213,7 +213,7 @@
         return CHIP_NO_ERROR;
     }
 
-    VerifyOrReturnError(cert.size() <= kMaxChipCertSize, CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrReturnError(cert.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_ARGUMENT);
     if (mRootCertLen != 0 && mRootCertAllocatedLen < cert.size())
     {
         ReleaseRootCert();
@@ -252,7 +252,7 @@
     }
 
     // There could be two certs in the set -> ICA and NOC
-    VerifyOrReturnError(cert.size() <= kMaxChipCertSize * 2, CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrReturnError(cert.size() <= kMaxCHIPCertLength * 2, CHIP_ERROR_INVALID_ARGUMENT);
     if (mOpCertLen != 0 && mOpCertAllocatedLen < cert.size())
     {
         ReleaseOperationalCert();
@@ -275,7 +275,7 @@
                                             CertificateKeyId & rootKeyId)
 {
     constexpr uint8_t kMaxNumCertsInOpCreds = 3;
-    ReturnErrorOnFailure(certificates.Init(kMaxNumCertsInOpCreds, kMaxChipCertSize * kMaxNumCertsInOpCreds));
+    ReturnErrorOnFailure(certificates.Init(kMaxNumCertsInOpCreds, kMaxCHIPCertLength * kMaxNumCertsInOpCreds));
 
     ReturnErrorOnFailure(
         certificates.LoadCert(mRootCert, mRootCertLen,
diff --git a/src/transport/AdminPairingTable.h b/src/transport/AdminPairingTable.h
index b25079d..1b2126c 100644
--- a/src/transport/AdminPairingTable.h
+++ b/src/transport/AdminPairingTable.h
@@ -43,8 +43,6 @@
 constexpr char kAdminTableKeyPrefix[] = "CHIPAdmin";
 constexpr char kAdminTableCountKey[]  = "CHIPAdminNextId";
 
-constexpr uint16_t kMaxChipCertSize = 600;
-
 struct AccessControlList
 {
     uint32_t placeholder;
@@ -193,9 +191,9 @@
         uint16_t mOpCertLen;   /* This field is serialized in LittleEndian byte order */
 
         Crypto::P256SerializedKeypair mOperationalKey;
-        uint8_t mRootCert[kMaxChipCertSize];
+        uint8_t mRootCert[Credentials::kMaxCHIPCertLength];
         // The operationa credentials set can have up to two certs -> ICAC and NOC
-        uint8_t mOperationalCert[kMaxChipCertSize * 2];
+        uint8_t mOperationalCert[Credentials::kMaxCHIPCertLength * 2];
         char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' };
     };
 };
diff --git a/src/transport/SecureSessionMgr.cpp b/src/transport/SecureSessionMgr.cpp
index 162efd3..b13e236 100644
--- a/src/transport/SecureSessionMgr.cpp
+++ b/src/transport/SecureSessionMgr.cpp
@@ -214,6 +214,34 @@
     return err;
 }
 
+void SecureSessionMgr::ExpirePairing(SecureSessionHandle session)
+{
+    PeerConnectionState * state = GetPeerConnectionState(session);
+    if (state != nullptr)
+    {
+        mPeerConnections.MarkConnectionExpired(
+            state, [this](const Transport::PeerConnectionState & state1) { HandleConnectionExpired(state1); });
+    }
+}
+
+void SecureSessionMgr::ExpireAllPairings(NodeId peerNodeId, Transport::AdminId admin)
+{
+    PeerConnectionState * state = mPeerConnections.FindPeerConnectionState(peerNodeId, nullptr);
+    while (state != nullptr)
+    {
+        if (admin == state->GetAdminId())
+        {
+            mPeerConnections.MarkConnectionExpired(
+                state, [this](const Transport::PeerConnectionState & state1) { HandleConnectionExpired(state1); });
+            state = mPeerConnections.FindPeerConnectionState(peerNodeId, nullptr);
+        }
+        else
+        {
+            state = mPeerConnections.FindPeerConnectionState(peerNodeId, state);
+        }
+    }
+}
+
 CHIP_ERROR SecureSessionMgr::NewPairing(const Optional<Transport::PeerAddress> & peerAddr, NodeId peerNodeId,
                                         PairingSession * pairing, SecureSession::SessionRole direction, Transport::AdminId admin,
                                         Transport::Base * transport)
diff --git a/src/transport/SecureSessionMgr.h b/src/transport/SecureSessionMgr.h
index acdb01c..ac29c1b 100644
--- a/src/transport/SecureSessionMgr.h
+++ b/src/transport/SecureSessionMgr.h
@@ -220,6 +220,9 @@
     CHIP_ERROR NewPairing(const Optional<Transport::PeerAddress> & peerAddr, NodeId peerNodeId, PairingSession * pairing,
                           SecureSession::SessionRole direction, Transport::AdminId admin, Transport::Base * transport = nullptr);
 
+    void ExpirePairing(SecureSessionHandle session);
+    void ExpireAllPairings(NodeId peerNodeId, Transport::AdminId admin);
+
     /**
      * @brief
      *   Return the System Layer pointer used by current SecureSessionMgr.