Enable pairing for multiple devices (#3630)

diff --git a/examples/all-clusters-app/esp32/main/RendezvousDeviceDelegate.cpp b/examples/all-clusters-app/esp32/main/RendezvousDeviceDelegate.cpp
index 5060b57..683a577 100644
--- a/examples/all-clusters-app/esp32/main/RendezvousDeviceDelegate.cpp
+++ b/examples/all-clusters-app/esp32/main/RendezvousDeviceDelegate.cpp
@@ -70,12 +70,23 @@
         bluetoothLED.Set(true);
         break;
 
+    case RendezvousSessionDelegate::SecurePairingFailed:
+        ESP_LOGI(TAG, "Failed in SPAKE2+ handshake\n");
+        bluetoothLED.Set(false);
+        break;
+
     case RendezvousSessionDelegate::NetworkProvisioningSuccess:
 
         ESP_LOGI(TAG, "Device was assigned an ip address\n");
         bluetoothLED.Set(false);
         break;
 
+    case RendezvousSessionDelegate::NetworkProvisioningFailed:
+
+        ESP_LOGI(TAG, "Failed in network provisioning\n");
+        bluetoothLED.Set(false);
+        break;
+
     default:
         break;
     };
diff --git a/examples/chip-tool/commands/common/Command.h b/examples/chip-tool/commands/common/Command.h
index 8c81731..7ef5727 100644
--- a/examples/chip-tool/commands/common/Command.h
+++ b/examples/chip-tool/commands/common/Command.h
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 #include <inet/InetInterface.h>
 #include <support/logging/CHIPLogging.h>
 
diff --git a/examples/chip-tool/commands/common/Commands.h b/examples/chip-tool/commands/common/Commands.h
index 531b557..c331831 100644
--- a/examples/chip-tool/commands/common/Commands.h
+++ b/examples/chip-tool/commands/common/Commands.h
@@ -21,7 +21,7 @@
 #include "Command.h"
 #include <map>
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 
 class Commands
 {
diff --git a/src/controller/BUILD.gn b/src/controller/BUILD.gn
index 7d843de..aaab1fe 100644
--- a/src/controller/BUILD.gn
+++ b/src/controller/BUILD.gn
@@ -18,8 +18,12 @@
   output_name = "libChipController"
 
   sources = [
+    "CHIPDevice.cpp",
+    "CHIPDevice.h",
     "CHIPDeviceController.cpp",
     "CHIPDeviceController.h",
+    "CHIPDeviceController_deprecated.cpp",
+    "CHIPDeviceController_deprecated.h",
   ]
 
   cflags = [ "-Wconversion" ]
diff --git a/src/controller/CHIPDevice.cpp b/src/controller/CHIPDevice.cpp
new file mode 100644
index 0000000..68048e7
--- /dev/null
+++ b/src/controller/CHIPDevice.cpp
@@ -0,0 +1,216 @@
+/*
+ *
+ *    Copyright (c) 2020 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.
+ */
+
+/**
+ *  @file
+ *    This file contains implementation of Device class. The objects of this
+ *    class will be used by Controller applications to interact with CHIP
+ *    devices. The class provides mechanism to construct, send and receive
+ *    messages to and from the corresponding CHIP devices.
+ */
+
+#include <controller/CHIPDevice.h>
+
+#if CONFIG_DEVICE_LAYER
+#include <platform/CHIPDeviceLayer.h>
+#endif
+
+#include <core/CHIPCore.h>
+#include <core/CHIPEncoding.h>
+#include <core/CHIPSafeCasts.h>
+#include <support/Base64.h>
+#include <support/CHIPMem.h>
+#include <support/CodeUtils.h>
+#include <support/ErrorStr.h>
+#include <support/SafeInt.h>
+#include <support/logging/CHIPLogging.h>
+
+using namespace chip::Inet;
+using namespace chip::System;
+
+namespace chip {
+namespace Controller {
+
+CHIP_ERROR Device::SendMessage(System::PacketBuffer * buffer)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    System::PacketBuffer * resend = nullptr;
+
+    VerifyOrExit(mSessionManager != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+    // If there is no secure connection to the device, try establishing it
+    if (mState != ConnectionState::SecureConnected)
+    {
+        err = LoadSecureSessionParameters();
+        SuccessOrExit(err);
+    }
+    else
+    {
+        // Secure connection already exists
+        // Hold on to the buffer, in case session resumption and resend is needed
+        buffer->AddRef();
+        resend = buffer;
+    }
+
+    err    = mSessionManager->SendMessage(mDeviceId, buffer);
+    buffer = nullptr;
+    ChipLogDetail(Controller, "SendMessage returned %d", err);
+
+    // The send could fail due to network timeouts (e.g. broken pipe)
+    // Try sesion resumption if needed
+    if (err != CHIP_NO_ERROR && resend != nullptr && mState == ConnectionState::SecureConnected)
+    {
+        mState = ConnectionState::NotConnected;
+
+        err = LoadSecureSessionParameters();
+        SuccessOrExit(err);
+
+        err    = mSessionManager->SendMessage(mDeviceId, resend);
+        resend = nullptr;
+        ChipLogDetail(Controller, "Re-SendMessage returned %d", err);
+        SuccessOrExit(err);
+    }
+
+exit:
+
+    if (buffer != nullptr)
+    {
+        PacketBuffer::Free(buffer);
+    }
+
+    if (resend != nullptr)
+    {
+        PacketBuffer::Free(resend);
+    }
+
+    return err;
+}
+
+CHIP_ERROR Device::Serialize(SerializedDevice & output)
+{
+    CHIP_ERROR error       = CHIP_NO_ERROR;
+    uint16_t serializedLen = 0;
+    SerializableDevice serializable;
+
+    nlSTATIC_ASSERT_PRINT(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner),
+                          "Size of serializable should be <= size of output");
+
+    CHIP_ZERO_AT(serializable);
+
+    memmove(&serializable.mOpsCreds, &mPairing, sizeof(mPairing));
+    serializable.mDeviceId   = Encoding::LittleEndian::HostSwap64(mDeviceId);
+    serializable.mDevicePort = Encoding::LittleEndian::HostSwap16(mDevicePort);
+    nlSTATIC_ASSERT_PRINT(sizeof(serializable.mDeviceAddr) <= INET6_ADDRSTRLEN,
+                          "Size of device address must fit within INET6_ADDRSTRLEN");
+    mDeviceAddr.ToString(Uint8::to_char(serializable.mDeviceAddr), sizeof(serializable.mDeviceAddr));
+
+    serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast<uint8_t *>(&serializable)),
+                                       static_cast<uint16_t>(sizeof(serializable)), Uint8::to_char(output.inner));
+    VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
+    output.inner[serializedLen] = '\0';
+
+exit:
+    return error;
+}
+
+CHIP_ERROR Device::Deserialize(const SerializedDevice & input)
+{
+    CHIP_ERROR error = CHIP_NO_ERROR;
+    SerializableDevice serializable;
+    size_t maxlen            = BASE64_ENCODED_LEN(sizeof(serializable));
+    size_t len               = strnlen(Uint8::to_const_char(&input.inner[0]), maxlen);
+    uint16_t deserializedLen = 0;
+
+    VerifyOrExit(len < sizeof(SerializedDevice), error = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(CanCastTo<uint16_t>(len), error = CHIP_ERROR_INVALID_ARGUMENT);
+
+    CHIP_ZERO_AT(serializable);
+    deserializedLen = Base64Decode(Uint8::to_const_char(input.inner), static_cast<uint16_t>(len),
+                                   Uint8::to_uchar(reinterpret_cast<uint8_t *>(&serializable)));
+
+    VerifyOrExit(deserializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(deserializedLen <= sizeof(serializable), error = CHIP_ERROR_INVALID_ARGUMENT);
+
+    // The second parameter to FromString takes the strlen value. We are subtracting 1
+    // from the sizeof(serializable.mDeviceAddr) to account for null termination, since
+    // strlen doesn't include null character in the size.
+    VerifyOrExit(
+        IPAddress::FromString(Uint8::to_const_char(serializable.mDeviceAddr), sizeof(serializable.mDeviceAddr) - 1, mDeviceAddr),
+        error = CHIP_ERROR_INVALID_ADDRESS);
+
+    memmove(&mPairing, &serializable.mOpsCreds, sizeof(mPairing));
+    mDeviceId   = Encoding::LittleEndian::HostSwap64(serializable.mDeviceId);
+    mDevicePort = Encoding::LittleEndian::HostSwap16(serializable.mDevicePort);
+
+exit:
+    return error;
+}
+
+void Device::OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader,
+                               Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr)
+{
+    if (mState == ConnectionState::SecureConnected && mStatusDelegate != nullptr)
+    {
+        mStatusDelegate->OnMessage(msgBuf);
+    }
+}
+
+CHIP_ERROR Device::LoadSecureSessionParameters()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    SecurePairingSession pairingSession;
+
+    if (mSessionManager == nullptr || mState == ConnectionState::SecureConnected)
+    {
+        ExitNow(err = CHIP_ERROR_INCORRECT_STATE);
+    }
+
+    err = pairingSession.FromSerializable(mPairing);
+    SuccessOrExit(err);
+
+    err = mSessionManager->ResetTransport(Transport::UdpListenParameters(mInetLayer).SetAddressType(mDeviceAddr.Type()));
+    SuccessOrExit(err);
+
+    err = mSessionManager->NewPairing(
+        Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::UDP(mDeviceAddr, mDevicePort, mInterface)),
+        &pairingSession);
+    SuccessOrExit(err);
+
+    mState = ConnectionState::SecureConnected;
+
+exit:
+
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Controller, "LoadSecureSessionParameters returning error %d\n", err);
+    }
+    return err;
+}
+
+bool Device::GetIpAddress(Inet::IPAddress & addr) const
+{
+    if (mState == ConnectionState::SecureConnected)
+        addr = mDeviceAddr;
+    return mState == ConnectionState::SecureConnected;
+}
+
+} // namespace Controller
+} // namespace chip
diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h
new file mode 100644
index 0000000..69d7491
--- /dev/null
+++ b/src/controller/CHIPDevice.h
@@ -0,0 +1,260 @@
+/*
+ *
+ *    Copyright (c) 2020 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.
+ */
+
+/**
+ *  @file
+ *    This file contains definitions for Device class. The objects of this
+ *    class will be used by Controller applications to interact with CHIP
+ *    devices. The class provides mechanism to construct, send and receive
+ *    messages to and from the corresponding CHIP devices.
+ */
+
+#pragma once
+
+#include <core/CHIPCore.h>
+#include <support/Base64.h>
+#include <support/DLLUtil.h>
+#include <transport/SecurePairingSession.h>
+#include <transport/SecureSessionMgr.h>
+#include <transport/raw/MessageHeader.h>
+#include <transport/raw/UDP.h>
+
+namespace chip {
+namespace Controller {
+
+class DeviceController;
+class DeviceStatusDelegate;
+struct SerializedDevice;
+
+class DLL_EXPORT Device
+{
+public:
+    Device() : mActive(false), mState(ConnectionState::NotConnected) {}
+    ~Device() {}
+
+    /**
+     * @brief
+     *   Set the delegate object which will be called when a message is received.
+     *   The user of this Device object must reset the delegate (by calling
+     *   SetDelegate(nullptr)) before releasing their delegate object.
+     *
+     * @param[in] delegate   The pointer to the delegate object.
+     */
+    void SetDelegate(DeviceStatusDelegate * delegate) { mStatusDelegate = delegate; }
+
+    // ----- Messaging -----
+    /**
+     * @brief
+     *   Send the provided message to the device
+     *
+     * @param[in] message   The message to be sent. The ownership of the message buffer
+     *                      is handed over to Device object. SendMessage() will
+     *                      decrement the reference count of the message buffer before
+     *                      returning.
+     *
+     * @return CHIP_ERROR   CHIP_NO_ERROR on success, or corresponding error
+     */
+    CHIP_ERROR SendMessage(System::PacketBuffer * message);
+
+    /**
+     * @brief
+     *   Get the IP address assigned to the device.
+     *
+     * @param[out] addr   The reference to the IP address.
+     *
+     * @return true, if the IP address was filled in the out parameter, false otherwise
+     */
+    bool GetIpAddress(Inet::IPAddress & addr) const;
+
+    /**
+     * @brief
+     *   Initialize the device object with secure session manager and inet layer object
+     *   references. This variant of function is typically used when the device object
+     *   is created from a serialized device information. The other parameters (address, port,
+     *   interface etc) are part of the serialized device, so those are not required to be
+     *   initialized.
+     *
+     *   Note: The lifetime of session manager and inet layer objects must be longer than
+     *   that of this device object. If these objects are freed, while the device object is
+     *   still using them, it can lead to unknown behavior and crashes.
+     *
+     * @param[in] sessionMgr   Secure session manager object pointer
+     * @param[in] inetLayer    InetLayer object pointer
+     */
+    void Init(SecureSessionMgr<Transport::UDP> * sessionMgr, Inet::InetLayer * inetLayer)
+    {
+        mSessionManager = sessionMgr;
+        mInetLayer      = inetLayer;
+    }
+
+    /**
+     * @brief
+     *   Initialize a new device object with secure session manager, inet layer object,
+     *   and other device specific parameters. This variant of function is typically used when
+     *   a new device is paired, and the corresponding device object needs to updated with
+     *   all device specifc parameters (address, port, interface etc).
+     *
+     *   This is not done as part of constructor so that the controller can have a list of
+     *   uninitialzed/unpaired device objects. The object is initialized only when the device
+     *   is actually paired.
+     *
+     * @param[in] sessionMgr   Secure session manager object pointer
+     * @param[in] inetLayer    InetLayer object pointer
+     * @param[in] deviceId     Node ID of the device
+     * @param[in] devicePort   Port on which device is listening (typically CHIP_PORT)
+     * @param[in] interfaceId  Local Interface ID that should be used to talk to the device
+     */
+    void Init(SecureSessionMgr<Transport::UDP> * sessionMgr, Inet::InetLayer * inetLayer, NodeId deviceId, uint16_t devicePort,
+              Inet::InterfaceId interfaceId)
+    {
+        Init(sessionMgr, inetLayer);
+        mDeviceId   = deviceId;
+        mDevicePort = devicePort;
+        mInterface  = interfaceId;
+        mState      = ConnectionState::Connecting;
+    }
+
+    /** @brief Serialize the Pairing Session to a string. It's guaranteed that the string
+     *         will be null terminated, and there won't be any embedded null characters.
+     *
+     * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
+     **/
+    CHIP_ERROR Serialize(SerializedDevice & output);
+
+    /** @brief Deserialize the Pairing Session from the string. It's expected that the string
+     *         will be null terminated, and there won't be any embedded null characters.
+     *
+     * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
+     **/
+    CHIP_ERROR Deserialize(const SerializedDevice & input);
+
+    /**
+     * @brief
+     *   This function is called when a message is received from the corresponding CHIP
+     *   device. The message ownership is transferred to the function, and it is expected
+     *   to release the message buffer before returning.
+     *
+     * @param[in] header        Reference to common packet header of the received message
+     * @param[in] payloadHeader Reference to payload header in the message
+     * @param[in] state         Pointer to the peer connection state on which message is received
+     * @param[in] msgBuf        The message buffer
+     * @param[in] mgr           Pointer to secure session manager which received the message
+     */
+    void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, Transport::PeerConnectionState * state,
+                           System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr);
+
+    /**
+     * @brief
+     *   Return whether the current device object is actively associated with a paired CHIP
+     *   device. An active object can be used to communicate with the corresponding device.
+     */
+    bool IsActive() const { return mActive; }
+
+    void SetActive(bool active) { mActive = active; }
+
+    NodeId GetDeviceId() const { return mDeviceId; }
+
+    void SetAddress(const Inet::IPAddress & deviceAddr) { mDeviceAddr = deviceAddr; }
+
+    SecurePairingSessionSerializable & GetPairing() { return mPairing; }
+
+private:
+    enum class ConnectionState
+    {
+        NotConnected,
+        Connecting,
+        SecureConnected,
+    };
+
+    /* Node ID assigned to the CHIP device */
+    NodeId mDeviceId;
+
+    /* IP Address of the CHIP device */
+    Inet::IPAddress mDeviceAddr;
+
+    /* Port on which the CHIP device is receiving messages. Typically it is CHIP_PORT */
+    uint16_t mDevicePort;
+
+    /* Local network interface that should be used to communicate with the device */
+    Inet::InterfaceId mInterface;
+
+    Inet::InetLayer * mInetLayer;
+
+    bool mActive;
+    ConnectionState mState;
+
+    SecurePairingSessionSerializable mPairing;
+
+    DeviceStatusDelegate * mStatusDelegate;
+
+    SecureSessionMgr<Transport::UDP> * mSessionManager;
+
+    /**
+     * @brief
+     *   This function loads the secure session object from the serialized operational
+     *   credentials corresponding to the device. This is typically done when the device
+     *   does not have an active secure channel.
+     */
+    CHIP_ERROR LoadSecureSessionParameters();
+};
+
+/**
+ * This class defines an interface for an object that the user of Device
+ * can register as a delegate. The delegate object will be called by the
+ * Device when a new message or status update is received from the corresponding
+ * CHIP device.
+ */
+class DLL_EXPORT DeviceStatusDelegate
+{
+public:
+    virtual ~DeviceStatusDelegate() {}
+
+    /**
+     * @brief
+     *   Called when a message is received from the device.
+     *
+     * @param[in] msg Received message buffer.
+     */
+    virtual void OnMessage(System::PacketBuffer * msg) = 0;
+
+    /**
+     * @brief
+     *   Called when device status is updated.
+     *
+     */
+    virtual void OnStatusChange(void){};
+};
+
+typedef struct SerializableDevice
+{
+    SecurePairingSessionSerializable mOpsCreds;
+    uint64_t mDeviceId; /* This field is serialized in LittleEndian byte order */
+    uint8_t mDeviceAddr[INET6_ADDRSTRLEN];
+    uint16_t mDevicePort; /* This field is serealized in LittelEndian byte order */
+} SerializableDevice;
+
+typedef struct SerializedDevice
+{
+    // Extra uint64_t to account for padding bytes (NULL termination, and some decoding overheads)
+    // The encoder may not include a NULL character, and there are maximum 2 bytes of padding.
+    // So extra 8 bytes should be sufficient to absorb this overhead.
+    uint8_t inner[BASE64_ENCODED_LEN(sizeof(SerializableDevice) + sizeof(uint64_t))];
+} SerializedDevice;
+
+} // namespace Controller
+} // namespace chip
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index ab1471b..7ce20bd 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -46,6 +46,7 @@
 #include <support/CHIPMem.h>
 #include <support/CodeUtils.h>
 #include <support/ErrorStr.h>
