Skip to content

Commit

Permalink
[2/3] CASE Eviction (Initial Impl) (#19502)
Browse files Browse the repository at this point in the history
* Initial commit

* Review feedback

* Fixes to the YAML to make window covering work
  • Loading branch information
mrjerryjohns authored and pull[bot] committed Jul 1, 2022
1 parent 970a51c commit efb8a41
Show file tree
Hide file tree
Showing 35 changed files with 813 additions and 228 deletions.
13 changes: 11 additions & 2 deletions src/app/OperationalDeviceProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,21 @@ void OperationalDeviceProxy::OnSessionEstablished(const SessionHandle & session)
CHIP_ERROR OperationalDeviceProxy::Disconnect()
{
ReturnErrorCodeIf(mState != State::SecureConnected, CHIP_ERROR_INCORRECT_STATE);

if (mSecureSession)
{
mInitParams.sessionManager->ExpirePairing(mSecureSession.Get().Value());
//
// Mark the session as defunct to signal that we no longer want to use this
// session anymore for further interactions to this peer. However, if we receive
// messages back from that peer on the defunct session, it will bring it back into an active
// state again.
//
mSecureSession.Get().Value()->AsSecureSession()->MarkAsDefunct();
}
MoveToState(State::HasAddress);

mSecureSession.Release();

MoveToState(State::HasAddress);
return CHIP_NO_ERROR;
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/server/CommissioningWindowManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void CommissioningWindowManager::OnPlatformEvent(const DeviceLayer::ChipDeviceEv
ChipLogError(AppServer, "Failsafe timer expired");
if (mPASESession)
{
mPASESession->AsSecureSession()->MarkForRemoval();
mPASESession->AsSecureSession()->MarkForEviction();
}
HandleFailedAttempt(CHIP_ERROR_TIMEOUT);
}
Expand Down Expand Up @@ -177,7 +177,7 @@ void CommissioningWindowManager::OnSessionEstablished(const SessionHandle & sess
{
ChipLogError(AppServer, "Error arming failsafe on PASE session establishment completion");
// Don't allow a PASE session to hang around without a fail-safe.
session->AsSecureSession()->MarkForRemoval();
session->AsSecureSession()->MarkForEviction();
HandleFailedAttempt(err);
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/app/tests/suites/certification/Test_TC_WNCV_4_5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,17 @@ tests:
- name: "LiftPercentageValue"
value: 90

#
# TODO: For some reason, the window-covering server impl arms a 5s timer before it actually
# processes the GoToLiftPercentage command's effect on the current tile value.
#
- label: "1b: TH Waits for 100ms-1s"
cluster: "DelayCommands"
command: "WaitForMs"
arguments:
values:
- name: "ms"
value: 500
value: 5000

- label: "1c: TH sends StopMotion command to DUT"
command: "StopMotion"
Expand All @@ -83,13 +87,17 @@ tests:
- name: "TiltPercentageValue"
value: 90

#
# TODO: For some reason, the window-covering server impl arms a 5s timer before it actually
# processes the GoToTiltPercentage command's effect on the current tile value.
#
- label: "2b: TH Waits for 100ms-1s"
cluster: "DelayCommands"
command: "WaitForMs"
arguments:
values:
- name: "ms"
value: 500
value: 5000

- label: "2c: TH sends StopMotion command to DUT"
command: "StopMotion"
Expand Down
5 changes: 4 additions & 1 deletion src/controller/CommissioneeDeviceProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ CHIP_ERROR CommissioneeDeviceProxy::CloseSession()
ReturnErrorCodeIf(mState != ConnectionState::SecureConnected, CHIP_ERROR_INCORRECT_STATE);
if (mSecureSession)
{
mSessionManager->ExpirePairing(mSecureSession.Get().Value());
mSecureSession->AsSecureSession()->MarkForEviction();
}

mState = ConnectionState::NotConnected;
mPairing.Clear();
return CHIP_NO_ERROR;
Expand Down Expand Up @@ -97,6 +98,8 @@ CHIP_ERROR CommissioneeDeviceProxy::UpdateDeviceData(const Transport::PeerAddres
CHIP_ERROR CommissioneeDeviceProxy::SetConnected(const SessionHandle & session)
{
VerifyOrReturnError(mState == ConnectionState::Connecting, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(session->AsSecureSession()->IsPASESession(), CHIP_ERROR_INVALID_ARGUMENT);

if (!mSecureSession.Grab(session))
{
mState = ConnectionState::NotConnected;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ CHIP_ERROR StorageAdapter::SyncDeleteKeyValue(const char * key)
return err;
}

ChipLogDetail(Controller, "StorageAdapter::DeleteKeyValue: Key = %s", key);
mDeleteKeyCb(mContext, key);
return CHIP_NO_ERROR;
}
Expand Down
2 changes: 1 addition & 1 deletion src/controller/python/chip/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
None, py_object, c_char_p, POINTER(c_char), c_uint16)
_SyncGetKeyValueCbFunct = CFUNCTYPE(
None, py_object, c_char_p, POINTER(c_char), POINTER(c_uint16))
_SyncDeleteKeyValueCbFunct = CFUNCTYPE(None, py_object, POINTER(c_char_p))
_SyncDeleteKeyValueCbFunct = CFUNCTYPE(None, py_object, c_char_p)


@_SyncSetKeyValueCbFunct
Expand Down
36 changes: 36 additions & 0 deletions src/controller/python/test/test_scripts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool =
self.controllerNodeId = nodeid
self.logger = logger
self.paaTrustStorePath = paaTrustStorePath
logging.getLogger().setLevel(logging.DEBUG)

def _WaitForOneDiscoveredDevice(self, timeoutSeconds: int = 2):
print("Waiting for device responses...")
Expand Down Expand Up @@ -356,6 +357,40 @@ def TestFailsafe(self, nodeid: int):
return True
return False

async def TestCaseEviction(self, nodeid: int):
self.logger.info("Testing CASE eviction")

minimumCASESessionsPerFabric = 3
minimumSupportedFabrics = 16

#
# This test exercises the ability to allocate more sessions than are supported in the
# pool configuration. By going beyond (minimumSupportedFabrics * minimumCASESessionsPerFabric),
# it starts to test out the eviction logic. This specific test does not validate the specifics
# of eviction, just that allocation and CASE session establishment proceeds successfully on both
# the controller and target.
#
for x in range(minimumSupportedFabrics * minimumCASESessionsPerFabric * 3):
self.devCtrl.CloseSession(nodeid)
await self.devCtrl.ReadAttribute(nodeid, [(Clusters.Basic.Attributes.ClusterRevision)])

self.logger.info("Testing CASE defunct logic")

#
# This tests establishing a subscription on a given CASE session, then mark it defunct (to simulate
# encountering a transport timeout on the session).
#
# At the max interval, we should still have a valid subscription.
#
sub = await self.devCtrl.ReadAttribute(nodeid, [(Clusters.Basic.Attributes.ClusterRevision)], reportInterval=(0, 2))
await asyncio.sleep(2)
self.devCtrl.CloseSession(nodeid)
await asyncio.sleep(4)

sub.Shutdown()

return True

async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int):
self.logger.info("Opening Commissioning Window")

Expand Down Expand Up @@ -823,6 +858,7 @@ def run(self):
# Thread join timed out
self.logger.error(f"Failed to join change thread")
return False

return True if receivedUpdate == 5 else False

except Exception as ex:
Expand Down
13 changes: 13 additions & 0 deletions src/controller/python/test/test_scripts/mobile-device-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin:
nodeid=device_nodeid),
"Failed to finish key exchange")

#
# Run this before the MultiFabric test, since it will result in the resultant CASE session
# on fabric2 to be evicted (due to the stressful nature of that test) on the target.
#
# It doesn't actually evict the CASE session for fabric2 on the client, since we prioritize
# defunct sessions for eviction first, which means our CASE session on fabric2 remains preserved
# throughout the stress test. This results in a mis-match later.
#
# TODO: Once we implement fabric-adjusted LRU, we should see if this issue remains (it shouldn't)
#
logger.info("Testing CASE Eviction")
FailIfNot(asyncio.run(test.TestCaseEviction(device_nodeid)), "Failed TestCaseEviction")

ok = asyncio.run(test.TestMultiFabric(ip=address,
setuppin=20202021,
nodeid=1))
Expand Down
7 changes: 6 additions & 1 deletion src/lib/core/CHIPConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -650,11 +650,16 @@
* secure sessions. This controls the maximum number of concurrent
* established secure sessions across all supported transports.
*
* This is sized to cover the sum of the following:
* This is sized by default to cover the sum of the following:
* - At least 3 CASE sessions / fabric (Spec Ref: 4.13.2.8)
* - 1 reserved slot for CASEServer as a responder.
* - 1 reserved slot for PASE.
*
* NOTE: On heap-based platforms, there is no pre-allocation of the pool.
* Due to the use of an LRU-scheme to manage sessions, the actual active
* size of the pool will grow up to the value of this define,
* after which, it will remain at or around this size indefinitely.
*
*/
#ifndef CHIP_CONFIG_SECURE_SESSION_POOL_SIZE
#define CHIP_CONFIG_SECURE_SESSION_POOL_SIZE (CHIP_CONFIG_MAX_FABRICS * 3 + 2)
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/CHIPPersistentStorageDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class DLL_EXPORT PersistentStorageDelegate
*
* @param[in] key Key to be deleted
*
* @return CHIP_NO_ERROR on success, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND the key is not found in storage,
* @return CHIP_NO_ERROR on success, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if the key is not found in storage,
* or another CHIP_ERROR value from implementation on failure.
*/
virtual CHIP_ERROR SyncDeleteKeyValue(const char * key) = 0;
Expand Down
1 change: 1 addition & 0 deletions src/lib/support/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ static_library("support") {
"SafeInt.h",
"SerializableIntegerSet.cpp",
"SerializableIntegerSet.h",
"SortUtils.h",
"StateMachine.h",
"ThreadOperationalDataset.cpp",
"ThreadOperationalDataset.h",
Expand Down
21 changes: 19 additions & 2 deletions src/lib/support/CodeUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,29 @@ constexpr inline const _T & max(const _T & a, const _T & b)
* @param[in] expr A Boolean expression to be evaluated.
* @param[in] code A value to return if @a expr is false.
*/
#define VerifyOrReturnError(expr, code) \
#define VerifyOrReturnError(expr, code) VerifyOrReturnValue(expr, code)

/**
* @def VerifyOrReturnValue(expr, value)
*
* @brief
* Returns a specified value if expression evaluates to false
*
* Example usage:
*
* @code
* VerifyOrReturnError(param != nullptr, Foo());
* @endcode
*
* @param[in] expr A Boolean expression to be evaluated.
* @param[in] value A value to return if @a expr is false.
*/
#define VerifyOrReturnValue(expr, value) \
do \
{ \
if (!(expr)) \
{ \
return code; \
return (value); \
} \
} while (false)

Expand Down
71 changes: 71 additions & 0 deletions src/lib/support/SortUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
*
* Copyright (c) 2020-2021 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.
*/

#pragma once

#include <algorithm>
#include <stdint.h>
#include <stdlib.h>

namespace chip {
namespace Sorting {

/**
*
* Impements the bubble sort algorithm to sort an array
* of items of size 'n'.
*
* T should be swappable using std::swap (i.e implements Swappable).
*
* The provided comparison function SHALL have the following signature:
*
* bool Compare(const T& a, const T& b);
*
* If a is deemed less than (i.e is ordered before) b, return true.
* Else, return false.
*
* This is a stable sort.
*
* NOTE: This has a worst case time complexity of O(n^2). This
* is well-suited for small arrays where the time trade-off is
* acceptable compared to the lower flash cost for the simple sorting
* implementation.
*
*/
template <typename T, typename CompareFunc>
void BubbleSort(T * items, size_t n, CompareFunc f)
{
for (size_t i = 0; i < (n - 1); i++)
{
for (size_t j = 0; j < (n - i - 1); j++)
{
const T & a = items[j + 1];
const T & b = items[j];

if (f(a, b))
{
std::swap(items[j], items[j + 1]);
}
}
}
}

} // namespace Sorting

} // namespace chip
2 changes: 1 addition & 1 deletion src/messaging/ExchangeContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ ExchangeContext::~ExchangeContext()
VerifyOrDie(!IsAckPending());

if (ReleaseSessionOnDestruction() && mSession)
mSession->AsSecureSession()->MarkForRemoval();
mSession->AsSecureSession()->MarkForEviction();

#if CONFIG_DEVICE_LAYER && CHIP_DEVICE_CONFIG_ENABLE_SED
// Make sure that the exchange withdraws the request for Sleepy End Device active mode.
Expand Down
2 changes: 1 addition & 1 deletion src/messaging/ExchangeMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ void ExchangeManager::OnMessageReceived(const PacketHeader & packetHeader, const
// If it's not a duplicate message, search for an unsolicited message handler if it is marked as being sent by an initiator.
// Since we didn't find an existing exchange that matches the message, it must be an unsolicited message. However all
// unsolicited messages must be marked as being from an initiator.
if (session->IsActiveSession() && !msgFlags.Has(MessageFlagValues::kDuplicateMessage) && payloadHeader.IsInitiator())
if (!msgFlags.Has(MessageFlagValues::kDuplicateMessage) && payloadHeader.IsInitiator())
{
// Search for an unsolicited message handler that can handle the message. Prefer handlers that can explicitly
// handle the message type over handlers that handle all messages for a profile.
Expand Down
4 changes: 2 additions & 2 deletions src/messaging/tests/MessagingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,15 @@ void MessagingContext::ExpireSessionBobToAlice()
{
if (mSessionBobToAlice)
{
mSessionManager.ExpirePairing(mSessionBobToAlice.Get().Value());
mSessionBobToAlice.Get().Value()->AsSecureSession()->MarkForEviction();
}
}

void MessagingContext::ExpireSessionAliceToBob()
{
if (mSessionAliceToBob)
{
mSessionManager.ExpirePairing(mSessionAliceToBob.Get().Value());
mSessionAliceToBob.Get().Value()->AsSecureSession()->MarkForEviction();
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/messaging/tests/TestExchangeMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ void CheckSessionExpirationBasics(nlTestSuite * inSuite, void * inContext)
ExchangeContext * ec1 = ctx.NewExchangeToBob(&sendDelegate);

// Expire the session this exchange is supposedly on.
ctx.GetSecureSessionManager().ExpirePairing(ec1->GetSessionHandle());
ec1->GetSessionHandle()->AsSecureSession()->MarkForEviction();

MockAppDelegate receiveDelegate;
CHIP_ERROR err =
Expand Down Expand Up @@ -156,7 +156,7 @@ void CheckSessionExpirationTimeout(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, !sendDelegate.IsOnResponseTimeoutCalled);

// Expire the session this exchange is supposedly on. This should close the exchange.
ctx.GetSecureSessionManager().ExpirePairing(ec1->GetSessionHandle());
ec1->GetSessionHandle()->AsSecureSession()->MarkForEviction();
NL_TEST_ASSERT(inSuite, sendDelegate.IsOnResponseTimeoutCalled);

// recreate closed session.
Expand Down
Loading

0 comments on commit efb8a41

Please sign in to comment.