diff --git a/src/app/CASESessionManager.cpp b/src/app/CASESessionManager.cpp index 52c48f102672b3..382d2620fcfa6a 100644 --- a/src/app/CASESessionManager.cpp +++ b/src/app/CASESessionManager.cpp @@ -36,6 +36,56 @@ void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Cal uint8_t attemptCount, Callback::Callback * onRetry #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES ) +{ + FindOrEstablishSessionHelper(peerId, onConnection, onFailure, nullptr +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + attemptCount, onRetry +#endif + ); +} + +void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Callback::Callback * onConnection, + Callback::Callback * onSetupFailure +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + uint8_t attemptCount, Callback::Callback * onRetry +#endif +) +{ + FindOrEstablishSessionHelper(peerId, onConnection, nullptr, onSetupFailure +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + attemptCount, onRetry +#endif + ); +} + +void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Callback::Callback * onConnection, + nullptr_t +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + uint8_t attemptCount, Callback::Callback * onRetry +#endif +) +{ + FindOrEstablishSessionHelper(peerId, onConnection, nullptr, nullptr +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + attemptCount, onRetry +#endif + ); +} + +void CASESessionManager::FindOrEstablishSessionHelper(const ScopedNodeId & peerId, + Callback::Callback * onConnection, + Callback::Callback * onFailure, + Callback::Callback * onSetupFailure +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + uint8_t attemptCount, Callback::Callback * onRetry +#endif +) { ChipLogDetail(CASESessionManager, "FindOrEstablishSession: PeerId = [%d:" ChipLogFormatX64 "]", peerId.GetFabricIndex(), ChipLogValueX64(peerId.GetNodeId())); @@ -45,7 +95,6 @@ void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Cal if (session == nullptr) { ChipLogDetail(CASESessionManager, "FindOrEstablishSession: No existing OperationalSessionSetup instance found"); - session = mConfig.sessionSetupPool->Allocate(mConfig.sessionInitParams, mConfig.clientPool, peerId, this); if (session == nullptr) @@ -54,6 +103,13 @@ void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Cal { onFailure->mCall(onFailure->mContext, peerId, CHIP_ERROR_NO_MEMORY); } + + if (onSetupFailure != nullptr) + { + OperationalSessionSetup::ConnnectionFailureInfo failureInfo(peerId, CHIP_ERROR_NO_MEMORY, + SessionEstablishmentStage::kUnknown); + onSetupFailure->mCall(onSetupFailure->mContext, failureInfo); + } return; } } @@ -66,7 +122,15 @@ void CASESessionManager::FindOrEstablishSession(const ScopedNodeId & peerId, Cal } #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES - session->Connect(onConnection, onFailure); + if (onFailure != nullptr) + { + session->Connect(onConnection, onFailure); + } + + if (onSetupFailure != nullptr) + { + session->Connect(onConnection, onSetupFailure); + } } void CASESessionManager::ReleaseSessionsForFabric(FabricIndex fabricIndex) diff --git a/src/app/CASESessionManager.h b/src/app/CASESessionManager.h index 7094c88f42e104..92fb7a9ba783c7 100644 --- a/src/app/CASESessionManager.h +++ b/src/app/CASESessionManager.h @@ -87,6 +87,63 @@ class CASESessionManager : public OperationalSessionReleaseDelegate, public Sess #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES ); + /** + * Find an existing session for the given node ID or trigger a new session request. + * + * The caller can optionally provide `onConnection` and `onSetupFailure` + * callback objects. If provided, these will be used to inform the caller about successful or + * failed connection establishment. + * + * If the connection is already established, the `onConnection` callback will be immediately called, + * before `FindOrEstablishSession` returns. + * + * The `onSetupFailure` callback may be called before the `FindOrEstablishSession` + * call returns, for error cases that are detected synchronously. + * + * The `attemptCount` parameter can be used to automatically retry multiple times if session setup is + * not successful. + * + * @param peerId The node ID to find or establish a session with. + * @param onConnection A callback to be called upon successful connection establishment. + * @param onSetupFailure A callback to be called upon an extended device connection failure. + * @param attemptCount The number of retry attempts if session setup fails (default is 1). + * @param onRetry A callback to be called on a retry attempt (enabled by a config flag). + */ + void FindOrEstablishSession(const ScopedNodeId & peerId, Callback::Callback * onConnection, + Callback::Callback * onSetupFailure +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + uint8_t attemptCount = 1, Callback::Callback * onRetry = nullptr +#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + ); + + /** + * Find an existing session for the given node ID or trigger a new session request. + * + * The caller can optionally provide `onConnection` + * callback objects. If provided, these will be used to inform the caller about successful connection establishment. + * + * If the connection is already established, the `onConnection` callback will be immediately called, + * before `FindOrEstablishSession` returns. + * + * The `attemptCount` parameter can be used to automatically retry multiple times if session setup is + * not successful. + * + * This function allows passing 'nullptr' for the error handler to compile, which is useful in scenarios where error + * handling is not needed. + * + * @param peerId The node ID to find or establish a session with. + * @param onConnection A callback to be called upon successful connection establishment. + * @param attemptCount The number of retry attempts if session setup fails (default is 1). + * @param onRetry A callback to be called on a retry attempt (enabled by a config flag). + */ + void FindOrEstablishSession(const ScopedNodeId & peerId, Callback::Callback * onConnection, nullptr_t +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + , + uint8_t attemptCount = 1, Callback::Callback * onRetry = nullptr +#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + ); + void ReleaseSessionsForFabric(FabricIndex fabricIndex); void ReleaseAllSessions(); @@ -112,6 +169,14 @@ class CASESessionManager : public OperationalSessionReleaseDelegate, public Sess Optional FindExistingSession(const ScopedNodeId & peerId) const; + void FindOrEstablishSessionHelper(const ScopedNodeId & peerId, Callback::Callback * onConnection, + Callback::Callback * onFailure, + Callback::Callback * onSetupFailure, +#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES + uint8_t attemptCount, Callback::Callback * onRetry +#endif + ); + CASESessionManagerConfig mConfig; }; diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 2007d8424b2f88..a7e6f6dac4bcb9 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -220,11 +220,11 @@ class DLL_EXPORT DeviceController : public AbstractDnssdDiscoveryController * This function finds the device corresponding to deviceId, and establishes * a CASE session with it. * - * Once the CASE session is successfully established the `onConnectedDevice` + * Once the CASE session is successfully established the `onConnection` * callback is called. This can happen before GetConnectedDevice returns if * there is an existing CASE session. * - * If a CASE sessions fails to be established, the `onError` callback will + * If a CASE sessions fails to be established, the `onFailure` callback will * be called. This can also happen before GetConnectedDevice returns. * * An error return from this function means that neither callback has been @@ -238,6 +238,30 @@ class DLL_EXPORT DeviceController : public AbstractDnssdDiscoveryController return CHIP_NO_ERROR; } + /** + * This function finds the device corresponding to deviceId, and establishes + * a CASE session with it. + * + * Once the CASE session is successfully established the `onConnection` + * callback is called. This can happen before GetConnectedDevice returns if + * there is an existing CASE session. + * + * If a CASE sessions fails to be established, the `onSetupFailure` callback will + * be called. This can also happen before GetConnectedDevice returns. + * + * An error return from this function means that neither callback has been + * called yet, and neither callback will be called in the future. + */ + CHIP_ERROR + GetConnectedDevice(NodeId peerNodeId, Callback::Callback * onConnection, + chip::Callback::Callback * onSetupFailure) + { + VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE); + mSystemState->CASESessionMgr()->FindOrEstablishSession(ScopedNodeId(peerNodeId, GetFabricIndex()), onConnection, + onSetupFailure); + return CHIP_NO_ERROR; + } + /** * @brief * Compute a PASE verifier and passcode ID for the desired setup pincode. diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index 85c948404343ab..b19e50b24e06f6 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -15,6 +15,7 @@ * limitations under the License. */ #include "AndroidCallbacks.h" +#include #include #ifdef USE_JAVA_TLV_ENCODE_DECODE #include @@ -114,7 +115,8 @@ void GetConnectedDeviceCallback::OnDeviceConnectedFn(void * context, Messaging:: VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) +void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, + const OperationalSessionSetup::ConnnectionFailureInfo & failureInfo) { JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); @@ -135,15 +137,15 @@ void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, con VerifyOrReturn(failureMethod != nullptr, ChipLogError(Controller, "Could not find onConnectionFailure method")); jthrowable exception; - CHIP_ERROR err = AndroidControllerExceptions::GetInstance().CreateAndroidControllerException(env, ErrorStr(error), - error.AsInteger(), exception); + CHIP_ERROR err = AndroidConnectionFailureExceptions::GetInstance().CreateAndroidConnectionFailureException( + env, failureInfo.error.Format(), failureInfo.error.AsInteger(), failureInfo.sessionStage, exception); VerifyOrReturn( err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create AndroidControllerException on GetConnectedDeviceCallback::OnDeviceConnectionFailureFn: %s", ErrorStr(err))); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, failureMethod, peerId.GetNodeId(), exception); + env->CallVoidMethod(javaCallback, failureMethod, failureInfo.peerId.GetNodeId(), exception); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h index b622fc3294832e..f00075258bea8f 100644 --- a/src/controller/java/AndroidCallbacks.h +++ b/src/controller/java/AndroidCallbacks.h @@ -39,10 +39,10 @@ struct GetConnectedDeviceCallback ~GetConnectedDeviceCallback(); static void OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle); - static void OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error); + static void OnDeviceConnectionFailureFn(void * context, const OperationalSessionSetup::ConnnectionFailureInfo & failureInfo); Callback::Callback mOnSuccess; - Callback::Callback mOnFailure; + Callback::Callback mOnFailure; JniGlobalReference mWrapperCallbackRef; JniGlobalReference mJavaCallbackRef; }; diff --git a/src/controller/java/AndroidConnectionFailureExceptions.cpp b/src/controller/java/AndroidConnectionFailureExceptions.cpp new file mode 100644 index 00000000000000..1e4bcd4a452ac1 --- /dev/null +++ b/src/controller/java/AndroidConnectionFailureExceptions.cpp @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AndroidConnectionFailureExceptions.h" + +#include +#include +#include +#include + +namespace chip { + +CHIP_ERROR AndroidConnectionFailureExceptions::CreateAndroidConnectionFailureException(JNIEnv * env, const char * message, + uint32_t errorCode, + SessionEstablishmentStage state, + jthrowable & outEx) +{ + jclass controllerExceptionCls; + CHIP_ERROR err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/ConnectionFailureException", + controllerExceptionCls); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_JNI_ERROR_TYPE_NOT_FOUND); + + jmethodID exceptionConstructor = env->GetMethodID(controllerExceptionCls, "", "(JILjava/lang/String;)V"); + outEx = static_cast(env->NewObject(controllerExceptionCls, exceptionConstructor, static_cast(errorCode), + static_cast(state), env->NewStringUTF(message))); + VerifyOrReturnError(outEx != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/controller/java/AndroidConnectionFailureExceptions.h b/src/controller/java/AndroidConnectionFailureExceptions.h new file mode 100644 index 00000000000000..54baacac9e3326 --- /dev/null +++ b/src/controller/java/AndroidConnectionFailureExceptions.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +class AndroidConnectionFailureExceptions +{ +public: + AndroidConnectionFailureExceptions(const AndroidConnectionFailureExceptions &) = delete; + AndroidConnectionFailureExceptions(const AndroidConnectionFailureExceptions &&) = delete; + AndroidConnectionFailureExceptions & operator=(const AndroidConnectionFailureExceptions &) = delete; + + static AndroidConnectionFailureExceptions & GetInstance() + { + static AndroidConnectionFailureExceptions androidConnectionFailureExceptions; + return androidConnectionFailureExceptions; + } + + /** + * Creates a Java ConnectionFailureException object in outEx. + */ + CHIP_ERROR CreateAndroidConnectionFailureException(JNIEnv * env, const char * message, uint32_t errorCode, + SessionEstablishmentStage state, jthrowable & outEx); + +private: + AndroidConnectionFailureExceptions() {} +}; +} // namespace chip diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index a538afefc744c5..e481e74feeda1c 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -45,6 +45,8 @@ shared_library("jni") { "AndroidClusterExceptions.h", "AndroidCommissioningWindowOpener.cpp", "AndroidCommissioningWindowOpener.h", + "AndroidConnectionFailureExceptions.cpp", + "AndroidConnectionFailureExceptions.h", "AndroidControllerExceptions.cpp", "AndroidControllerExceptions.h", "AndroidCurrentFabricRemover.cpp", @@ -439,6 +441,7 @@ android_library("java") { "src/chip/devicecontroller/ChipCommandType.java", "src/chip/devicecontroller/ChipDeviceController.java", "src/chip/devicecontroller/ChipDeviceControllerException.java", + "src/chip/devicecontroller/ConnectionFailureException.java", "src/chip/devicecontroller/ControllerParams.java", "src/chip/devicecontroller/DeviceAttestationDelegate.java", "src/chip/devicecontroller/DiscoveredDevice.java", diff --git a/src/controller/java/src/chip/devicecontroller/ConnectionFailureException.java b/src/controller/java/src/chip/devicecontroller/ConnectionFailureException.java new file mode 100644 index 00000000000000..4b5121bdcc6331 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ConnectionFailureException.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 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. + * + */ +package chip.devicecontroller; + +@SuppressWarnings("serial") +public class ConnectionFailureException extends ChipDeviceControllerException { + public enum ConnectionState { + UNKNOWN, // Unknown state + NOT_IN_KEY_EXCHANGE, // Not currently in key exchange process + SENT_SIGMA1, // Sigma1 message sent + RECEIVED_SIGMA1, // Sigma1 message received + SENT_SIGMA2, // Sigma2 message sent + RECEIVED_SIGMA2, // Sigma2 message received + SENT_SIGMA3, // Sigma3 message sent + RECEIVED_SIGMA3 // Sigma3 message received + } + + private ConnectionState connectionState; + + public ConnectionFailureException() { + super(); + } + + public ConnectionFailureException(long errorCode, int connectionState, String message) { + super(errorCode, message); + this.connectionState = mapIntToConnectionState(connectionState); + } + + public ConnectionState getConnectionState() { + return connectionState; + } + + // Helper method to map an int to ConnectionState enum + private ConnectionState mapIntToConnectionState(int value) { + switch (value) { + case 0: + return ConnectionState.UNKNOWN; + case 1: + return ConnectionState.NOT_IN_KEY_EXCHANGE; + case 2: + return ConnectionState.SENT_SIGMA1; + case 3: + return ConnectionState.RECEIVED_SIGMA1; + case 4: + return ConnectionState.SENT_SIGMA2; + case 5: + return ConnectionState.RECEIVED_SIGMA2; + case 6: + return ConnectionState.SENT_SIGMA3; + case 7: + return ConnectionState.RECEIVED_SIGMA3; + default: + throw new IllegalArgumentException("Invalid connection state value: " + value); + } + } +}