+#include <support/SafeInt.h>
 #include <support/TimeUtils.h>
 #include <support/logging/CHIPLogging.h>
 
@@ -59,12 +60,12 @@
 using namespace chip::System;
 
 namespace chip {
-namespace DeviceController {
+namespace Controller {
 
 using namespace chip::Encoding;
 
-constexpr const char kDeviceCredentialsKeyPrefix[] = "DeviceCredentials";
-constexpr const char kDeviceAddressKeyPrefix[]     = "DeviceAddress";
+constexpr const char kPairedDeviceListKeyPrefix[] = "ListPairedDevices";
+constexpr const char kPairedDeviceKeyPrefix[]     = "PairedDevice";
 
 // This macro generates a key using node ID an key prefix, and performs the given action
 // on that key.
@@ -80,66 +81,41 @@
         action;                                                                                                                    \
     } while (0)
 
-ChipDeviceController::ChipDeviceController()
+DeviceController::DeviceController()
 {
-    mState             = kState_NotInitialized;
-    AppState           = nullptr;
-    mConState          = kConnectionState_NotConnected;
-    mRendezvousSession = nullptr;
-    mSessionManager    = nullptr;
-    mCurReqMsg         = nullptr;
-    mOnError           = nullptr;
-    mOnNewConnection   = nullptr;
-    mPairingDelegate   = nullptr;
-    mStorageDelegate   = nullptr;
-    mListenPort        = CHIP_PORT;
-    mDeviceAddr        = IPAddress::Any;
-    mDevicePort        = CHIP_PORT;
-    mInterface         = INET_NULL_INTERFACEID;
-    mLocalDeviceId     = 0;
-    mNumCachedPackets  = 0;
-    CHIP_ZERO_AT(mOnComplete);
-    CHIP_ZERO_AT(mCachedPackets);
+    mState                    = State::NotInitialized;
+    mSessionManager           = nullptr;
+    mLocalDeviceId            = 0;
+    mStorageDelegate          = nullptr;
+    mPairedDevicesInitialized = false;
 }
 
-ChipDeviceController::~ChipDeviceController()
+CHIP_ERROR DeviceController::Init(NodeId localDeviceId, PersistentStorageDelegate * storageDelegate, System::Layer * systemLayer,
+                                  Inet::InetLayer * inetLayer)
 {
-    if (mTestSecurePairingSecret != nullptr)
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    VerifyOrExit(mState == State::NotInitialized, err = CHIP_ERROR_INCORRECT_STATE);
+
+    if (systemLayer != nullptr && inetLayer != nullptr)
     {
-        chip::Platform::Delete(mTestSecurePairingSecret);
+        mSystemLayer = systemLayer;
+        mInetLayer   = inetLayer;
     }
-}
-
-CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, DevicePairingDelegate * pairingDelegate,
-                                      PersistentStorageDelegate * storageDelegate)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    VerifyOrExit(mState == kState_NotInitialized, err = CHIP_ERROR_INCORRECT_STATE);
-
+    else
+    {
 #if CONFIG_DEVICE_LAYER
-    err = DeviceLayer::PlatformMgr().InitChipStack();
-    SuccessOrExit(err);
+        err = DeviceLayer::PlatformMgr().InitChipStack();
+        SuccessOrExit(err);
 
-    err = Init(localNodeId, &DeviceLayer::SystemLayer, &DeviceLayer::InetLayer, pairingDelegate, storageDelegate);
+        mSystemLayer = &DeviceLayer::SystemLayer;
+        mInetLayer   = &DeviceLayer::InetLayer;
 #endif // CONFIG_DEVICE_LAYER
+    }
 
-exit:
-    return err;
-}
+    VerifyOrExit(mSystemLayer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(mInetLayer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
 
-CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, System::Layer * systemLayer, InetLayer * inetLayer,
-                                      DevicePairingDelegate * pairingDelegate, PersistentStorageDelegate * storageDelegate)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    VerifyOrExit(mState == kState_NotInitialized, err = CHIP_ERROR_INCORRECT_STATE);
-
-    mState           = kState_Initialized;
-    mLocalDeviceId   = localNodeId;
-    mSystemLayer     = systemLayer;
-    mInetLayer       = inetLayer;
-    mPairingDelegate = pairingDelegate;
     mStorageDelegate = storageDelegate;
 
     if (mStorageDelegate != nullptr)
@@ -147,19 +123,30 @@
         mStorageDelegate->SetDelegate(this);
     }
 
+    mSessionManager = chip::Platform::New<SecureSessionMgr<Transport::UDP>>();
+
+    err = mSessionManager->Init(localDeviceId, mSystemLayer,
+                                Transport::UdpListenParameters(mInetLayer).SetAddressType(Inet::kIPAddressType_IPv6));
+    SuccessOrExit(err);
+
+    mSessionManager->SetDelegate(this);
+
+    mState         = State::Initialized;
+    mLocalDeviceId = localDeviceId;
+
 exit:
     return err;
 }
 
-CHIP_ERROR ChipDeviceController::Shutdown()
+CHIP_ERROR DeviceController::Shutdown()
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+
     ChipLogDetail(Controller, "Shutting down the controller");
 
-    VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE);
-
-    mState = kState_NotInitialized;
+    mState = State::NotInitialized;
 
 #if CONFIG_DEVICE_LAYER
     err = DeviceLayer::PlatformMgr().Shutdown();
@@ -174,408 +161,128 @@
     mSystemLayer = nullptr;
     mInetLayer   = nullptr;
 
-    if (mSessionManager != nullptr)
-    {
-        chip::Platform::Delete(mSessionManager);
-        mSessionManager = nullptr;
-    }
-
-    if (mRendezvousSession != nullptr)
-    {
-        chip::Platform::Delete(mRendezvousSession);
-        mRendezvousSession = nullptr;
-    }
-
-    mConState = kConnectionState_NotConnected;
-    CHIP_ZERO_AT(mOnComplete);
-    mOnError         = nullptr;
-    mOnNewConnection = nullptr;
-    mRemoteDeviceId.ClearValue();
-
-exit:
-    return err;
-}
-
-CHIP_ERROR ChipDeviceController::ConnectDevice(NodeId remoteDeviceId, RendezvousParameters & params, void * appReqState,
-                                               NewConnectionHandler onConnected, MessageReceiveHandler onMessageReceived,
-                                               ErrorHandler onError, uint16_t devicePort, Inet::InterfaceId interfaceId)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE);
-    VerifyOrExit(mConState == kConnectionState_NotConnected, err = CHIP_ERROR_INCORRECT_STATE);
-
-#if CONFIG_DEVICE_LAYER && CONFIG_NETWORK_LAYER_BLE
-    if (!params.HasBleLayer())
-    {
-        params.SetBleLayer(DeviceLayer::ConnectivityMgr().GetBleLayer());
-    }
-#endif // CONFIG_DEVICE_LAYER && CONFIG_NETWORK_LAYER_BLE
-
-    mRendezvousSession = chip::Platform::New<RendezvousSession>(this);
-    err                = mRendezvousSession->Init(params.SetLocalNodeId(mLocalDeviceId));
-    SuccessOrExit(err);
-
-    mRemoteDeviceId  = Optional<NodeId>::Value(remoteDeviceId);
-    mDevicePort      = devicePort;
-    mInterface       = interfaceId;
-    mAppReqState     = appReqState;
-    mOnNewConnection = onConnected;
-
-    // connected state before 'OnConnect'
-    mConState = kConnectionState_Connected;
-
-    mOnComplete.Response = onMessageReceived;
-    mOnError             = onError;
-
-exit:
-    if (err != CHIP_NO_ERROR && mRendezvousSession != nullptr)
-    {
-        chip::Platform::Delete(mRendezvousSession);
-        mRendezvousSession = nullptr;
-    }
-
-    return err;
-}
-
-CHIP_ERROR ChipDeviceController::ConnectDeviceWithoutSecurePairing(NodeId remoteDeviceId, const IPAddress & deviceAddr,
-                                                                   void * appReqState, NewConnectionHandler onConnected,
-                                                                   MessageReceiveHandler onMessageReceived, ErrorHandler onError,
-                                                                   uint16_t devicePort, Inet::InterfaceId interfaceId)
-{
-    if (mTestSecurePairingSecret != nullptr)
-    {
-        chip::Platform::Delete(mTestSecurePairingSecret);
-    }
-
-    mTestSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>(
-        Optional<NodeId>::Value(remoteDeviceId), static_cast<uint16_t>(0), static_cast<uint16_t>(0));
-
-    mSecurePairingSession = mTestSecurePairingSecret;
-
-    mDeviceAddr      = deviceAddr;
-    mRemoteDeviceId  = Optional<NodeId>::Value(remoteDeviceId);
-    mDevicePort      = devicePort;
-    mInterface       = interfaceId;
-    mAppReqState     = appReqState;
-    mOnNewConnection = onConnected;
-
-    mConState = kConnectionState_Connected;
-
-    mOnComplete.Response = onMessageReceived;
-    mOnError             = onError;
-
-    if (mOnNewConnection)
-    {
-        mOnNewConnection(this, nullptr, mAppReqState);
-    }
-
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR ChipDeviceController::SetUdpListenPort(uint16_t listenPort)
-{
-    if (mState != kState_Initialized || mConState != kConnectionState_NotConnected)
-        return CHIP_ERROR_INCORRECT_STATE;
-    mListenPort = listenPort;
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR ChipDeviceController::EstablishSecureSession()
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    VerifyOrExit(mSecurePairingSession != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
-
-    mSessionManager = chip::Platform::New<SecureSessionMgr<Transport::UDP>>();
-
-    err = mSessionManager->Init(
-        mLocalDeviceId, mSystemLayer,
-        Transport::UdpListenParameters(mInetLayer).SetAddressType(mDeviceAddr.Type()).SetListenPort(mListenPort));
-    SuccessOrExit(err);
-
-    mSessionManager->SetDelegate(this);
-
-    err = mSessionManager->NewPairing(
-        Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::UDP(mDeviceAddr, mDevicePort, mInterface)),
-        mSecurePairingSession);
-    SuccessOrExit(err);
-
-    mConState = kConnectionState_SecureConnected;
-
-    if (mDeviceAddr != IPAddress::Any)
-    {
-        SendCachedPackets();
-    } // else wait for mdns discovery
-
-exit:
-
-    if (err != CHIP_NO_ERROR)
-    {
-        if (mSessionManager != nullptr)
-        {
-            chip::Platform::Delete(mSessionManager);
-            mSessionManager = nullptr;
-        }
-        mConState = kConnectionState_NotConnected;
-    }
-
-    return err;
-}
-
-void ChipDeviceController::OnValue(const char * key, const char * value)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-    NodeId peer    = mRemoteDeviceId.Value();
-
-    VerifyOrExit(key != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
-
-    PERSISTENT_KEY_OP(
-        peer, kDeviceCredentialsKeyPrefix, expectedKey, if (strcmp(key, expectedKey) == 0) {
-            SecurePairingSessionSerialized serialized;
-            size_t length = 0;
-
-            VerifyOrExit(value != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
-            length = strlen(value) + 1; // account for the null termination
-            VerifyOrExit(length <= sizeof(serialized.inner), err = CHIP_ERROR_INVALID_ARGUMENT);
-            memmove(serialized.inner, value, length);
-            SuccessOrExit(mPairingSession.Deserialize(serialized));
-
-            mSecurePairingSession = &mPairingSession;
-        });
-
-    PERSISTENT_KEY_OP(
-        peer, kDeviceAddressKeyPrefix, expectedKey, if (strcmp(key, expectedKey) == 0) {
-            VerifyOrExit(value != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
-            VerifyOrExit(IPAddress::FromString(value, mDeviceAddr), err = CHIP_ERROR_INVALID_ADDRESS);
-        });
-
-    if (mSecurePairingSession != nullptr && mDeviceAddr != IPAddress::Any)
-    {
-        SuccessOrExit(EstablishSecureSession());
-    }
-
-exit:
-    if (err != CHIP_NO_ERROR)
-    {
-        mConState = kConnectionState_NotConnected;
-        DiscardCachedPackets();
-    }
-}
-
-void ChipDeviceController::OnStatus(const char * key, Operation op, CHIP_ERROR err) {}
-
-CHIP_ERROR ChipDeviceController::TryEstablishingSecureSession(NodeId peer)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    if (mState != kState_Initialized || mSessionManager != nullptr || mConState == kConnectionState_SecureConnected)
-    {
-        ExitNow(err = CHIP_ERROR_INCORRECT_STATE);
-    }
-
     if (mStorageDelegate != nullptr)
     {
-        mSecurePairingSession = nullptr;
-        mDeviceAddr           = IPAddress::Any;
-        PERSISTENT_KEY_OP(peer, kDeviceCredentialsKeyPrefix, key, mStorageDelegate->GetKeyValue(key));
-        PERSISTENT_KEY_OP(peer, kDeviceAddressKeyPrefix, key, mStorageDelegate->GetKeyValue(key));
-        mConState = kConnectionState_SecureConnecting;
-    }
-    else
-    {
-        ExitNow(err = EstablishSecureSession());
-    }
-
-exit:
-
-    if (err != CHIP_NO_ERROR)
-    {
-        DiscardCachedPackets();
-    }
-    return err;
-}
-
-CHIP_ERROR ChipDeviceController::ResumeSecureSession(NodeId peer)
-{
-    if (mConState == kConnectionState_SecureConnected)
-    {
-        mConState = kConnectionState_Connected;
+        mStorageDelegate->SetDelegate(nullptr);
+        mStorageDelegate = nullptr;
     }
 
     if (mSessionManager != nullptr)
     {
+        mSessionManager->SetDelegate(nullptr);
         chip::Platform::Delete(mSessionManager);
         mSessionManager = nullptr;
     }
 
-    CHIP_ERROR err = TryEstablishingSecureSession(peer);
-    SuccessOrExit(err);
-
-exit:
-
-    if (err != CHIP_NO_ERROR)
-    {
-        ChipLogError(Controller, "ResumeSecureSession returning error %d\n", err);
-    }
-    return err;
-}
-
-bool ChipDeviceController::IsConnected() const
-{
-    return mState == kState_Initialized &&
-        (mConState == kConnectionState_Connected || mConState == kConnectionState_SecureConnected);
-}
-
-bool ChipDeviceController::IsSecurelyConnected() const
-{
-    return mState == kState_Initialized && mConState == kConnectionState_SecureConnected;
-}
-
-bool ChipDeviceController::GetIpAddress(Inet::IPAddress & addr) const
-{
-    if (IsConnected())
-        addr = mDeviceAddr;
-    return IsConnected();
-}
-
-CHIP_ERROR ChipDeviceController::DisconnectDevice()
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    if (!IsConnected())
-    {
-        return CHIP_ERROR_INCORRECT_STATE;
-    }
-
-    if (mSessionManager != nullptr)
-    {
-        chip::Platform::Delete(mSessionManager);
-        mSessionManager = nullptr;
-    }
-
-    if (mRendezvousSession != nullptr)
-    {
-        chip::Platform::Delete(mRendezvousSession);
-        mRendezvousSession = nullptr;
-    }
-
-    mConState = kConnectionState_NotConnected;
-    return err;
-}
-
-CHIP_ERROR ChipDeviceController::CachePacket(System::PacketBuffer * buffer)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-
-    VerifyOrExit(mNumCachedPackets < kPacketCacheMaxSize, err = CHIP_ERROR_INTERNAL);
-    mCachedPackets[mNumCachedPackets++] = buffer;
-
 exit:
     return err;
 }
 
