//////////////////////////////////////////////////////////////////////////////// // (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour // //////////////////////////////////////////////////////////////////////////////// #include "ubiservices/sessionManager.h" #include "ubiservices/core/configs/configpriv.h" #include "ubiservices/core/extensions/enumeration.h" #include "ubiservices/core/helpers/instancesManager.h" #include "ubiservices/core/log/remoteLoggerSdk.h" #include "ubiservices/core/notifications/notificationSource.inl" // INTENDED #include "ubiservices/core/tasks/asyncResultInternal.h" #if defined(US_TARGET_XONE) #include "ubiservices/core/tasks/xone/xboxLiveContext.h" #endif #include "ubiservices/facadeInternal.h" #include "ubiservices/services/authentication/authenticationClient.h" #if !defined(US_SDK_SERVER) #include "ubiservices/services/authentication/playerCredentials.h" #endif #if defined(US_MODULE_CLUB) #include "ubiservices/services/club/clubClient.h" #include "ubiservices/services/club/clubNotification.h" #endif #include "ubiservices/services/parameters/featureSwitchId.h" #include "ubiservices/services/parameters/featureSwitchHelper.h" #include "ubiservices/services/parameters/parametersInfoHelper.h" #include "ubiservices/services/notification/notificationClient.h" #include "ubiservices/services/notification/notificationUbiServices.h" #include "ubiservices/services/notification/notificationCustom.h" #include "ubiservices/services/notification/standardTypes.h" #include "ubiservices/services/remoteLog/remoteLoggerCustom.h" #if !defined(US_SDK_SERVER) #include "ubiservices/services/secondaryStore/secondaryStoreClient.h" #include "ubiservices/services/secondaryStore/secondaryStoreNotification.h" #include "ubiservices/services/websocket/websocketConnection.h" #endif namespace US_NS {; SessionManager::RestPeriod::RestPeriod() : m_chrono(ClockSteady::getNanoFromSeconds(1000)) { } SessionManager::SessionManager(FacadeInternal& facade) : m_facade(facade) , m_sessionInfoSnapshot() , m_jobManagerExtendSession(1, "AuthenticationClient-ExtendSession") , m_extendSessionResult("SessionManager async #1") , m_isFirstCallToJobLogin(true) // The following results are used to prevent parallel execution of CreateSession and PopUnsentEvent , m_createSessionResult("SessionManager/CreateSessionResult") , m_executionBlockers() #if !defined(US_SDK_SERVER) , m_webSocketInit(facade) , m_isEventEndOfGame(false) #if defined(US_MODULE_STORE) , m_sessionManagerStore(facade) #endif #if defined(US_MODULE_CLUB) , m_sessionManagerClub() #endif // defined(US_MODULE_CLUB) #if defined(US_TARGET_SWITCH) , m_isUserHandleSet(false) #elif defined(US_TARGET_PS4) || defined(US_TARGET_PS5) , m_isUserIdSet(false) #elif defined(US_TARGET_STADIA) , m_isPlayerIdSet(false) #endif , m_sessionManagerPopulations(facade) , m_connectionMetaData(String()) , m_popUnsentEventsResult("SessionManager/PopUnsentEvenResult") , m_concurrentConnectRestPeriodInProgress(false) #else , m_isCertificatePublicKeySet(false) #endif { } SessionManager::~SessionManager() { } void SessionManager::init() { #if !defined(US_SDK_SERVER) && defined(US_MODULE_STORE) m_sessionManagerStore.init(); #endif } bool SessionManager::hasValidSessionInfoInternal() const { US_PERF_TASK_FUNC_DETAIL(); return m_sessionInfo.isValid(); } const SessionInfo& SessionManager::getSessionInfoRO() const { US_PERF_TASK_FUNC_DETAIL(); return m_sessionInfo; } void SessionManager::invalidateSessionInfo() { US_PERF_TASK_FUNC(); resetSessionInfo(); if (m_extendSessionResult.isProcessing()) { m_extendSessionResult.cancel(); // Stop extend session job } } void SessionManager::resetSessionInfo() { m_sessionInfo = SessionInfo(m_sessionInfo.m_pImpl->m_parametersApplicationReady, m_sessionInfo.m_pImpl->m_parametersSpaceReady); m_sessionInfoSnapshot = US_NEW(SessionInfo, m_sessionInfo); // Refresh snapshot with new changes } void SessionManager::setSessionInfo(const SessionInfo& sessionInfo, bool restartJob) { US_PERF_TASK_FUNC(); #if !defined(US_SDK_SERVER) US_ASSERT(sessionInfo.m_pImpl->m_playerCredentials.isValid(), "An invalid SessionInfo has been passed"); #endif const bool parametersApplicationReadySaved = m_sessionInfo.m_pImpl->m_parametersApplicationReady; const bool parametersSpaceReadySaved = m_sessionInfo.m_pImpl->m_parametersSpaceReady; m_sessionInfo = sessionInfo; m_sessionInfo.m_pImpl->m_parametersApplicationReady = parametersApplicationReadySaved; m_sessionInfo.m_pImpl->m_parametersSpaceReady = parametersSpaceReadySaved; m_sessionInfoSnapshot = US_NEW(SessionInfo, m_sessionInfo); // Refresh snapshot with new changes #if !defined(US_SDK_SERVER) if (sessionInfo.getProfileId().isValid()) //profileInfo is not sent while extending the session. { m_storedProfileId = sessionInfo.getProfileId(); } #endif m_facade.getAuthenticationClient().m_notificationsAuthentication->m_impl->m_notificationsQueue->pushNotification( AuthenticationNotification(AuthenticationNotificationType::SessionInfoChanged)); if (restartJob) { restartExtendSessionJob(); } } void SessionManager::restartExtendSessionJob() { US_PERF_TASK_FUNC(); if(getSwitchesContent().isEnabled(FeatureSwitchId::ExtendSession) && hasValidSessionInfoInternal()) { US_ASSERT(m_sessionInfo.isValid(), "This method shall only be called with a session instance"); m_extendSessionResult.cancel(); // Stop extend session job AsyncResultInternal result("restartExtendSessionJob"); const ClockSteady::TTimeMilli ticketTTL = static_cast(m_sessionInfo.m_pImpl->m_localExpirationTime - ClockSteady::getTimeMilli()); m_jobManagerExtendSession.launch(result, US_NEW(JobExtendSession, result, m_facade, ticketTTL)); m_extendSessionResult = result; } } Environment SessionManager::getDetectedEnvironment(const SessionInfo* sessionInfo) const { US_PERF_TASK_FUNC(); // Search for the environment in session info first if (sessionInfo != nullptr && sessionInfo->isValid()) { return sessionInfo->getEnvironmentCode(); } // If the createSession (or other methods like createAccount) failed, we may have an environment set despite not // having a valid session. This is useful when trying to get the parameters from the correct environment namely if (m_environmentOnError.isSpecified()) { return m_environmentOnError.value(); } // Search for the environment in instance manager const InstancesManager* im = InstancesManager::getInstance(); if (im != nullptr) { return im->getEnvironment(); } US_LOG(LogLevel::Error, LogCategory::Core, "Failed to find the current environment. Returning default value (PROD)."); return Environment::Prod; } void SessionManager::setEnvironmentOnError(Environment environment) { m_environmentOnError = environment; } void SessionManager::unsetEnvironmentOnError() { m_environmentOnError.clear(); } void SessionManager::resetParametersInfo() { US_PERF_TASK_FUNC(); m_parameters = ParametersInfo(); m_sessionInfo.m_pImpl->m_parametersApplicationReady = false; m_sessionInfo.m_pImpl->m_parametersSpaceReady = false; m_sessionInfoSnapshot = US_NEW(SessionInfo, m_sessionInfo); // Refresh snapshot with new changes } void SessionManager::updateParametersSettings() { US_PERF_TASK_FUNC(); // No internal logs for S2S #if !defined(US_SDK_SERVER) // Configure remote logger internal const ParametersRemoteLogsInternal& remoteLogConfigInternal = m_parameters.m_remoteLogParametersInternal; m_facade.getRemoteLoggerSdk().setLogLevelByCategory(remoteLogConfigInternal.m_logLevelByCategory); m_facade.getRemoteLoggerSdk().setTextMaxLength(remoteLogConfigInternal.m_textMaxLength); #endif // Configure remote logger exposed const ParametersRemoteLogsGame& remoteLogConfigExposed = m_parameters.m_paramsRemoteLogExposed; m_facade.getRemoteLoggerCustom().setLogLevelByCategory(remoteLogConfigExposed.m_logLevelByCategory); m_facade.getRemoteLoggerCustom().setTextMaxLength(remoteLogConfigExposed.m_textMaxLength); m_facade.getRemoteLoggerCustom().setUrl(remoteLogConfigExposed.m_url); HttpRetryConfig retryConfigCustom(m_parameters.m_paramsSettingsHttpGame.m_maxCount); retryConfigCustom.m_retryParam = HttpRetryConfig::RetryParam( m_parameters.m_paramsSettingsHttpGame.m_retryInitialDelayMsec.getCount(), m_parameters.m_paramsSettingsHttpGame.m_retryIncrementFactorMsec.getCount(), m_parameters.m_paramsSettingsHttpGame.m_retryRandomDelayMsec.getCount(), m_parameters.m_paramsSettingsHttpGame.m_retryMaxDelayMsec.getCount()); retryConfigCustom.m_timeoutParam = HttpRetryConfig::TimeoutParam( m_parameters.m_paramsSettingsHttpGame.m_timeoutInitialDelayMsec.getCount(), m_parameters.m_paramsSettingsHttpGame.m_timeoutIncrementFactorMsec.getCount()); m_facade.getRemoteLoggerCustom().setHttpRetryConfig(retryConfigCustom); m_sessionInfo.m_pImpl->m_parametersApplicationReady = true; m_sessionInfo.m_pImpl->m_parametersSpaceReady = true; m_sessionInfoSnapshot = US_NEW(SessionInfo, m_sessionInfo); // Refresh snapshot with new changes } bool SessionManager::areParametersGroupsReady() const { return m_sessionInfo.m_pImpl->m_parametersApplicationReady && m_sessionInfo.m_pImpl->m_parametersSpaceReady; } bool SessionManager::isParametersApplicationReady() const { return m_sessionInfo.m_pImpl->m_parametersApplicationReady; } bool SessionManager::isParametersSpaceReady() const { return m_sessionInfo.m_pImpl->m_parametersSpaceReady; } const ParametersInfo& SessionManager::getParametersInfo() const { US_PERF_TASK_FUNC_DETAIL(); //ScopedCS cs(*m_criticalSection); return m_parameters; } ParametersInfo& SessionManager::getParametersInfoRW() { US_PERF_TASK_FUNC_DETAIL(); //ScopedCS cs(*m_criticalSection); return m_parameters; } bool SessionManager::setParametersGroupApplication(const ParametersGroupInfoMap& parametersGroupApplication) { const bool isSuccess = m_parameters.setParametersApplication(parametersGroupApplication, m_applicationSwitches); // The feature switches might have failed, but we can do a best effort regardless. m_killSwitch = FeatureSwitchHelper::mergeFeatureSwitches(m_applicationSwitches, m_spaceSwitches); return isSuccess; } bool SessionManager::setParametersGroupSpace(const ParametersGroupInfoMap& parametersGroupSpace) { const bool isSuccess = m_parameters.setParametersSpace(parametersGroupSpace, m_spaceSwitches); // The feature switches might have failed, but we can do a best effort regardless. m_killSwitch = FeatureSwitchHelper::mergeFeatureSwitches(m_applicationSwitches, m_spaceSwitches); updateParametersSettings(); m_sessionInfo.m_pImpl->m_parametersSpaceReady = true; return isSuccess; } Vector& SessionManager::getParametersGroupListApplicationRW() { return m_parametersGroupListApplication; } const Vector& SessionManager::getParametersGroupListApplication() const { return m_parametersGroupListApplication; } Vector& SessionManager::getParametersGroupListSpaceRW() { return m_parametersGroupListSpace; } const Vector& SessionManager::getParametersGroupListSpace() const { return m_parametersGroupListSpace; } const FeatureSwitch& SessionManager::getSwitchesContent() const { return m_killSwitch; } FeatureSwitch& SessionManager::getSwitchesContentRW() { return m_killSwitch; } void SessionManager::setSwitchesContent(const FeatureSwitch& switches) { US_PERF_TASK_FUNC_DETAIL(); m_killSwitch = switches; } void SessionManager::enableSwitch(FeatureSwitchId feature, bool enableState) { US_PERF_TASK_FUNC_DETAIL(); m_killSwitch.enable(feature, enableState); } SessionManager::RestPeriod& SessionManager::getRestPeriod() { return m_restPeriod; } const SessionManagerOverlay& SessionManager::getSessionManagerOverlay() const { return m_sessionManagerOverlay; } SessionManagerOverlay& SessionManager::getSessionManagerOverlayRW() { return m_sessionManagerOverlay; } bool SessionManager::getIsFirstCallToLogin() { return m_isFirstCallToJobLogin; } void SessionManager::setFirstCallToLoginDone() { m_isFirstCallToJobLogin = false; } bool SessionManager::isLogEnabledSdk(RemoteLogLevel::Enum logLevel, LogCategory logCategory) const { return m_facade.getRemoteLoggerSdk().isLogEnabled(logLevel, logCategory); } bool SessionManager::isLogEnabledCustom(RemoteLogLevel::Enum logLevel, const String& logCategory) const { return m_facade.getRemoteLoggerCustom().isLogEnabled(logLevel, logCategory); } void SessionManager::setWaitRemoteLogCompletionOnDeleteSessionParameter(bool state) { m_parameters.m_paramsSettings.m_waitRemoteLogCompletionOnDeleteSession = state; } SpaceId SessionManager::getWorkingSpaceId() const { return m_workingSpaceId; } void SessionManager::setWorkingSpaceId(const SpaceId& workingSpaceid) { m_workingSpaceId = workingSpaceid; } bool SessionManager::updateWorkingSpaceId(const Map& parameters) { return m_parameters.updateWorkingSpaceId(m_workingSpaceId, parameters); } AsyncResult& SessionManager::getCreateSessionAsyncResultRW() { return m_createSessionResult; } ExecutionBlocker& SessionManager::getExecutionBlocker(MutualExecutionBlocker::BlockingCallType blockingCallType) { return m_executionBlockers[blockingCallType]; } void SessionManager::reduceSessionDuration(uint64 expirationTimeMs) //TODO: REMOVE TEST-ONLY CODE FROM SDK { US_ASSERT(m_sessionInfo.isValid(), "State failure"); m_sessionInfo.m_pImpl->m_localExpirationTime = expirationTimeMs; //m_sessionInfoSnapshot = US_NEW(SessionInfo, m_sessionInfo); // Refresh snapshot with new changes // TODO: REVIEW restartExtendSessionJob(); } #if !defined(US_SDK_SERVER) /////////////////////////////////////////////////////////////////////// // WebSocketInit SessionManager::WebSocketInit::WebSocketInit(FacadeInternal& facade) : m_facade(facade) , m_result("SessionManager async #2") , m_manager(1u, "AuthenticationClient-InitWebSocket") { } void SessionManager::WebSocketInit::launchInitLogin() { AsyncResultInternal initResult("WebSocketInit/Login"); m_manager.launch(initResult, US_NEW(JobInitWebsocket, initResult, m_facade, m_facade.getManager().getParametersInfo().m_paramsSettingsWebsocketInternal)); m_result = initResult; } void SessionManager::WebSocketInit::launchInitResume() { AsyncResultInternal initResult("WebSocketInit/Resume"); m_manager.launch(initResult, US_NEW(JobInitWebsocket, initResult, m_facade, m_facade.getManager().getParametersInfo().m_paramsSettingsWebsocketInternal)); m_result = initResult; } void SessionManager::WebSocketInit::cancelWebsocketInit() { m_result.cancel(); } /////////////////////////////////////////////////////////////////////// SessionManager::WebSocketInit& SessionManager::getWebSocketInitializer() { return m_webSocketInit; } bool SessionManager::hasDefaultSessionInfo() const { US_PERF_TASK_FUNC_DETAIL(); return m_sessionInfo.isDefault(); } const ProfileId& SessionManager::getStoredProfile() const { return m_storedProfileId; } bool SessionManager::hasNotificationTypeChanged() const { US_PERF_TASK_FUNC_DETAIL(); return (m_parameters.m_hasNotificationTypeChanged); } DurationMilli SessionManager::getNotificationUpdateRandomDelay() const { US_PERF_TASK_FUNC_DETAIL(); return m_parameters.m_paramsSettings.m_notificationTypesUpdateRandomDelayMsec; } Vector SessionManager::mergeNotificationTypes(const Vector& types1, const Vector& types2) const { US_PERF_TASK_FUNC_DETAIL(); Vector result; result.reserve(types1.size() + types2.size()); result.insert(result.end(), types1.begin(), types1.end()); result.insert(result.end(), types2.begin(), types2.end()); return result; } // Returns a map that contains the name of the group and parameterGroupInfo with m_fields containing the information to be updated after matching the populations. Map SessionManager::getUpdatedParameterGroupInfo(const ParametersGroupUpdate& parametersGroupUpdate) { US_PERF_TASK_FUNC_DETAIL(); Map parametersGroup; const auto& cachePointer = getSessionManagerPopulations().getPopulationsCache(); if (!cachePointer.isValid()) { US_LOG(LogLevel::Warning, LogCategory::Parameters, "Couldn't find population when receiving parameters partial update. No update is done."); return parametersGroup; } const auto& cache = cachePointer->m_cache; const auto populationsIterator = cache.find(m_facade.getManager().getWorkingSpaceId()); if (populationsIterator == cache.end()) { return parametersGroup; } String finalPopulation = "noPopulation"; for (const auto& population : populationsIterator->second) { const String fullName = population.m_subject + "." + population.m_name; auto populationIt = parametersGroupUpdate.populations.find(fullName); if (populationIt != parametersGroupUpdate.populations.end()) { finalPopulation = fullName; break; } } const auto parametersGroupUpdateIterator = parametersGroupUpdate.populations.find(finalPopulation); if (parametersGroupUpdateIterator != parametersGroupUpdate.populations.end()) { ParametersGroupInfo group; group.m_fields = parametersGroupUpdateIterator->second; parametersGroup[parametersGroupUpdate.name] = group; } return parametersGroup; } bool SessionManager::updateApplicationGroupParameters(const ParametersGroupUpdate& parametersGroupUpdate) { US_PERF_TASK_FUNC_DETAIL(); const Map& groupUpdate = getUpdatedParameterGroupInfo(parametersGroupUpdate); if (groupUpdate.empty()) { return false; } const bool isSuccess = m_parameters.updateApplicationParametersGroup(groupUpdate, m_applicationSwitches); m_killSwitch = FeatureSwitchHelper::mergeFeatureSwitches(m_applicationSwitches, m_spaceSwitches); return isSuccess; } bool SessionManager::updateSpaceGroupParameters(const ParametersGroupUpdate& parametersGroupUpdate) { US_PERF_TASK_FUNC_DETAIL(); const Map& groupUpdate = getUpdatedParameterGroupInfo(parametersGroupUpdate); if (groupUpdate.empty()) { return false; } const bool isSuccess = m_parameters.updateSpaceParametersGroup(groupUpdate, m_spaceSwitches); m_killSwitch = FeatureSwitchHelper::mergeFeatureSwitches(m_applicationSwitches, m_spaceSwitches); updateParametersSettings(); return isSuccess; } const SessionManagerPopulations& SessionManager::getSessionManagerPopulations() const { return m_sessionManagerPopulations; } SessionManagerPopulations& SessionManager::getSessionManagerPopulationsRW() { return m_sessionManagerPopulations; } const SmartPtr& SessionManager::getStoredCredentials() const { return m_storedCredentials; } SmartPtr& SessionManager::getStoredCredentialsRW() { return m_storedCredentials; } void SessionManager::setStoredCredentials(const PlayerCredentials& credentials) { m_storedCredentials = US_NEW(PlayerCredentials, credentials); #if defined(US_TARGET_SWITCH) setStoredUserHandle(m_storedCredentials->getHandle()); #elif defined(US_TARGET_PS4) || defined(US_TARGET_PS5) setStoredUserId(m_storedCredentials->getUserId()); #endif } bool SessionManager::isEventEndOfGame() const { return m_isEventEndOfGame; } void SessionManager::setEventEndOfGame() { // Note : Once this flag is set to true, it shouldn't go back to false // The event system cannot be restarted to ensure the events are sent in the correct order to the servers. m_isEventEndOfGame = true; } const WebSocketConnectionPtr& SessionManager::getConnection() const { return m_connection; } WebSocketConnectionPtr& SessionManager::getConnection() { return m_connection; } String SessionManager::getConnectionMetaData(void) const { return m_connectionMetaData; } void SessionManager::setConnectionMetaData(const String& connectionMetaData) { m_connectionMetaData = const_cast(connectionMetaData); } AsyncResult& SessionManager::getPopUnsentEventsAsyncResultRW() { return m_popUnsentEventsResult; } #if defined(US_MODULE_CLUB) const SessionManagerClub& SessionManager::getSessionManagerClub() const { return m_sessionManagerClub; } SessionManagerClub& SessionManager::getSessionManagerClubRW() { return m_sessionManagerClub; } #endif // defined(US_MODULE_CLUB) #if defined(US_MODULE_STORE) const SessionManagerStore& SessionManager::getSessionManagerStore() const { return m_sessionManagerStore; } SessionManagerStore& SessionManager::getSessionManagerStoreRW() { return m_sessionManagerStore; } #endif // defined(US_MODULE_STORE) #if defined(US_TARGET_SWITCH) void SessionManager::setStoredUserHandle(const nn::account::UserHandle& userHandle) { m_userHandle = userHandle; m_isUserHandleSet = true; } const nn::account::UserHandle& SessionManager::getStoredUserHandle() const { return m_userHandle; } const bool SessionManager::isUserHandleSet() const { return m_isUserHandleSet; } #elif defined(US_TARGET_PS4) || defined(US_TARGET_PS5) void SessionManager::setStoredUserId(const SceUserServiceUserId& userId) { m_userId = userId; m_isUserIdSet = true; } const SceUserServiceUserId& SessionManager::getStoredUserId() const { return m_userId; } const bool SessionManager::isUserIdSet() const { return m_isUserIdSet; } #elif defined(US_TARGET_STADIA) void SessionManager::setStoredPlayerId(const ggp::PlayerId& playerId) { m_playerId = playerId; m_isPlayerIdSet = true; } const ggp::PlayerId& SessionManager::getStoredPlayerId() const { return m_playerId; } const bool SessionManager::isPlayerIdSet() const { return m_isPlayerIdSet; } #endif void SessionManager::setConcurrentConnectRestPeriodInProgress(bool enabled) { m_concurrentConnectRestPeriodInProgress = enabled; } const bool SessionManager::isConcurrentConnectRestPeriodInProgress() { return m_concurrentConnectRestPeriodInProgress; } #elif defined(US_SDK_SERVER) void SessionManager::setUbiCertificateKeyPublic(const String& ubiCertificateKeyPublic) { m_certificateKeyPublic = ubiCertificateKeyPublic; m_isCertificatePublicKeySet = true; } const String SessionManager::getUbiCertificateKeyPublic() const { return m_certificateKeyPublic; } void SessionManager::setUbiCertificateKeyPrivateObfuscated(const Vector& ubiCertificateKeyPrivateObfuscated) { m_certificateKeyPrivateObfuscated = ubiCertificateKeyPrivateObfuscated; } const Vector& SessionManager::getUbiCertificateKeyPrivateObfuscated() const { return m_certificateKeyPrivateObfuscated; } void SessionManager::setServerCreateSessionOverrideSpaceId(const SpaceId& spaceId) { m_serverCreateSessionOverrideSpaceId = spaceId; } const SpaceId& SessionManager::getServerCreateSessionOverrideSpaceId() const { return m_serverCreateSessionOverrideSpaceId; } const bool SessionManager::isCertificatePublicSet() const { return m_isCertificatePublicKeySet; } #endif };