-CHIP_ERROR ChipDeviceController::SendCachedPackets()
+CHIP_ERROR DeviceController::GetDevice(NodeId deviceId, const SerializedDevice & deviceInfo, Device ** out_device)
 {
-    CHIP_ERROR err = CHIP_NO_ERROR;
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+    uint16_t index  = 0;
 
-    VerifyOrExit(IsSecurelyConnected(), err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(out_device != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    index = FindDeviceIndex(deviceId);
 
-    for (uint16_t i = 0; i < mNumCachedPackets; i++)
+    if (index < kNumMaxActiveDevices)
     {
-        err = mSessionManager->SendMessage(mRemoteDeviceId.Value(), mCachedPackets[i]);
-        ChipLogDetail(Controller, "SendMessage returned %d", err);
-    }
-
-    mNumCachedPackets = 0;
-    CHIP_ZERO_AT(mCachedPackets);
-
-exit:
-    return err;
-}
-
-void ChipDeviceController::DiscardCachedPackets()
-{
-    for (uint16_t i = 0; i < mNumCachedPackets; i++)
-    {
-        PacketBuffer::Free(mCachedPackets[i]);
-    }
-
-    mNumCachedPackets = 0;
-    CHIP_ZERO_AT(mCachedPackets);
-}
-
-CHIP_ERROR ChipDeviceController::SendMessage(void * appReqState, PacketBuffer * buffer, NodeId peerDevice)
-{
-    CHIP_ERROR err            = CHIP_NO_ERROR;
-    bool trySessionResumption = true;
-
-    VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
-    VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE);
-
-    mAppReqState = appReqState;
-
-    if (peerDevice != kUndefinedNodeId)
-    {
-        mRemoteDeviceId = Optional<NodeId>::Value(peerDevice);
-    }
-    VerifyOrExit(mRemoteDeviceId.HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
-
-    // If there is no secure connection to the device, try establishing it
-    if (!IsSecurelyConnected())
-    {
-        err = TryEstablishingSecureSession(mRemoteDeviceId.Value());
-        SuccessOrExit(err);
-
-        trySessionResumption = false;
-
-        if (mDeviceAddr == IPAddress::Any || mConState == kConnectionState_SecureConnecting)
-        {
-            // Cache the packet while connection is being established
-            ExitNow(err = CachePacket(buffer));
-        }
-    }
-
-    // Hold on to the buffer, in case of session resumption and resend is needed
-    buffer->AddRef();
-
-    err = mSessionManager->SendMessage(mRemoteDeviceId.Value(), buffer);
-    ChipLogDetail(Controller, "SendMessage returned %d", err);
-
-    // The send could fail due to network timeouts (e.g. broken pipe)
-    // Try sesion resumption if needed
-    if (err != CHIP_NO_ERROR && trySessionResumption)
-    {
-        err = ResumeSecureSession(mRemoteDeviceId.Value());
-        // If session resumption failed, let's free the extra reference to
-        // the buffer. If not, SendMessage would free it.
-        VerifyOrExit(err == CHIP_NO_ERROR, PacketBuffer::Free(buffer));
-
-        if (mConState == kConnectionState_SecureConnecting)
-        {
-            // Cache the packet while connection is being established
-            ExitNow(err = CachePacket(buffer));
-        }
-
-        err = mSessionManager->SendMessage(mRemoteDeviceId.Value(), buffer);
-        SuccessOrExit(err);
+        device = &mActiveDevices[index];
     }
     else
     {
-        // Free the extra reference to the buffer
-        PacketBuffer::Free(buffer);
+        VerifyOrExit(mPairedDevices.Contains(deviceId), err = CHIP_ERROR_NOT_CONNECTED);
+
+        index = GetInactiveDeviceIndex();
+        VerifyOrExit(index < kNumMaxActiveDevices, err = CHIP_ERROR_NO_MEMORY);
+        device = &mActiveDevices[index];
+
+        err = device->Deserialize(deviceInfo);
+        VerifyOrExit(err == CHIP_NO_ERROR, ReleaseDevice(device));
+
+        device->Init(mSessionManager, mInetLayer);
     }
 
-exit:
+    *out_device = device;
 
+exit:
     return err;
 }
 
-CHIP_ERROR ChipDeviceController::ServiceEvents()
+CHIP_ERROR DeviceController::GetDevice(NodeId deviceId, Device ** out_device)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+    uint16_t index  = 0;
+    char * buffer   = nullptr;
+
+    VerifyOrExit(out_device != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    index = FindDeviceIndex(deviceId);
+
+    if (index < kNumMaxActiveDevices)
+    {
+        device = &mActiveDevices[index];
+    }
+    else
+    {
+        VerifyOrExit(mStorageDelegate != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+        if (!mPairedDevicesInitialized)
+        {
+            constexpr uint16_t max_size = CHIP_MAX_SERIALIZED_SIZE_U64(kNumMaxPairedDevices);
+            buffer                      = static_cast<char *>(chip::Platform::MemoryAlloc(max_size));
+            uint16_t size               = max_size;
+
+            VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+            PERSISTENT_KEY_OP(static_cast<uint64_t>(0), kPairedDeviceListKeyPrefix, key,
+                              err = mStorageDelegate->GetKeyValue(key, buffer, size));
+            SuccessOrExit(err);
+            VerifyOrExit(size <= max_size, err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
+
+            err = SetPairedDeviceList(buffer);
+            SuccessOrExit(err);
+        }
+
+        VerifyOrExit(mPairedDevices.Contains(deviceId), err = CHIP_ERROR_NOT_CONNECTED);
+
+        index = GetInactiveDeviceIndex();
+        VerifyOrExit(index < kNumMaxActiveDevices, err = CHIP_ERROR_NO_MEMORY);
+        device = &mActiveDevices[index];
+
+        {
+            SerializedDevice deviceInfo;
+            uint16_t size = sizeof(deviceInfo.inner);
+
+            PERSISTENT_KEY_OP(deviceId, kPairedDeviceKeyPrefix, key,
+                              err = mStorageDelegate->GetKeyValue(key, Uint8::to_char(deviceInfo.inner), size));
+            SuccessOrExit(err);
+            VerifyOrExit(size <= sizeof(deviceInfo.inner), err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
+
+            err = device->Deserialize(deviceInfo);
+            VerifyOrExit(err == CHIP_NO_ERROR, ReleaseDevice(device));
+
+            device->Init(mSessionManager, mInetLayer);
+        }
+    }
+
+    *out_device = device;
+
+exit:
+    if (buffer != nullptr)
+    {
+        chip::Platform::MemoryFree(buffer);
+    }
+    return err;
+}
+
+CHIP_ERROR DeviceController::ServiceEvents()
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
-    VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
 
 #if CONFIG_DEVICE_LAYER
     err = DeviceLayer::PlatformMgr().StartEventLoopTask();
@@ -586,11 +293,11 @@
     return err;
 }
 
-CHIP_ERROR ChipDeviceController::ServiceEventSignal()
+CHIP_ERROR DeviceController::ServiceEventSignal()
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
-    VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
 
 #if CONFIG_DEVICE_LAYER && (CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK)
     DeviceLayer::SystemLayer.WakeSelect();
@@ -602,54 +309,274 @@
     return err;
 }
 
-CHIP_ERROR ChipDeviceController::SetDevicePairingDelegate(DevicePairingDelegate * pairingDelegate)
+void DeviceController::OnNewConnection(Transport::PeerConnectionState * peerConnection, SecureSessionMgrBase * mgr) {}
+
+void DeviceController::OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader,
+                                         Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf,
+                                         SecureSessionMgrBase * mgr)
 {
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    uint16_t index = 0;
+    NodeId peer;
+
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(header.GetSourceNodeId().HasValue(), err = CHIP_ERROR_INVALID_ARGUMENT);
+
+    peer  = header.GetSourceNodeId().Value();
+    index = FindDeviceIndex(peer);
+    VerifyOrExit(index < kNumMaxActiveDevices, err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
+
+    mActiveDevices[index].OnMessageReceived(header, payloadHeader, state, msgBuf, mgr);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Controller, "Failed to process received message: err %d", err);
+    }
+    return;
+}
+
+uint16_t DeviceController::GetInactiveDeviceIndex()
+{
+    uint16_t i = 0;
+    while (i < kNumMaxActiveDevices && mActiveDevices[i].IsActive())
+        i++;
+    if (i < kNumMaxActiveDevices)
+    {
+        mActiveDevices[i].SetActive(true);
+    }
+
+    return i;
+}
+
+void DeviceController::ReleaseDevice(Device * device)
+{
+    device->SetActive(false);
+}
+
+void DeviceController::ReleaseDevice(uint16_t index)
+{
+    if (index < kNumMaxActiveDevices)
+    {
+        ReleaseDevice(&mActiveDevices[index]);
+    }
+}
+
+uint16_t DeviceController::FindDeviceIndex(NodeId id)
+{
+    uint16_t i = 0;
+    while (i < kNumMaxActiveDevices)
+    {
+        if (mActiveDevices[i].IsActive() && mActiveDevices[i].GetDeviceId() == id)
+        {
+            return i;
+        }
+        i++;
+    }
+    return i;
+}
+
+CHIP_ERROR DeviceController::SetPairedDeviceList(const char * serialized)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    size_t len      = strlen(serialized) + 1;
+    uint16_t lenU16 = static_cast<uint16_t>(len);
+    VerifyOrExit(CanCastTo<uint16_t>(len), err = CHIP_ERROR_INVALID_ARGUMENT);
+    err = mPairedDevices.DeserializeBase64(serialized, lenU16);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Controller, "Failed to recreate the device list\n");
+    }
+    else
+    {
+        mPairedDevicesInitialized = true;
+    }
+
+    return err;
+}
+
+void DeviceController::OnValue(const char * key, const char * value) {}
+
+void DeviceController::OnStatus(const char * key, Operation op, CHIP_ERROR err) {}
+
+DeviceCommissioner::DeviceCommissioner()
+{
+    mPairingDelegate      = nullptr;
+    mRendezvousSession    = nullptr;
+    mDeviceBeingPaired    = kNumMaxActiveDevices;
+    mPairedDevicesUpdated = false;
+}
+
+CHIP_ERROR DeviceCommissioner::Init(NodeId localDeviceId, PersistentStorageDelegate * storageDelegate,
+                                    DevicePairingDelegate * pairingDelegate, System::Layer * systemLayer,
+                                    Inet::InetLayer * inetLayer)
+{
+    CHIP_ERROR err = DeviceController::Init(localDeviceId, storageDelegate, systemLayer, inetLayer);
+    SuccessOrExit(err);
+
     mPairingDelegate = pairingDelegate;
+
+exit:
+    return err;
+}
+
+CHIP_ERROR DeviceCommissioner::Shutdown()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+
+    ChipLogDetail(Controller, "Shutting down the commissioner");
+
+    PersistDeviceList();
+
+    if (mRendezvousSession != nullptr)
+    {
+        chip::Platform::Delete(mRendezvousSession);
+        mRendezvousSession = nullptr;
+    }
+
+    DeviceController::Shutdown();
+
+exit:
+    return err;
+}
+
+CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & params, uint16_t remotePort,
+                                          Inet::InterfaceId interfaceId)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mDeviceBeingPaired == kNumMaxActiveDevices, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mStorageDelegate != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+#if CONFIG_DEVICE_LAYER && CONFIG_NETWORK_LAYER_BLE
+    if (!params.HasBleLayer())
+    {
+        params.SetBleLayer(DeviceLayer::ConnectivityMgr().GetBleLayer());
+    }
+#endif // CONFIG_DEVICE_LAYER && CONFIG_NETWORK_LAYER_BLE
+
+    mDeviceBeingPaired = GetInactiveDeviceIndex();
+    VerifyOrExit(mDeviceBeingPaired < kNumMaxActiveDevices, err = CHIP_ERROR_NO_MEMORY);
+    device = &mActiveDevices[mDeviceBeingPaired];
+
+    mRendezvousSession = chip::Platform::New<RendezvousSession>(this);
+    VerifyOrExit(mRendezvousSession != nullptr, err = CHIP_ERROR_NO_MEMORY);
+    err = mRendezvousSession->Init(params.SetLocalNodeId(mLocalDeviceId));
+    SuccessOrExit(err);
+
+    device->Init(mSessionManager, mInetLayer, remoteDeviceId, remotePort, interfaceId);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        if (mRendezvousSession != nullptr)
+        {
+            chip::Platform::Delete(mRendezvousSession);
+            mRendezvousSession = nullptr;
+        }
+
+        if (device != nullptr)
+        {
+            ReleaseDevice(device);
+            mDeviceBeingPaired = kNumMaxActiveDevices;
+        }
+    }
+
+    return err;
+}
+
+CHIP_ERROR DeviceCommissioner::PairTestDeviceWithoutSecurity(NodeId remoteDeviceId, const Inet::IPAddress & deviceAddr,
+                                                             SerializedDevice & serialized, uint16_t remotePort,
+                                                             Inet::InterfaceId interfaceId)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+
+    SecurePairingUsingTestSecret * testSecurePairingSecret = nullptr;
+
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mDeviceBeingPaired == kNumMaxActiveDevices, err = CHIP_ERROR_INCORRECT_STATE);
+
+    testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>(Optional<NodeId>::Value(remoteDeviceId),
+                                                                                static_cast<uint16_t>(0), static_cast<uint16_t>(0));
+    VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY);
+
+    mDeviceBeingPaired = GetInactiveDeviceIndex();
+    VerifyOrExit(mDeviceBeingPaired < kNumMaxActiveDevices, err = CHIP_ERROR_NO_MEMORY);
+    device = &mActiveDevices[mDeviceBeingPaired];
+
+    testSecurePairingSecret->ToSerializable(device->GetPairing());
+
+    device->Init(mSessionManager, mInetLayer, remoteDeviceId, remotePort, interfaceId);
+
+    device->SetAddress(deviceAddr);
+
+    device->Serialize(serialized);
+
+    OnRendezvousComplete();
+
+exit:
+    if (testSecurePairingSecret != nullptr)
+    {
+        chip::Platform::Delete(testSecurePairingSecret);
+    }
+
+    if (err != CHIP_NO_ERROR)
+    {
+        if (device != nullptr)
+        {
+            ReleaseDevice(device);
+            mDeviceBeingPaired = kNumMaxActiveDevices;
+        }
+    }
+
+    return err;
+}
+CHIP_ERROR DeviceCommissioner::StopPairing(NodeId remoteDeviceId)
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+
+    VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mDeviceBeingPaired < kNumMaxActiveDevices, err = CHIP_ERROR_INCORRECT_STATE);
+
+    device = &mActiveDevices[mDeviceBeingPaired];
+    VerifyOrExit(device->GetDeviceId() == remoteDeviceId, err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
+
+    if (mRendezvousSession != nullptr)
+    {
+        chip::Platform::Delete(mRendezvousSession);
+        mRendezvousSession = nullptr;
+    }
+
+    ReleaseDevice(device);
+    mDeviceBeingPaired = kNumMaxActiveDevices;
+
+exit:
+    return err;
+}
+
+CHIP_ERROR DeviceCommissioner::UnpairDevice(NodeId remoteDeviceId)
+{
+    // TODO: Send unpairing message to the remote device.
+
+    if (mStorageDelegate != nullptr)
+    {
+        PERSISTENT_KEY_OP(remoteDeviceId, kPairedDeviceKeyPrefix, key, mStorageDelegate->DeleteKeyValue(key));
+        mPairedDevices.Remove(remoteDeviceId);
+        mPairedDevicesUpdated = true;
+    }
+
     return CHIP_NO_ERROR;
 }
 
-void ChipDeviceController::ClearRequestState()
-{
-    if (mCurReqMsg != nullptr)
-    {
-        PacketBuffer::Free(mCurReqMsg);
-        mCurReqMsg = nullptr;
-    }
-}
-
-void ChipDeviceController::OnNewConnection(Transport::PeerConnectionState * state, SecureSessionMgrBase * mgr) {}
-
-void ChipDeviceController::OnAddressResolved(CHIP_ERROR error, NodeId nodeId, SecureSessionMgrBase * mgr)
-{
-    if (error == CHIP_NO_ERROR && nodeId == mSecurePairingSession->GetPeerNodeId() && mDeviceAddr == IPAddress::Any)
-    {
-        SendCachedPackets();
-    }
-}
-
-void ChipDeviceController::OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader,
-                                             Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf,
-                                             SecureSessionMgrBase * mgr)
-{
-    if (header.GetSourceNodeId().HasValue())
-    {
-        if (!mRemoteDeviceId.HasValue())
-        {
-            ChipLogDetail(Controller, "Learned remote device id");
-            mRemoteDeviceId = header.GetSourceNodeId();
-        }
-        else if (mRemoteDeviceId != header.GetSourceNodeId())
-        {
-            ChipLogError(Controller, "Received message from an unexpected source node id.");
-        }
-    }
-    if (IsSecurelyConnected() && mOnComplete.Response != nullptr)
-    {
-        mOnComplete.Response(this, mAppReqState, msgBuf);
-    }
-}
-
-void ChipDeviceController::OnRendezvousError(CHIP_ERROR err)
+void DeviceCommissioner::RendezvousCleanup(CHIP_ERROR status)
 {
     if (mRendezvousSession != nullptr)
     {
@@ -659,74 +586,130 @@
 
     if (mPairingDelegate != nullptr)
     {
-        mPairingDelegate->OnPairingComplete(err);
+        mPairingDelegate->OnPairingComplete(status);
     }
-}
 
-void ChipDeviceController::OnRendezvousComplete()
-{
-    OnRendezvousError(CHIP_NO_ERROR);
-}
-
-void ChipDeviceController::OnRendezvousStatusUpdate(RendezvousSessionDelegate::Status status, CHIP_ERROR err)
-{
-    if (mOnError != nullptr && err != CHIP_NO_ERROR)
+    if (mDeviceBeingPaired != kNumMaxActiveDevices)
     {
-        mOnError(this, mAppReqState, err, nullptr);
+        // Let's release the device that's being paired.
+        // If pairing was successful, its information is
+        // already persisted. The application will use GetDevice()
+        // method to get access to the device, which will fetch
+        // the device information from the persistent storage.
+        DeviceController::ReleaseDevice(mDeviceBeingPaired);
+        mDeviceBeingPaired = kNumMaxActiveDevices;
+    }
+}
+
+void DeviceCommissioner::OnRendezvousError(CHIP_ERROR err)
+{
+    RendezvousCleanup(err);
+}
+
+void DeviceCommissioner::OnRendezvousComplete()
+{
+    CHIP_ERROR err  = CHIP_NO_ERROR;
+    Device * device = nullptr;
+
+    VerifyOrExit(mDeviceBeingPaired < kNumMaxActiveDevices, err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
+    device = &mActiveDevices[mDeviceBeingPaired];
+    mPairedDevices.Insert(device->GetDeviceId());
+    mPairedDevicesUpdated = true;
+
+    // mStorageDelegate would not be null for a typical pairing scenario, as Pair()
+    // requires a valid storage delegate. However, test pairing usecase, that's used
+    // mainly by test applications, do not require a storage delegate. This is to
+    // reduce overheads on these tests.
+    // Let's make sure the delegate object is available before calling into it.
+    if (mStorageDelegate != nullptr)
+    {
+        SerializedDevice serialized;
+        device->Serialize(serialized);
+        PERSISTENT_KEY_OP(device->GetDeviceId(), kPairedDeviceKeyPrefix, key,
+                          mStorageDelegate->SetKeyValue(key, Uint8::to_const_char(serialized.inner)));
     }
 
+exit:
+    if (err == CHIP_NO_ERROR)
+    {
+        RendezvousCleanup(CHIP_NO_ERROR);
+    }
+    else
+    {
+        OnRendezvousError(err);
+    }
+}
+
+void DeviceCommissioner::OnRendezvousStatusUpdate(RendezvousSessionDelegate::Status status, CHIP_ERROR err)
+{
+    Device * device = nullptr;
+    if (mDeviceBeingPaired >= kNumMaxActiveDevices)
+    {
+        ExitNow();
+    }
+
+    device = &mActiveDevices[mDeviceBeingPaired];
     switch (status)
     {
     case RendezvousSessionDelegate::SecurePairingSuccess:
-        ChipLogProgress(Controller, "Remote device completed SPAKE2+ handshake\n");
-        mPairingSession       = mRendezvousSession->GetPairingSession();
-        mSecurePairingSession = &mPairingSession;
-
-        if (mOnNewConnection)
-        {
-            ChipLogProgress(Controller, "Will Call on mOnNewConnection(%p)\n", mOnNewConnection);
-            mOnNewConnection(this, nullptr, mAppReqState);
-        }
+        ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake\n");
+        mRendezvousSession->GetPairingSession().ToSerializable(device->GetPairing());
 
         if (mPairingDelegate != nullptr)
         {
             mPairingDelegate->OnNetworkCredentialsRequested(mRendezvousSession);
         }
+        break;
 
-        if (mStorageDelegate != nullptr)
-        {
-            SecurePairingSessionSerialized serialized;
-            CHIP_ERROR err = mSecurePairingSession->Serialize(serialized);
-            if (err == CHIP_NO_ERROR)
-            {
-                PERSISTENT_KEY_OP(mSecurePairingSession->GetPeerNodeId(), kDeviceCredentialsKeyPrefix, key,
-                                  mStorageDelegate->SetKeyValue(key, Uint8::to_const_char(serialized.inner)));
-            }
-        }
+    case RendezvousSessionDelegate::SecurePairingFailed:
+        ChipLogDetail(Controller, "Remote device failed in SPAKE2+ handshake\n");
         break;
 
     case RendezvousSessionDelegate::NetworkProvisioningSuccess:
-
         ChipLogDetail(Controller, "Remote device was assigned an ip address\n");
-        mDeviceAddr = mRendezvousSession->GetIPAddress();
-        if (mStorageDelegate != nullptr)
-        {
-            char addrStr[INET6_ADDRSTRLEN];
-            mDeviceAddr.ToString(addrStr, INET6_ADDRSTRLEN);
-            PERSISTENT_KEY_OP(mRendezvousSession->GetPairingSession().GetPeerNodeId(), kDeviceAddressKeyPrefix, key,
-                              mStorageDelegate->SetKeyValue(key, addrStr));
-        }
+        device->SetAddress(mRendezvousSession->GetIPAddress());
+        break;
+
+    case RendezvousSessionDelegate::NetworkProvisioningFailed:
+        ChipLogDetail(Controller, "Remote device failed in network provisioning\n");
         break;
 
     default:
         break;
     };
-
+exit:
     if (mPairingDelegate != nullptr)
     {
         mPairingDelegate->OnStatusUpdate(status);
     }
 }
 
-} // namespace DeviceController
+void DeviceCommissioner::PersistDeviceList()
+{
+    if (mStorageDelegate != nullptr && mPairedDevicesUpdated)
+    {
+        constexpr uint16_t size = CHIP_MAX_SERIALIZED_SIZE_U64(kNumMaxPairedDevices);
+        char * serialized       = static_cast<char *>(chip::Platform::MemoryAlloc(size));
+        uint16_t requiredSize   = size;
+        if (serialized != nullptr)
+        {
+            const char * value = mPairedDevices.SerializeBase64(serialized, requiredSize);
+            if (value != nullptr && requiredSize <= size)
+            {
+                PERSISTENT_KEY_OP(static_cast<uint64_t>(0), kPairedDeviceListKeyPrefix, key,
+                                  mStorageDelegate->SetKeyValue(key, value));
+                mPairedDevicesUpdated = false;
+            }
+            chip::Platform::MemoryFree(serialized);
+        }
+    }
+}
+
+void DeviceCommissioner::ReleaseDevice(Device * device)
+{
+    PersistDeviceList();
+    DeviceController::ReleaseDevice(device);
+}
+
+} // namespace Controller
 } // namespace chip
diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h
index a115288..1e7ec69 100644
--- a/src/controller/CHIPDeviceController.h
+++ b/src/controller/CHIPDeviceController.h
@@ -28,28 +28,23 @@
 
 #pragma once
 
+#include <controller/CHIPDevice.h>
 #include <controller/CHIPPersistentStorageDelegate.h>
 #include <core/CHIPCore.h>
 #include <core/CHIPTLV.h>
 #include <support/DLLUtil.h>
+#include <support/SerializableIntegerSet.h>
 #include <transport/RendezvousSession.h>
 #include <transport/RendezvousSessionDelegate.h>
 #include <transport/SecureSessionMgr.h>
 #include <transport/raw/UDP.h>
 
 namespace chip {
-namespace DeviceController {
 
-constexpr uint16_t kPacketCacheMaxSize = 16;
+namespace Controller {
 
-class ChipDeviceController;
-
-typedef void (*NewConnectionHandler)(ChipDeviceController * deviceController, Transport::PeerConnectionState * state,
-                                     void * appReqState);
-typedef void (*CompleteHandler)(ChipDeviceController * deviceController, void * appReqState);
-typedef void (*ErrorHandler)(ChipDeviceController * deviceController, void * appReqState, CHIP_ERROR err,
-                             const Inet::IPPacketInfo * pktInfo);
-typedef void (*MessageReceiveHandler)(ChipDeviceController * deviceController, void * appReqState, System::PacketBuffer * payload);
+constexpr uint16_t kNumMaxActiveDevices = 64;
+constexpr uint16_t kNumMaxPairedDevices = 128;
 
 class DLL_EXPORT DevicePairingDelegate
 {
@@ -100,117 +95,57 @@
     virtual void OnPairingDeleted(CHIP_ERROR error) {}
 };
 
-class DLL_EXPORT ChipDeviceController : public SecureSessionMgrDelegate,
-                                        public RendezvousSessionDelegate,
-                                        public PersistentStorageResultDelegate
+/**
+ * @brief
+ *   Controller applications can use this class to communicate with already paired CHIP devices. The
+ *   application is required to provide access to the persistent storage, where the paired device information
+ *   is stored. This object of this class can be initialized with the data from the storage (List of devices,
+ *   and device pairing information for individual devices). Alternatively, this class can retrieve the
+ *   relevant information when the application tries to communicate with the device
+ */
+class DLL_EXPORT DeviceController : public SecureSessionMgrDelegate, public PersistentStorageResultDelegate
 {
-    friend class ChipDeviceControllerCallback;
-
 public:
-    ChipDeviceController();
-    ~ChipDeviceController();
-
-    void * AppState;
+    DeviceController();
+    virtual ~DeviceController() {}
 
     /**
      * Init function to be used when there exists a device layer that takes care of initializing
      * System::Layer and InetLayer.
      */
-    CHIP_ERROR Init(NodeId localDeviceId, DevicePairingDelegate * pairingDelegate = nullptr,
-                    PersistentStorageDelegate * storageDelegate = nullptr);
-    /**
-     * Init function to be used when already-initialized System::Layer and InetLayer are available.
-     */
-    CHIP_ERROR Init(NodeId localDeviceId, System::Layer * systemLayer, Inet::InetLayer * inetLayer,
-                    DevicePairingDelegate * pairingDelegate = nullptr, PersistentStorageDelegate * storageDelegate = nullptr);
-    CHIP_ERROR Shutdown();
+    CHIP_ERROR Init(NodeId localDeviceId, PersistentStorageDelegate * storageDelegate = nullptr,
+                    System::Layer * systemLayer = nullptr, Inet::InetLayer * inetLayer = nullptr);
 
-    CHIP_ERROR SetUdpListenPort(uint16_t listenPort);
-
-    // ----- Connection Management -----
-    /**
-     * @brief
-     *   Connect to a CHIP device with the provided Rendezvous connection parameters
-     *
-     * @param[in] remoteDeviceId        The remote device Id.
-     * @param[in] params                The Rendezvous connection parameters
-     * @param[in] appReqState           Application specific context to be passed back when a message is received or on error
-     * @param[in] onConnected           Callback for when the connection is established
-     * @param[in] onMessageReceived     Callback for when a message is received
-     * @param[in] onError               Callback for when an error occurs
-     * @param[in] devicePort            [Optional] The CHIP Device's port, defaults to CHIP_PORT
-     * @param[in] interfaceId           [Optional] The interface indicator to use
-     *
-     * @return CHIP_ERROR               The connection status
-     */
-    CHIP_ERROR ConnectDevice(NodeId remoteDeviceId, RendezvousParameters & params, void * appReqState,
-                             NewConnectionHandler onConnected, MessageReceiveHandler onMessageReceived, ErrorHandler onError,
-                             uint16_t devicePort = CHIP_PORT, Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+    virtual CHIP_ERROR Shutdown();
 
     /**
      * @brief
-     *   Connect to a CHIP device at a given address and an optional port. This is a test only API
-     *   that bypasses Rendezvous and Secure Pairing process.
+     *   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] remoteDeviceId        The remote device Id.
-     * @param[in] deviceAddr            The IPAddress of the requested Device
-     * @param[in] appReqState           Application specific context to be passed back when a message is received or on error
-     * @param[in] onConnected           Callback for when the connection is established
-     * @param[in] onMessageReceived     Callback for when a message is received
-     * @param[in] onError               Callback for when an error occurs
-     * @param[in] devicePort            [Optional] The CHIP Device's port, defaults to CHIP_PORT
-     * @param[in] interfaceId           [Optional] The interface indicator to use
-     * @return CHIP_ERROR           The connection status
+     * @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.
      */
-    [[deprecated("Available until Rendezvous is implemented")]] CHIP_ERROR
-    ConnectDeviceWithoutSecurePairing(NodeId remoteDeviceId, const Inet::IPAddress & deviceAddr, void * appReqState,
-                                      NewConnectionHandler onConnected, MessageReceiveHandler onMessageReceived,
-                                      ErrorHandler onError, uint16_t devicePort = CHIP_PORT,
-                                      Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+    CHIP_ERROR GetDevice(NodeId deviceId, const SerializedDevice & deviceInfo, Device ** device);
 
     /**
      * @brief
-     *   Disconnect from a connected device
+     *   This function is similar to the other GetDevice object, except it reads the serialized object from
+     *   the persistent storage.
      *
-     * @return CHIP_ERROR   If the device was disconnected successfully
+     * @param[in] deviceId   Node ID for the CHIP device
+     * @param[out] device    The output device object
+     *
+     * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
      */
-    CHIP_ERROR DisconnectDevice();
+    CHIP_ERROR GetDevice(NodeId deviceId, Device ** device);
 
-    /**
-     * @brief
-     *   Check if there's an active connection
-     *
-     * @return bool   If there is an active connection
-     */
-    bool IsConnected() const;
-
-    /**
-     * @brief
-     *   Check if the connection is active and security context is established
-     *
-     * @return bool   If the connection is active and security context is established
-     */
-    bool IsSecurelyConnected() const;
-
-    /**
-     * @brief
-     *   Get IP Address of the peer if the connection is active
-     *
-     * @return bool   If IP Address was returned
-     */
-    bool GetIpAddress(Inet::IPAddress & addr) const;
-
-    // ----- Messaging -----
-    /**
-     * @brief
-     *   Send a message to a connected CHIP device
-     *
-     * @param[in] appReqState   Application specific context to be passed back when a message is received or on error
-     * @param[in] buffer        The Data Buffer to trasmit to the device
-     * @param[in] peerDevice    Device ID of the peer device
-     * @return CHIP_ERROR   The return status
-     */
-    CHIP_ERROR SendMessage(void * appReqState, System::PacketBuffer * buffer, NodeId peerDevice = kUndefinedNodeId);
+    virtual void ReleaseDevice(Device * device);
 
     // ----- IO -----
     /**
@@ -220,14 +155,6 @@
      */
     CHIP_ERROR ServiceEvents();
 
-    // ----- Pairing -----
-    /**
-     * @brief
-     * Set device pairing delegate after init, pass nullptr remove device delegate.
-     * @return CHIP_ERROR   The return status
-     */
-    CHIP_ERROR SetDevicePairingDelegate(DevicePairingDelegate * pairingDelegate);
-
     /**
      * @brief
      *   Allow the CHIP Stack to process any pending events
@@ -236,86 +163,142 @@
      */
     CHIP_ERROR ServiceEventSignal();
 
+protected:
+    enum class State
+    {
+        NotInitialized,
+        Initialized
+    };
+
+    State mState;
+
+    /* A list of device objects that can be used for communicating with corresponding
+       CHIP devices. The list does not contain all the paired devices, but only the ones
+       which the controller application is currently accessing.
+    */
+    Device mActiveDevices[kNumMaxActiveDevices];
+
+    SerializableU64Set<kNumMaxPairedDevices> mPairedDevices;
+    bool mPairedDevicesInitialized;
+
+    NodeId mLocalDeviceId;
+    SecureSessionMgr<Transport::UDP> * mSessionManager;
+    PersistentStorageDelegate * mStorageDelegate;
+    Inet::InetLayer * mInetLayer;
+
+    uint16_t GetInactiveDeviceIndex();
+    uint16_t FindDeviceIndex(NodeId id);
+    void ReleaseDevice(uint16_t index);
+    CHIP_ERROR SetPairedDeviceList(const char * pairedDeviceSerializedSet);
+
+private:
+    //////////// SecureSessionMgrDelegate Implementation ///////////////
     void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, Transport::PeerConnectionState * state,
                            System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr) override;
 
     void OnNewConnection(Transport::PeerConnectionState * state, SecureSessionMgrBase * mgr) override;
 
-    void OnAddressResolved(CHIP_ERROR error, NodeId nodeId, SecureSessionMgrBase * mgr) override;
+    //////////// PersistentStorageResultDelegate Implementation ///////////////
+    void OnValue(const char * key, const char * value) override;
+    void OnStatus(const char * key, Operation op, CHIP_ERROR err) override;
+
+    System::Layer * mSystemLayer;
+};
+
+/**
+ * @brief
+ *   The commissioner applications can use this class to pair new/unpaired CHIP devices. The application is
+ *   required to provide write access to the persistent storage, where the paired device information
+ *   will be stored.
+ */
+class DLL_EXPORT DeviceCommissioner : public DeviceController, public RendezvousSessionDelegate
+{
+public:
+    DeviceCommissioner();
+    ~DeviceCommissioner() {}
+
+    /**
+     * Init function to be used when there exists a device layer that takes care of initializing
+     * System::Layer and InetLayer.
+     */
+    CHIP_ERROR Init(NodeId localDeviceId, PersistentStorageDelegate * storageDelegate = nullptr,
+                    DevicePairingDelegate * pairingDelegate = nullptr, System::Layer * systemLayer = nullptr,
+                    Inet::InetLayer * inetLayer = nullptr);
+
+    void SetDevicePairingDelegate(DevicePairingDelegate * pairingDelegate) { mPairingDelegate = pairingDelegate; }
+
+    CHIP_ERROR Shutdown() override;
+
+    // ----- Connection Management -----
+    /**
+     * @brief
+     *   Pair a CHIP device with the provided Rendezvous connection parameters.
+     *   Use registered DevicePairingDelegate object to receive notifications on
+     *   pairing status updates.
+     *
+     *   Note: Pairing process requires that the caller has registered PersistentStorageDelegate
+     *         in the Init() call.
+     *
+     * @param[in] remoteDeviceId        The remote device Id.
+     * @param[in] params                The Rendezvous connection parameters
+     * @param[in] devicePort            [Optional] The CHIP Device's port, defaults to CHIP_PORT
+     * @param[in] interfaceId           [Optional] The local inet interface to use to communicate with the device.
+     *
+     * @return CHIP_ERROR               The connection status
+     */
+    CHIP_ERROR PairDevice(NodeId remoteDeviceId, RendezvousParameters & params, uint16_t devicePort = CHIP_PORT,
+                          Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+
+    [[deprecated("Available until Rendezvous is implemented")]] CHIP_ERROR
+    PairTestDeviceWithoutSecurity(NodeId remoteDeviceId, const Inet::IPAddress & deviceAddr, SerializedDevice & serialized,
+                                  uint16_t devicePort = CHIP_PORT, Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+
+    /**
+     * @brief
+     *   This function stops a pairing process that's in progress. It does not delete the pairing of a previously
+     *   paired device.
+     *
+     * @param[in] remoteDeviceId        The remote device Id.
+     *
+     * @return CHIP_ERROR               CHIP_NO_ERROR on success, or corresponding error
+     */
+    CHIP_ERROR StopPairing(NodeId remoteDeviceId);
+
+    /**
+     * @brief
+     *   Remove pairing for a paired device. If the device is currently being paired, it'll stop the pairing process.
+     *
+     * @param[in] remoteDeviceId        The remote device Id.
+     *
+     * @return CHIP_ERROR               CHIP_NO_ERROR on success, or corresponding error
+     */
+    CHIP_ERROR UnpairDevice(NodeId remoteDeviceId);
 
     //////////// RendezvousSessionDelegate Implementation ///////////////
     void OnRendezvousError(CHIP_ERROR err) override;
     void OnRendezvousComplete() override;
     void OnRendezvousStatusUpdate(RendezvousSessionDelegate::Status status, CHIP_ERROR err) override;
 
-    //////////// PersistentStorageResultDelegate Implementation ///////////////
-    void OnValue(const char * key, const char * value) override;
-    void OnStatus(const char * key, Operation op, CHIP_ERROR err) override;
+    void RendezvousCleanup(CHIP_ERROR status);
+
+    void ReleaseDevice(Device * device) override;
 
 private:
-    enum
-    {
-        kState_NotInitialized = 0,
-        kState_Initialized    = 1
-    } mState;
-
-    enum ConnectionState
-    {
-        kConnectionState_NotConnected     = 0,
-        kConnectionState_Connected        = 1,
-        kConnectionState_SecureConnecting = 2,
-        kConnectionState_SecureConnected  = 3,
-    };
-
-    System::Layer * mSystemLayer;
-    Inet::InetLayer * mInetLayer;
-
-    SecureSessionMgr<Transport::UDP> * mSessionManager;
+    DevicePairingDelegate * mPairingDelegate;
     RendezvousSession * mRendezvousSession;
 
-    ConnectionState mConState;
-    void * mAppReqState;
+    /* This field is an index in mActiveDevices list. The object at this index in the list
+       contains the device object that's tracking the state of the device that's being paired.
+       If no device is currently being paired, this value will be kNumMaxPairedDevices.  */
+    uint16_t mDeviceBeingPaired;
 
-    union
-    {
-        CompleteHandler General;
-        MessageReceiveHandler Response;
-    } mOnComplete;
+    /* This field is true when device pairing information changes, e.g. a new device is paired, or
+       the pairing for a device is removed. The DeviceCommissioner uses this to decide when to
+       persist the device list */
+    bool mPairedDevicesUpdated;
 
-    ErrorHandler mOnError;
-    NewConnectionHandler mOnNewConnection;
-    System::PacketBuffer * mCurReqMsg;
-
-    NodeId mLocalDeviceId;
-    uint16_t mListenPort;
-    Inet::IPAddress mDeviceAddr;
-    uint16_t mDevicePort;
-    Inet::InterfaceId mInterface;
-    Optional<NodeId> mRemoteDeviceId;
-
-    SecurePairingSession mPairingSession;
-    SecurePairingUsingTestSecret * mTestSecurePairingSecret = nullptr;
-
-    SecurePairingSession * mSecurePairingSession = nullptr;
-
-    DevicePairingDelegate * mPairingDelegate = nullptr;
-
-    PersistentStorageDelegate * mStorageDelegate;
-
-    System::PacketBuffer * mCachedPackets[kPacketCacheMaxSize];
-    uint16_t mNumCachedPackets;
-
-    void ClearRequestState();
-    void ClearOpState();
-
-    CHIP_ERROR EstablishSecureSession();
-    CHIP_ERROR TryEstablishingSecureSession(NodeId peer);
-    CHIP_ERROR ResumeSecureSession(NodeId peer);
-
-    CHIP_ERROR CachePacket(System::PacketBuffer * buffer);
-    CHIP_ERROR SendCachedPackets();
-    void DiscardCachedPackets();
+    void PersistDeviceList();
 };
 
-} // namespace DeviceController
+} // namespace Controller
 } // namespace chip
diff --git a/src/controller/CHIPDeviceController_deprecated.cpp b/src/controller/CHIPDeviceController_deprecated.cpp
new file mode 100644
index 0000000..eaaa237
--- /dev/null
+++ b/src/controller/CHIPDeviceController_deprecated.cpp
@@ -0,0 +1,239 @@
+/*
+ *
+ *    Copyright (c) 2020 Project CHIP Authors
+ *    Copyright (c) 2013-2017 Nest Labs, Inc.
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      Implementation of CHIP Device Controller, a common class
+ *      that implements discovery, pairing and provisioning of CHIP
+ *      devices.
+ *
+ */
+
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+// module header, comes first
+#include <controller/CHIPDeviceController_deprecated.h>
+
+#if CONFIG_DEVICE_LAYER
+#include <platform/CHIPDeviceLayer.h>
+#endif
+
+#include <core/CHIPCore.h>
+#include <core/CHIPEncoding.h>
+#include <core/CHIPSafeCasts.h>
+#include <support/Base64.h>
+#include <support/CHIPMem.h>
+#include <support/CodeUtils.h>
+#include <support/ErrorStr.h>
+#include <support/TimeUtils.h>
+#include <support/logging/CHIPLogging.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+using namespace chip::Inet;
+using namespace chip::System;
+using namespace chip::Controller;
+
+namespace chip {
+namespace DeviceController {
+
+using namespace chip::Encoding;
+
+ChipDeviceController::ChipDeviceController()
+{
+    mState                  = kState_NotInitialized;
+    AppState                = nullptr;
+    mCurReqMsg              = nullptr;
+    mOnError                = nullptr;
+    mOnNewConnection        = nullptr;
+    mListenPort             = CHIP_PORT;
+    mLocalDeviceId          = kUndefinedNodeId;
+    mRemoteDeviceId         = kUndefinedNodeId;
+    mDevice                 = nullptr;
+    mPairingWithoutSecurity = false;
+    CHIP_ZERO_AT(mOnComplete);
+}
+
+ChipDeviceController::~ChipDeviceController() {}
+
+CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, DevicePairingDelegate * pairingDelegate,
+                                      PersistentStorageDelegate * storageDelegate)
+{
+    return mCommissioner.Init(localNodeId, storageDelegate, pairingDelegate);
+}
+
+CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, System::Layer * systemLayer, InetLayer * inetLayer,
+                                      DevicePairingDelegate * pairingDelegate, PersistentStorageDelegate * storageDelegate)
+{
+    return mCommissioner.Init(localNodeId, storageDelegate, pairingDelegate, systemLayer, inetLayer);
+}
+
+CHIP_ERROR ChipDeviceController::Shutdown()
+{
+    return mCommissioner.Shutdown();
+}
+
+CHIP_ERROR ChipDeviceController::ConnectDevice(NodeId remoteDeviceId, RendezvousParameters & params, void * appReqState,
+                                               NewConnectionHandler onConnected, MessageReceiveHandler onMessageReceived,
+                                               ErrorHandler onError, uint16_t devicePort, Inet::InterfaceId interfaceId)
+{
+    CHIP_ERROR err = mCommissioner.PairDevice(remoteDeviceId, params, devicePort, interfaceId);
+    SuccessOrExit(err);
+
+    mRemoteDeviceId  = remoteDeviceId;
+    mAppReqState     = appReqState;
+    mOnNewConnection = onConnected;
+
+    mOnComplete.Response = onMessageReceived;
+    mOnError             = onError;
+
+exit:
+    return err;
+}
+
+CHIP_ERROR ChipDeviceController::ConnectDeviceWithoutSecurePairing(NodeId remoteDeviceId, const IPAddress & deviceAddr,
+                                                                   void * appReqState, NewConnectionHandler onConnected,
+                                                                   MessageReceiveHandler onMessageReceived, ErrorHandler onError,
+                                                                   uint16_t devicePort, Inet::InterfaceId interfaceId)
+{
+    CHIP_ERROR err =
+        mCommissioner.PairTestDeviceWithoutSecurity(remoteDeviceId, deviceAddr, mSerializedTestDevice, devicePort, interfaceId);
+    SuccessOrExit(err);
+
+    mPairingWithoutSecurity = true;
+
+    mRemoteDeviceId  = remoteDeviceId;
+    mAppReqState     = appReqState;
+    mOnNewConnection = onConnected;
+
+    mOnComplete.Response = onMessageReceived;
+    mOnError             = onError;
+
+    if (mOnNewConnection)
+    {
+        mOnNewConnection(this, nullptr, mAppReqState);
+    }
+
+exit:
+    return err;
+}
+
+CHIP_ERROR ChipDeviceController::SetUdpListenPort(uint16_t listenPort)
+{
+    if (mState != kState_Initialized)
+        return CHIP_ERROR_INCORRECT_STATE;
+    mListenPort = listenPort;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ChipDeviceController::ServiceEvents()
+{
+    return mCommissioner.ServiceEvents();
+}
+
+CHIP_ERROR ChipDeviceController::ServiceEventSignal()
+{
+    return mCommissioner.ServiceEventSignal();
+}
+
+bool ChipDeviceController::IsConnected() const
+{
+    return mState == kState_Initialized;
+}
+
+bool ChipDeviceController::GetIpAddress(Inet::IPAddress & addr) const
+{
+    if (IsConnected() && mDevice != nullptr)
+        return mDevice->GetIpAddress(addr);
+
+    return false;
+}
+
+CHIP_ERROR ChipDeviceController::DisconnectDevice()
+{
+    if (mDevice != nullptr)
+    {
+        mCommissioner.ReleaseDevice(mDevice);
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ChipDeviceController::SendMessage(void * appReqState, PacketBuffer * buffer, NodeId peerDevice)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+    mAppReqState = appReqState;
+
+    if (peerDevice != kUndefinedNodeId)
+    {
+        mRemoteDeviceId = peerDevice;
+    }
+    VerifyOrExit(mRemoteDeviceId != kUndefinedNodeId, err = CHIP_ERROR_INCORRECT_STATE);
+
+    if (mDevice == nullptr)
+    {
+        if (mPairingWithoutSecurity)
+        {
+            err = mCommissioner.GetDevice(mRemoteDeviceId, mSerializedTestDevice, &mDevice);
+        }
+        else
+        {
+            err = mCommissioner.GetDevice(mRemoteDeviceId, &mDevice);
+        }
+        SuccessOrExit(err);
+    }
+
+    VerifyOrExit(mDevice != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    mDevice->SetDelegate(this);
+
+    err = mDevice->SendMessage(buffer);
+
+exit:
+
+    return err;
+}
+
+CHIP_ERROR ChipDeviceController::SetDevicePairingDelegate(DevicePairingDelegate * pairingDelegate)
+{
+    mCommissioner.SetDevicePairingDelegate(pairingDelegate);
+    return CHIP_NO_ERROR;
+}
+
+void ChipDeviceController::OnMessage(System::PacketBuffer * msgBuf)
+{
+    if (mOnComplete.Response != nullptr)
+    {
+        mOnComplete.Response(this, mAppReqState, msgBuf);
+    }
+}
+
+} // namespace DeviceController
+} // namespace chip
diff --git a/src/controller/CHIPDeviceController_deprecated.h b/src/controller/CHIPDeviceController_deprecated.h
new file mode 100644
index 0000000..d1bf836
--- /dev/null
+++ b/src/controller/CHIPDeviceController_deprecated.h
@@ -0,0 +1,220 @@
+/*
+ *
+ *    Copyright (c) 2020 Project CHIP Authors
+ *    Copyright (c) 2013-2017 Nest Labs, Inc.
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      Declaration of CHIP Device Controller, a common class
+ *      that implements connecting and messaging and will later
+ *      be expanded to support discovery, pairing and
+ *      provisioning of CHIP  devices.
+ *
+ */
+
+#pragma once
+
+#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPPersistentStorageDelegate.h>
+#include <core/CHIPCore.h>
+#include <core/CHIPTLV.h>
+#include <support/DLLUtil.h>
+#include <transport/RendezvousSession.h>
+#include <transport/RendezvousSessionDelegate.h>
+#include <transport/SecureSessionMgr.h>
+#include <transport/raw/UDP.h>
+
+namespace chip {
+namespace DeviceController {
+
+constexpr uint16_t kPacketCacheMaxSize = 16;
+
+class ChipDeviceController;
+
+typedef void (*NewConnectionHandler)(ChipDeviceController * deviceController, Transport::PeerConnectionState * state,
+                                     void * appReqState);
+typedef void (*CompleteHandler)(ChipDeviceController * deviceController, void * appReqState);
+typedef void (*ErrorHandler)(ChipDeviceController * deviceController, void * appReqState, CHIP_ERROR err,
+                             const Inet::IPPacketInfo * pktInfo);
+typedef void (*MessageReceiveHandler)(ChipDeviceController * deviceController, void * appReqState, System::PacketBuffer * payload);
+
+class DLL_EXPORT ChipDeviceController : public Controller::DeviceStatusDelegate
+{
+    friend class ChipDeviceControllerCallback;
+
+public:
+    ChipDeviceController();
+    ~ChipDeviceController();
+
+    void * AppState;
+
+    /**
+     * Init function to be used when there exists a device layer that takes care of initializing
+     * System::Layer and InetLayer.
+     */
+    CHIP_ERROR Init(NodeId localDeviceId, Controller::DevicePairingDelegate * pairingDelegate = nullptr,
+                    Controller::PersistentStorageDelegate * storageDelegate = nullptr);
+    /**
+     * Init function to be used when already-initialized System::Layer and InetLayer are available.
+     */
+    CHIP_ERROR Init(NodeId localDeviceId, System::Layer * systemLayer, Inet::InetLayer * inetLayer,
+                    Controller::DevicePairingDelegate * pairingDelegate     = nullptr,
+                    Controller::PersistentStorageDelegate * storageDelegate = nullptr);
+    CHIP_ERROR Shutdown();
+
+    CHIP_ERROR SetUdpListenPort(uint16_t listenPort);
+
+    // ----- Connection Management -----
+    /**
+     * @brief
+     *   Connect to a CHIP device with the provided Rendezvous connection parameters
+     *
+     * @param[in] remoteDeviceId        The remote device Id.
+     * @param[in] params                The Rendezvous connection parameters
+     * @param[in] appReqState           Application specific context to be passed back when a message is received or on error
+     * @param[in] onConnected           Callback for when the connection is established
+     * @param[in] onMessageReceived     Callback for when a message is received
+     * @param[in] onError               Callback for when an error occurs
+     * @param[in] devicePort            [Optional] The CHIP Device's port, defaults to CHIP_PORT
+     * @param[in] interfaceId           [Optional] The interface indicator to use
+     *
+     * @return CHIP_ERROR               The connection status
+     */
+    [[deprecated("Available until controller apps move to DeviceController/DeviceCommissioner API")]] CHIP_ERROR
+    ConnectDevice(NodeId remoteDeviceId, RendezvousParameters & params, void * appReqState, NewConnectionHandler onConnected,
+                  MessageReceiveHandler onMessageReceived, ErrorHandler onError, uint16_t devicePort = CHIP_PORT,
+                  Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+
+    /**
+     * @brief
+     *   Connect to a CHIP device at a given address and an optional port. This is a test only API
+     *   that bypasses Rendezvous and Secure Pairing process.
+     *
+     * @param[in] remoteDeviceId        The remote device Id.
+     * @param[in] deviceAddr            The IPAddress of the requested Device
+     * @param[in] appReqState           Application specific context to be passed back when a message is received or on error
+     * @param[in] onConnected           Callback for when the connection is established
+     * @param[in] onMessageReceived     Callback for when a message is received
+     * @param[in] onError               Callback for when an error occurs
+     * @param[in] devicePort            [Optional] The CHIP Device's port, defaults to CHIP_PORT
+     * @param[in] interfaceId           [Optional] The interface indicator to use
+     * @return CHIP_ERROR           The connection status
+     */
+    [[deprecated("Available until Rendezvous is implemented")]] CHIP_ERROR
+    ConnectDeviceWithoutSecurePairing(NodeId remoteDeviceId, const Inet::IPAddress & deviceAddr, void * appReqState,
+                                      NewConnectionHandler onConnected, MessageReceiveHandler onMessageReceived,
+                                      ErrorHandler onError, uint16_t devicePort = CHIP_PORT,
+                                      Inet::InterfaceId interfaceId = INET_NULL_INTERFACEID);
+
+    /**
+     * @brief
+     *   Disconnect from a connected device
+     *
+     * @return CHIP_ERROR   If the device was disconnected successfully
+     */
+    CHIP_ERROR DisconnectDevice();
+
+    /**
+     * @brief
+     *   Check if there's an active connection
+     *
+     * @return bool   If there is an active connection
+     */
+    bool IsConnected() const;
+
+    /**
+     * @brief
+     *   Get IP Address of the peer if the connection is active
+     *
+     * @return bool   If IP Address was returned
+     */
+    bool GetIpAddress(Inet::IPAddress & addr) const;
+
+    // ----- Messaging -----
+    /**
+     * @brief
+     *   Send a message to a connected CHIP device
+     *
+     * @param[in] appReqState   Application specific context to be passed back when a message is received or on error
+     * @param[in] buffer        The Data Buffer to trasmit to the device
+     * @param[in] peerDevice    Device ID of the peer device
+     * @return CHIP_ERROR   The return status
+     */
+    CHIP_ERROR SendMessage(void * appReqState, System::PacketBuffer * buffer, NodeId peerDevice = kUndefinedNodeId);
+
+    // ----- IO -----
+    /**
+     * @brief
+     * Start the event loop task within the CHIP stack
+     * @return CHIP_ERROR   The return status
+     */
+    CHIP_ERROR ServiceEvents();
+
+    /**
+     * @brief
+     *   Allow the CHIP Stack to process any pending events
+     *   This can be called in an event handler loop to tigger callbacks within the CHIP stack
+     * @return CHIP_ERROR   The return status
+     */
+    CHIP_ERROR ServiceEventSignal();
+
+    // ----- Pairing -----
+    /**
+     * @brief
+     * Set device pairing delegate after init, pass nullptr remove device delegate.
+     * @return CHIP_ERROR   The return status
+     */
+    CHIP_ERROR SetDevicePairingDelegate(Controller::DevicePairingDelegate * pairingDelegate);
+
+    //////////// DeviceStatusDelegate Implementation ///////////////
+    void OnMessage(System::PacketBuffer * msg) override;
+
+private:
+    enum
+    {
+        kState_NotInitialized = 0,
+        kState_Initialized    = 1
+    } mState;
+
+    Controller::DeviceCommissioner mCommissioner;
+    Controller::Device * mDevice;
+
+    void * mAppReqState;
+
+    union
+    {
+        CompleteHandler General;
+        MessageReceiveHandler Response;
+    } mOnComplete;
+
+    ErrorHandler mOnError;
+    NewConnectionHandler mOnNewConnection;
+    System::PacketBuffer * mCurReqMsg;
+
+    NodeId mLocalDeviceId;
+    NodeId mRemoteDeviceId;
+    uint16_t mListenPort;
+
+    Controller::SerializedDevice mSerializedTestDevice;
+    bool mPairingWithoutSecurity;
+
+    void ClearRequestState();
+    void ClearOpState();
+};
+
+} // namespace DeviceController
+} // namespace chip
diff --git a/src/controller/CHIPPersistentStorageDelegate.h b/src/controller/CHIPPersistentStorageDelegate.h
index 1414477..a2176f2 100644
--- a/src/controller/CHIPPersistentStorageDelegate.h
+++ b/src/controller/CHIPPersistentStorageDelegate.h
@@ -22,7 +22,7 @@
 #include <support/DLLUtil.h>
 
 namespace chip {
-namespace DeviceController {
+namespace Controller {
 
 class DLL_EXPORT PersistentStorageResultDelegate
 {
@@ -81,6 +81,21 @@
 
     /**
      * @brief
+     *   This is a synchronous Get API, where the value is returned via the output
+     *   buffer. This API should be used sparingly, since it may block for
+     *   some duration.
+     *
+     * @param[in]      key Key to lookup
+     * @param[out]     value Value for the key
+     * @param[in, out] size Input value buffer size, output length of value.
+     *                 The output length could be larger than input value. In
+     *                 such cases, the user should allocate the buffer large
+     *                 enough (>= output length), and call the API again.
+     */
+    virtual CHIP_ERROR GetKeyValue(const char * key, char * value, uint16_t & size) { return CHIP_ERROR_NOT_IMPLEMENTED; }
+
+    /**
+     * @brief
      *   Set the value for the key
      *
      * @param[in] key Key to be set
@@ -97,5 +112,5 @@
     virtual void DeleteKeyValue(const char * key) = 0;
 };
 
-} // namespace DeviceController
+} // namespace Controller
 } // namespace chip
diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h
index a2715f8..59eb1fe 100644
--- a/src/controller/java/AndroidDeviceControllerWrapper.h
+++ b/src/controller/java/AndroidDeviceControllerWrapper.h
@@ -21,7 +21,7 @@
 
 #include <jni.h>
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 
 /**
  * This class contains all relevant information for the JNI view of CHIPDeviceController
@@ -29,7 +29,7 @@
  *
  * Generally it contains the DeviceController class itself, plus any related delegates/callbacks.
  */
-class AndroidDeviceControllerWrapper : public chip::DeviceController::DevicePairingDelegate
+class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDelegate
 {
 public:
     ~AndroidDeviceControllerWrapper();
diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp
index 007e53e..721d8a7 100644
--- a/src/controller/java/CHIPDeviceController-JNI.cpp
+++ b/src/controller/java/CHIPDeviceController-JNI.cpp
@@ -28,7 +28,7 @@
 #include "AndroidDeviceControllerWrapper.h"
 
 #include <ble/BleUUID.h>
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 #include <jni.h>
 #include <pthread.h>
 #include <support/CHIPMem.h>
diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn
index dd9407a..84b22a1 100644
--- a/src/controller/python/BUILD.gn
+++ b/src/controller/python/BUILD.gn
@@ -23,7 +23,8 @@
 }
 
 config("includes") {
-  include_dirs = [ "${chip_root}/src/controller/CHIPDeviceController.h" ]
+  include_dirs =
+      [ "${chip_root}/src/controller/CHIPDeviceController_deprecated.h" ]
 }
 
 shared_library("ChipDeviceCtrl") {
diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
index 18f6c02..8e9e927 100644
--- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
@@ -45,7 +45,7 @@
 
 #include "ChipDeviceController-ScriptDevicePairingDelegate.h"
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 #include <support/CHIPMem.h>
 #include <support/CodeUtils.h>
 #include <support/DLLUtil.h>
@@ -178,7 +178,7 @@
 nl_Chip_ScriptDevicePairingDelegate_SetWifiCredential(chip::DeviceController::ScriptDevicePairingDelegate * pairingDelegate,
                                                       const char * ssid, const char * password);
 CHIP_ERROR nl_Chip_DeviceController_SetDevicePairingDelegate(chip::DeviceController::ChipDeviceController * devCtrl,
-                                                             chip::DeviceController::DevicePairingDelegate * pairingDelegate);
+                                                             chip::Controller::DevicePairingDelegate * pairingDelegate);
 
 #if CONFIG_NETWORK_LAYER_BLE
 CHIP_ERROR nl_Chip_DeviceController_ValidateBTP(chip::DeviceController::ChipDeviceController * devCtrl,
@@ -583,7 +583,7 @@
     return err;
 }
 CHIP_ERROR nl_Chip_DeviceController_SetDevicePairingDelegate(chip::DeviceController::ChipDeviceController * devCtrl,
-                                                             chip::DeviceController::DevicePairingDelegate * pairingDelegate)
+                                                             chip::Controller::DevicePairingDelegate * pairingDelegate)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
index 17b51a6..3a821bf 100644
--- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
+++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
@@ -25,7 +25,7 @@
 
 #pragma once
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 
 #include <platform/internal/DeviceNetworkInfo.h>
 #include <transport/RendezvousSessionDelegate.h>
@@ -33,7 +33,7 @@
 namespace chip {
 namespace DeviceController {
 
-class ScriptDevicePairingDelegate final : public DevicePairingDelegate
+class ScriptDevicePairingDelegate final : public Controller::DevicePairingDelegate
 {
 public:
     ~ScriptDevicePairingDelegate() = default;
diff --git a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m
index be0c0fc..fcc9d05 100644
--- a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m
+++ b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m
@@ -48,9 +48,17 @@
 - (void)GetKeyValue:(NSString *)key handler:(SendKeyValue)completionHandler
 {
     NSString * value = CHIPGetDomainValueForKey(kCHIPToolDefaultsDomain, key);
+    NSLog(@"CHIPPersistentStorageDelegate Get Value for Key: %@, value %@", key, value);
     completionHandler(key, value);
 }
 
+- (NSString *)GetKeyValue:(NSString *)key
+{
+    NSString * value = CHIPGetDomainValueForKey(kCHIPToolDefaultsDomain, key);
+    NSLog(@"CHIPPersistentStorageDelegate Get Value for Key: %@, value %@", key, value);
+    return value;
+}
+
 - (void)SetKeyValue:(NSString *)key value:(NSString *)value handler:(SendStatus)completionHandler
 {
     CHIPSetDomainValueForKey(kCHIPToolDefaultsDomain, key, value);
diff --git a/src/darwin/Framework/CHIP/CHIPDeviceController.mm b/src/darwin/Framework/CHIP/CHIPDeviceController.mm
index 3d57db3..faa2d56 100644
--- a/src/darwin/Framework/CHIP/CHIPDeviceController.mm
+++ b/src/darwin/Framework/CHIP/CHIPDeviceController.mm
@@ -25,7 +25,7 @@
 #import "CHIPLogging.h"
 #import "CHIPPersistentStorageDelegateBridge.h"
 
-#include <controller/CHIPDeviceController.h>
+#include <controller/CHIPDeviceController_deprecated.h>
 #include <inet/IPAddress.h>
 #include <support/CHIPMem.h>
 #include <system/SystemPacketBuffer.h>
@@ -95,6 +95,11 @@
             return nil;
         }
 
+        if (CHIP_NO_ERROR != chip::Platform::MemoryInit()) {
+            CHIP_LOG_ERROR("Error: Failed in memory init");
+            return nil;
+        }
+
         _cppController = new chip::DeviceController::ChipDeviceController();
         if (!_cppController) {
             CHIP_LOG_ERROR("Error: couldn't create c++ controller");
@@ -129,17 +134,6 @@
             _persistentStorageDelegateBridge = NULL;
             return nil;
         }
-
-        if (CHIP_NO_ERROR != chip::Platform::MemoryInit()) {
-            CHIP_LOG_ERROR("Error: couldn't initialize c++ controller");
-            delete _cppController;
-            _cppController = NULL;
-            delete _pairingDelegateBridge;
-            _pairingDelegateBridge = NULL;
-            delete _persistentStorageDelegateBridge;
-            _persistentStorageDelegateBridge = NULL;
-            return nil;
-        }
     }
     return self;
 }
diff --git a/src/darwin/Framework/CHIP/CHIPDevicePairingDelegateBridge.h b/src/darwin/Framework/CHIP/CHIPDevicePairingDelegateBridge.h
index b448c06..884c17b 100644
--- a/src/darwin/Framework/CHIP/CHIPDevicePairingDelegateBridge.h
+++ b/src/darwin/Framework/CHIP/CHIPDevicePairingDelegateBridge.h
@@ -22,7 +22,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-class CHIPDevicePairingDelegateBridge : public chip::DeviceController::DevicePairingDelegate
+class CHIPDevicePairingDelegateBridge : public chip::Controller::DevicePairingDelegate
 {
 public:
     CHIPDevicePairingDelegateBridge();
diff --git a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegate.h b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegate.h
index 916b1f4..b59cce2 100644
--- a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegate.h
+++ b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegate.h
@@ -44,6 +44,12 @@
 - (void)GetKeyValue:(NSString *)key handler:(SendKeyValue)completionHandler;
 
 /**
+ * Get the value for the given key
+ *
+ */
+- (NSString *)GetKeyValue:(NSString *)key;
+
+/**
  * Set the value of the key to the given value
  *
  */
diff --git a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.h b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.h
index fe9645b..bb689a0 100644
--- a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.h
+++ b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.h
@@ -22,7 +22,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-class CHIPPersistentStorageDelegateBridge : public chip::DeviceController::PersistentStorageDelegate
+class CHIPPersistentStorageDelegateBridge : public chip::Controller::PersistentStorageDelegate
 {
 public:
     CHIPPersistentStorageDelegateBridge();
@@ -30,10 +30,12 @@
 
     void setFrameworkDelegate(id<CHIPPersistentStorageDelegate> delegate, dispatch_queue_t queue);
 
-    void SetDelegate(chip::DeviceController::PersistentStorageResultDelegate * delegate);
+    void SetDelegate(chip::Controller::PersistentStorageResultDelegate * delegate);
 
     void GetKeyValue(const char * key) override;
 
+    CHIP_ERROR GetKeyValue(const char * key, char * value, uint16_t & size) override;
+
     void SetKeyValue(const char * key, const char * value) override;
 
     void DeleteKeyValue(const char * key) override;
@@ -42,7 +44,7 @@
     id<CHIPPersistentStorageDelegate> mDelegate;
     dispatch_queue_t mQueue;
 
-    chip::DeviceController::PersistentStorageResultDelegate * mCallback;
+    chip::Controller::PersistentStorageResultDelegate * mCallback;
     SendKeyValue mCompletionHandler;
     SendStatus mStatusHandler;
     NSUserDefaults * mDefaultPersistentStorage;
diff --git a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.mm b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.mm
index 51a7444..92f3862 100644
--- a/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.mm
+++ b/src/darwin/Framework/CHIP/CHIPPersistentStorageDelegateBridge.mm
@@ -39,7 +39,7 @@
     }
 }
 
-void CHIPPersistentStorageDelegateBridge::SetDelegate(chip::DeviceController::PersistentStorageResultDelegate * delegate)
+void CHIPPersistentStorageDelegateBridge::SetDelegate(chip::Controller::PersistentStorageResultDelegate * delegate)
 {
     if (delegate) {
         mCallback = delegate;
@@ -49,17 +49,17 @@
         };
 
         mStatusHandler = ^(NSString * key, Operation operation, NSError * status) {
-            chip::DeviceController::PersistentStorageResultDelegate::Operation op
-                = chip::DeviceController::PersistentStorageResultDelegate::Operation::kGET;
+            chip::Controller::PersistentStorageResultDelegate::Operation op
+                = chip::Controller::PersistentStorageResultDelegate::Operation::kGET;
             switch (operation) {
             case kGet:
-                op = chip::DeviceController::PersistentStorageResultDelegate::Operation::kGET;
+                op = chip::Controller::PersistentStorageResultDelegate::Operation::kGET;
                 break;
             case kSet:
-                op = chip::DeviceController::PersistentStorageResultDelegate::Operation::kSET;
+                op = chip::Controller::PersistentStorageResultDelegate::Operation::kSET;
                 break;
             case kDelete:
-                op = chip::DeviceController::PersistentStorageResultDelegate::Operation::kDELETE;
+                op = chip::Controller::PersistentStorageResultDelegate::Operation::kDELETE;
                 break;
             }
             mCallback->OnStatus([key UTF8String], op, [CHIPError errorToCHIPErrorCode:status]);
@@ -84,16 +84,41 @@
     } else {
         dispatch_async(mDefaultCallbackQueue, ^{
             NSString * value = [mDefaultPersistentStorage objectForKey:keyString];
+            NSLog(@"PersistentStorageDelegate Get Value for Key: %@, value %@", keyString, value);
             mCompletionHandler(keyString, value);
         });
     }
 }
 
+CHIP_ERROR CHIPPersistentStorageDelegateBridge::GetKeyValue(const char * key, char * value, uint16_t & size)
+{
+    NSString * keyString = [NSString stringWithUTF8String:key];
+    NSLog(@"PersistentStorageDelegate Sync Get Value for Key: %@", keyString);
+
+    NSString * valueString = nil;
+
+    id<CHIPPersistentStorageDelegate> strongDelegate = mDelegate;
+    if (strongDelegate) {
+        valueString = [strongDelegate GetKeyValue:keyString];
+    } else {
+        valueString = [mDefaultPersistentStorage objectForKey:keyString];
+    }
+
+    if (value != nullptr) {
+        size = strlcpy(value, [valueString UTF8String], size);
+    } else {
+        size = [valueString length];
+    }
+    // Increment size to account for null termination
+    size += 1;
+    return CHIP_NO_ERROR;
+}
+
 void CHIPPersistentStorageDelegateBridge::SetKeyValue(const char * key, const char * value)
 {
     NSString * keyString = [NSString stringWithUTF8String:key];
     NSString * valueString = [NSString stringWithUTF8String:value];
-    NSLog(@"PersistentStorageDelegate Set Key %@", keyString);
+    NSLog(@"PersistentStorageDelegate Set Key %@, Value %@", keyString, valueString);
 
     id<CHIPPersistentStorageDelegate> strongDelegate = mDelegate;
     if (strongDelegate && mQueue) {
diff --git a/src/lib/support/SerializableIntegerSet.h b/src/lib/support/SerializableIntegerSet.h
index 0fe6e0d..6e59f77 100644
--- a/src/lib/support/SerializableIntegerSet.h
+++ b/src/lib/support/SerializableIntegerSet.h
@@ -34,6 +34,8 @@
 #include <support/Base64.h>
 #include <support/CodeUtils.h>
 
+#define CHIP_MAX_SERIALIZED_SIZE_U64(count) static_cast<uint16_t>(BASE64_ENCODED_LEN(sizeof(uint64_t) * (count)))
+
 namespace chip {
 
 class SerializableU64SetBase
@@ -78,13 +80,13 @@
      * @brief
      *   Get the length of string if the array is serialized.
      */
-    uint16_t SerializedSize() { return static_cast<uint16_t>(BASE64_ENCODED_LEN(sizeof(uint64_t) * mNextAvailable)); }
+    uint16_t SerializedSize() { return CHIP_MAX_SERIALIZED_SIZE_U64(mNextAvailable); }
 
     /**
      * @brief
      *   Get the maximum length of string if the array were full and serialized.
      */
-    uint16_t MaxSerializedSize() { return static_cast<uint16_t>(BASE64_ENCODED_LEN(sizeof(uint64_t) * mCapacity)); }
+    uint16_t MaxSerializedSize() { return CHIP_MAX_SERIALIZED_SIZE_U64(mCapacity); }
 
     /**
      * @brief
diff --git a/src/transport/RendezvousSession.cpp b/src/transport/RendezvousSession.cpp
index 838d4d0..8e66d74 100644
--- a/src/transport/RendezvousSession.cpp
+++ b/src/transport/RendezvousSession.cpp
@@ -213,19 +213,33 @@
         break;
     };
     mDelegate->OnRendezvousError(err);
-    UpdateState(State::kInit);
+    UpdateState(State::kInit, err);
 }
 
-void RendezvousSession::UpdateState(RendezvousSession::State newState)
+void RendezvousSession::UpdateState(RendezvousSession::State newState, CHIP_ERROR err)
 {
     switch (mCurrentState)
     {
     case State::kSecurePairing:
-        mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::SecurePairingSuccess, CHIP_NO_ERROR);
+        if (newState != State::kInit)
+        {
+            mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::SecurePairingSuccess, err);
+        }
+        else
+        {
+            mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::SecurePairingFailed, err);
+        }
         break;
 
     case State::kNetworkProvisioning:
-        mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::NetworkProvisioningSuccess, CHIP_NO_ERROR);
+        if (newState != State::kInit)
+        {
+            mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::NetworkProvisioningSuccess, err);
+        }
+        else
+        {
+            mDelegate->OnRendezvousStatusUpdate(RendezvousSessionDelegate::NetworkProvisioningFailed, err);
+        }
         break;
 
     default:
diff --git a/src/transport/RendezvousSession.h b/src/transport/RendezvousSession.h
index 3a0b718..ed0415f 100644
--- a/src/transport/RendezvousSession.h
+++ b/src/transport/RendezvousSession.h
@@ -138,7 +138,7 @@
     uint16_t mNextKeyId          = 0;
 
     RendezvousSession::State mCurrentState = State::kInit;
-    void UpdateState(RendezvousSession::State newState);
+    void UpdateState(RendezvousSession::State newState, CHIP_ERROR err = CHIP_NO_ERROR);
 };
 
 } // namespace chip
diff --git a/src/transport/SecurePairingSession.cpp b/src/transport/SecurePairingSession.cpp
index 61335c1..780654b 100644
--- a/src/transport/SecurePairingSession.cpp
+++ b/src/transport/SecurePairingSession.cpp
@@ -57,37 +57,20 @@
 
 CHIP_ERROR SecurePairingSession::Serialize(SecurePairingSessionSerialized & output)
 {
-    CHIP_ERROR error = CHIP_NO_ERROR;
+    CHIP_ERROR error       = CHIP_NO_ERROR;
+    uint16_t serializedLen = 0;
+    SecurePairingSessionSerializable serializable;
 
-    const NodeId localNodeId = (mLocalNodeId.HasValue()) ? mLocalNodeId.Value() : kUndefinedNodeId;
-    const NodeId peerNodeId  = (mPeerNodeId.HasValue()) ? mPeerNodeId.Value() : kUndefinedNodeId;
-    VerifyOrExit(CanCastTo<uint16_t>(mKeLen), error = CHIP_ERROR_INTERNAL);
-    VerifyOrExit(CanCastTo<uint64_t>(localNodeId), error = CHIP_ERROR_INTERNAL);
-    VerifyOrExit(CanCastTo<uint64_t>(peerNodeId), error = CHIP_ERROR_INTERNAL);
-    VerifyOrExit(CanCastTo<uint16_t>(sizeof(SecurePairingSessionSerializable)), error = CHIP_ERROR_INTERNAL);
+    VerifyOrExit(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
 
-    {
-        SecurePairingSessionSerializable serializable;
-        memset(&serializable, 0, sizeof(serializable));
-        serializable.mKeLen           = static_cast<uint16_t>(mKeLen);
-        serializable.mPairingComplete = (mPairingComplete) ? 1 : 0;
-        serializable.mLocalNodeId     = localNodeId;
-        serializable.mPeerNodeId      = peerNodeId;
-        serializable.mLocalKeyId      = mLocalKeyId;
-        serializable.mPeerKeyId       = mPeerKeyId;
+    error = ToSerializable(serializable);
+    SuccessOrExit(error);
 
-        memcpy(serializable.mKe, mKe, mKeLen);
-
-        uint16_t serializedLen = 0;
-
-        VerifyOrExit(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
-
-        serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast<uint8_t *>(&serializable)),
-                                           static_cast<uint16_t>(sizeof(serializable)), Uint8::to_char(output.inner));
-        VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
-        VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
-        output.inner[serializedLen] = '\0';
-    }
+    serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast<uint8_t *>(&serializable)),
+                                       static_cast<uint16_t>(sizeof(serializable)), Uint8::to_char(output.inner));
+    VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
+    output.inner[serializedLen] = '\0';
 
 exit:
     return error;
@@ -107,9 +90,44 @@
     memset(&serializable, 0, sizeof(serializable));
     deserializedLen =
         Base64Decode(Uint8::to_const_char(input.inner), static_cast<uint16_t>(len), Uint8::to_uchar((uint8_t *) &serializable));
+
     VerifyOrExit(deserializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
     VerifyOrExit(deserializedLen <= sizeof(serializable), error = CHIP_ERROR_INVALID_ARGUMENT);
 
+    error = FromSerializable(serializable);
+
+exit:
+    return error;
+}
+
+CHIP_ERROR SecurePairingSession::ToSerializable(SecurePairingSessionSerializable & serializable)
+{
+    CHIP_ERROR error = CHIP_NO_ERROR;
+
+    const NodeId localNodeId = (mLocalNodeId.HasValue()) ? mLocalNodeId.Value() : kUndefinedNodeId;
+    const NodeId peerNodeId  = (mPeerNodeId.HasValue()) ? mPeerNodeId.Value() : kUndefinedNodeId;
+    VerifyOrExit(CanCastTo<uint16_t>(mKeLen), error = CHIP_ERROR_INTERNAL);
+    VerifyOrExit(CanCastTo<uint64_t>(localNodeId), error = CHIP_ERROR_INTERNAL);
+    VerifyOrExit(CanCastTo<uint64_t>(peerNodeId), error = CHIP_ERROR_INTERNAL);
+
+    memset(&serializable, 0, sizeof(serializable));
+    serializable.mKeLen           = static_cast<uint16_t>(mKeLen);
+    serializable.mPairingComplete = (mPairingComplete) ? 1 : 0;
+    serializable.mLocalNodeId     = localNodeId;
+    serializable.mPeerNodeId      = peerNodeId;
+    serializable.mLocalKeyId      = mLocalKeyId;
+    serializable.mPeerKeyId       = mPeerKeyId;
+
+    memcpy(serializable.mKe, mKe, mKeLen);
+
+exit:
+    return error;
+}
+
+CHIP_ERROR SecurePairingSession::FromSerializable(const SecurePairingSessionSerializable & serializable)
+{
+    CHIP_ERROR error = CHIP_NO_ERROR;
+
     mPairingComplete = (serializable.mPairingComplete == 1);
     mKeLen           = static_cast<size_t>(serializable.mKeLen);
 
diff --git a/src/transport/SecurePairingSession.h b/src/transport/SecurePairingSession.h
index dfe3857..d811c2e 100644
--- a/src/transport/SecurePairingSession.h
+++ b/src/transport/SecurePairingSession.h
@@ -74,6 +74,17 @@
 
 struct SecurePairingSessionSerialized;
 
+struct SecurePairingSessionSerializable
+{
+    uint16_t mKeLen;
+    uint8_t mKe[kMAX_Hash_Length];
+    uint8_t mPairingComplete;
+    uint64_t mLocalNodeId;
+    uint64_t mPeerNodeId;
+    uint16_t mLocalKeyId;
+    uint16_t mPeerKeyId;
+};
+
 class DLL_EXPORT SecurePairingSession
 {
 public:
@@ -178,6 +189,18 @@
      **/
     CHIP_ERROR Deserialize(SecurePairingSessionSerialized & input);
 
+    /** @brief Serialize the SecurePairingSession to the given serializable data structure for secure pairing
+     *
+     * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
+     **/
+    CHIP_ERROR ToSerializable(SecurePairingSessionSerializable & output);
+
+    /** @brief Reconstruct secure pairing class from the serializable data structure.
+     *
+     * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
+     **/
+    CHIP_ERROR FromSerializable(const SecurePairingSessionSerializable & output);
+
 private:
     CHIP_ERROR Init(uint32_t setupCode, uint32_t pbkdf2IterCount, const uint8_t * salt, size_t saltLen, Optional<NodeId> myNodeId,
                     uint16_t myKeyId, SecurePairingSessionDelegate * delegate);
@@ -209,12 +232,6 @@
     /* w0s and w1s */
     uint8_t mWS[2][kSpake2p_WS_Length];
 
-    uint8_t mKe[kMAX_Hash_Length];
-
-    size_t mKeLen = sizeof(mKe);
-
-    bool mPairingComplete = false;
-
 protected:
     Optional<NodeId> mLocalNodeId = Optional<NodeId>::Value(kUndefinedNodeId);
 
@@ -223,6 +240,12 @@
     uint16_t mLocalKeyId;
 
     uint16_t mPeerKeyId;
+
+    uint8_t mKe[kMAX_Hash_Length];
+
+    size_t mKeLen = sizeof(mKe);
+
+    bool mPairingComplete = false;
 };
 
 /*
@@ -234,12 +257,25 @@
 class SecurePairingUsingTestSecret : public SecurePairingSession
 {
 public:
-    SecurePairingUsingTestSecret() {}
+    SecurePairingUsingTestSecret()
+    {
+        const char * secret = "Test secret for key derivation";
+        size_t secretLen    = strlen(secret);
+        mKeLen              = secretLen;
+        memmove(mKe, secret, mKeLen);
+        mPairingComplete = true;
+    }
+
     SecurePairingUsingTestSecret(Optional<NodeId> peerNodeId, uint16_t peerKeyId, uint16_t localKeyId)
     {
-        mPeerNodeId = peerNodeId;
-        mPeerKeyId  = peerKeyId;
-        mLocalKeyId = localKeyId;
+        const char * secret = "Test secret for key derivation";
+        size_t secretLen    = strlen(secret);
+        mPeerNodeId         = peerNodeId;
+        mPeerKeyId          = peerKeyId;
+        mLocalKeyId         = localKeyId;
+        mKeLen              = secretLen;
+        memmove(mKe, secret, mKeLen);
+        mPairingComplete = true;
     }
 
     ~SecurePairingUsingTestSecret() override {}
@@ -256,28 +292,9 @@
         return CHIP_NO_ERROR;
     }
 
-    CHIP_ERROR DeriveSecureSession(const uint8_t * info, size_t info_len, SecureSession & session) override
-    {
-        const char * secret = "Test secret for key derivation";
-        size_t secretLen    = strlen(secret);
-        return session.InitFromSecret(reinterpret_cast<const uint8_t *>(secret), secretLen, reinterpret_cast<const uint8_t *>(""),
-                                      0, reinterpret_cast<const uint8_t *>(secret), secretLen);
-    }
-
     CHIP_ERROR HandlePeerMessage(const PacketHeader & packetHeader, System::PacketBuffer * msg) override { return CHIP_NO_ERROR; }
 };
 
-typedef struct SecurePairingSessionSerializable
-{
-    uint16_t mKeLen;
-    uint8_t mKe[kMAX_Hash_Length];
-    uint8_t mPairingComplete;
-    uint64_t mLocalNodeId;
-    uint64_t mPeerNodeId;
-    uint16_t mLocalKeyId;
-    uint16_t mPeerKeyId;
-} SecurePairingSessionSerializable;
-
 typedef struct SecurePairingSessionSerialized
 {
     // Extra uint64_t to account for padding bytes (NULL termination, and some decoding overheads)
diff --git a/src/transport/SecureSessionMgr.cpp b/src/transport/SecureSessionMgr.cpp
index 4e41dc4..a8dea8e 100644
--- a/src/transport/SecureSessionMgr.cpp
+++ b/src/transport/SecureSessionMgr.cpp
@@ -65,6 +65,8 @@
     mSystemLayer = systemLayer;
     mTransport   = transport;
 
+    ChipLogProgress(Inet, "local node id is %llu\n", mLocalNodeId);
+
     mTransport->SetMessageReceiveHandler(HandleDataReceived, this);
     mPeerConnections.SetConnectionExpiredHandler(HandleConnectionExpired, this);
 
@@ -124,6 +126,8 @@
             .SetEncryptionKeyID(state->GetLocalKeyID()) //
             .SetPayloadLength(static_cast<uint16_t>(payloadLength));
 
+        ChipLogProgress(Inet, "Sending msg from %llu to %llu\n", mLocalNodeId, peerNodeId);
+
         VerifyOrExit(msgBuf->EnsureReservedSize(headerSize), err = CHIP_ERROR_NO_MEMORY);
 
         msgBuf->SetStart(msgBuf->Start() - headerSize);
diff --git a/src/transport/SecureSessionMgr.h b/src/transport/SecureSessionMgr.h
index ac66baf..5536715 100644
--- a/src/transport/SecureSessionMgr.h
+++ b/src/transport/SecureSessionMgr.h
@@ -224,6 +224,12 @@
         return err;
     }
 
+    template <typename... Args>
+    CHIP_ERROR ResetTransport(Args &&... transportInitArgs)
+    {
+        return mTransport.Init(std::forward<Args>(transportInitArgs)...);
+    }
+
 private:
     Transport::Tuple<TransportTypes...> mTransport;
 };
diff --git a/src/transport/raw/Base.h b/src/transport/raw/Base.h
index fe1dc4f..f374bff 100644
--- a/src/transport/raw/Base.h
+++ b/src/transport/raw/Base.h
@@ -82,6 +82,11 @@
      */
     virtual void Disconnect(const PeerAddress & address) {}
 
+    /**
+     * Close the open endpoint without destroying the object
+     */
+    virtual void Close(){};
+
 protected:
     /**
      * Method used by subclasses to notify that a packet has been received after
diff --git a/src/transport/raw/UDP.cpp b/src/transport/raw/UDP.cpp
index ce065b6..aa5621a 100644
--- a/src/transport/raw/UDP.cpp
+++ b/src/transport/raw/UDP.cpp
@@ -34,20 +34,17 @@
 
 UDP::~UDP()
 {
-    if (mUDPEndPoint)
-    {
-        // Udp endpoint is only non null if udp endpoint is initialized and listening
-        mUDPEndPoint->Close();
-        mUDPEndPoint->Free();
-        mUDPEndPoint = nullptr;
-    }
+    Close();
 }
 
 CHIP_ERROR UDP::Init(UdpListenParameters & params)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
-    VerifyOrExit(mState == State::kNotReady, err = CHIP_ERROR_INCORRECT_STATE);
+    if (mState != State::kNotReady)
+    {
+        Close();
+    }
 
     err = params.GetInetLayer()->NewUDPEndPoint(&mUDPEndPoint);
     SuccessOrExit(err);
@@ -78,6 +75,18 @@
     return err;
 }
 
+void UDP::Close()
+{
+    if (mUDPEndPoint)
+    {
+        // Udp endpoint is only non null if udp endpoint is initialized and listening
+        mUDPEndPoint->Close();
+        mUDPEndPoint->Free();
+        mUDPEndPoint = nullptr;
+    }
+    mState = State::kNotReady;
+}
+
 CHIP_ERROR UDP::SendMessage(const PacketHeader & header, Header::Flags payloadFlags, const Transport::PeerAddress & address,
                             System::PacketBuffer * msgBuf)
 {
diff --git a/src/transport/raw/UDP.h b/src/transport/raw/UDP.h
index d2dfcc3..2cfbf76 100644
--- a/src/transport/raw/UDP.h
+++ b/src/transport/raw/UDP.h
@@ -105,6 +105,11 @@
      */
     CHIP_ERROR Init(UdpListenParameters & params);
 
+    /**
+     * Close the open endpoint without destroying the object
+     */
+    void Close() override;
+
     CHIP_ERROR SendMessage(const PacketHeader & header, Header::Flags payloadFlags, const Transport::PeerAddress & address,
                            System::PacketBuffer * msgBuf) override;