diff -r 000000000000 -r 307788aac0a8 realtimenetprots/sipfw/ProfileAgent/IMS_Agent/Src/CSIPRegEventSubscriber.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/realtimenetprots/sipfw/ProfileAgent/IMS_Agent/Src/CSIPRegEventSubscriber.cpp Tue Feb 02 01:03:15 2010 +0200 @@ -0,0 +1,732 @@ +// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// Name : CSIPRegEventSubscriber.cpp +// Part of : SIP Profile Agent +// implementation +// Version : 1.0 +// + + + + +// INCLUDE FILES +#include "CSIPRegEventSubscriber.h" +#include "CSIPNotifyXmlBodyParser.h" +#include "sipreginfoelement.h" +#include "TSIPRegEventStateNotSubscribed.h" +#include "TSIPRegEventStateSubscribing.h" +#include "TSIPRegEventStateSubscribed.h" +#include "TSIPRegEventStateReSubscribing.h" +#include "sipfromheader.h" +#include "siptoheader.h" +#include "sipeventheader.h" +#include "siprefresh.h" +#include "sipmessageelements.h" +#include "sipexpiresheader.h" +#include "sipacceptheader.h" +#include "sipcontenttypeheader.h" +#include "sipsubscriptionstateheader.h" +#include "sipretryafterheader.h" +#include "sipresponseelements.h" +#include "siperr.h" +#include "sipstrings.h" +#include "sipstrconsts.h" +#include + + +_LIT8(KRegEventName, "reg"); +_LIT8(KAcceptType, "application"); +_LIT8(KAcceptSubtype, "reginfo+xml"); +_LIT8(KStateActive, "active"); +_LIT8(KStatePending, "pending"); +_LIT8(KStateTerminated, "terminated"); +_LIT8(KReasonProbation, "probation"); +_LIT8(KReasonRejected, "rejected"); +_LIT8(KReasonGiveup, "giveup"); +_LIT8(KReasonNoresource, "noresource"); + + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::NewL +// ----------------------------------------------------------------------------- +// +CSIPRegEventSubscriber* CSIPRegEventSubscriber::NewL( + CSIPNotifyXmlBodyParser& aXmlParser, + CSIPConnection& aConnection, + MSIPRegistrationContext& aContext, + CUri8& aUserIdentity, + MSIPRegEventSubscriberObserver& aObserver, + CSipProfileAgentConfigExtension& aConfigExtension) + { + CSIPRegEventSubscriber* self = + NewLC(aXmlParser,aConnection,aContext,aUserIdentity,aObserver,aConfigExtension); + CleanupStack::Pop(); + return self; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::NewLC +// ----------------------------------------------------------------------------- +// +CSIPRegEventSubscriber* CSIPRegEventSubscriber::NewLC( + CSIPNotifyXmlBodyParser& aXmlParser, + CSIPConnection& aConnection, + MSIPRegistrationContext& aContext, + CUri8& aUserIdentity, + MSIPRegEventSubscriberObserver& aObserver, + CSipProfileAgentConfigExtension& aConfigExtension) + { + CSIPRegEventSubscriber* self = + new(ELeave)CSIPRegEventSubscriber( + aXmlParser,aConnection,aContext,aUserIdentity,aObserver,aConfigExtension); + CleanupStack::PushL(self); + self->ConstructL(); + return self; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::CSIPRegEventSubscriber +// ----------------------------------------------------------------------------- +// +CSIPRegEventSubscriber::CSIPRegEventSubscriber( + CSIPNotifyXmlBodyParser& aXmlParser, + CSIPConnection& aConnection, + MSIPRegistrationContext& aContext, + CUri8& aUserIdentity, + MSIPRegEventSubscriberObserver& aObserver, + CSipProfileAgentConfigExtension& aConfigExtension) + : iStates(MSIPRegEventContext::EMaxStates), + iCurrentState(MSIPRegEventContext::ENotSubscribed), + iXmlParser(aXmlParser), + iConnection(aConnection), + iRegistrationContext(aContext), + iUserIdentity(aUserIdentity), + iObserver(aObserver), + iConfigExtension(aConfigExtension) + { + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ConstructL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ConstructL() + { + InitializeStatesL(); + iReasonProbation = SIPStrings::Pool().OpenFStringL(KReasonProbation); + iReasonRejected = SIPStrings::Pool().OpenFStringL(KReasonRejected); + iReasonGiveup = SIPStrings::Pool().OpenFStringL(KReasonGiveup); + iReasonNoresource = SIPStrings::Pool().OpenFStringL(KReasonNoresource); + iSubscribeAssoc = CreateSubscribeAssocL(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::InitializeStatesL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::InitializeStatesL() + { + iStates.AppendL(TSIPRegEventStateNotSubscribed(*this), + sizeof(TSIPRegEventStateNotSubscribed)); + + iStates.AppendL(TSIPRegEventStateSubscribing(*this), + sizeof(TSIPRegEventStateSubscribing)); + + iStates.AppendL(TSIPRegEventStateSubscribed(*this), + sizeof(TSIPRegEventStateSubscribed)); + + iStates.AppendL(TSIPRegEventStateReSubscribing(*this), + sizeof(TSIPRegEventStateReSubscribing)); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::~CSIPRegEventSubscriber +// ----------------------------------------------------------------------------- +// +CSIPRegEventSubscriber::~CSIPRegEventSubscriber() + { + delete iSubscribeAssoc; + delete iClientTransaction; + iReasonProbation.Close(); + iReasonRejected.Close(); + iReasonGiveup.Close(); + iReasonNoresource.Close(); + delete iCurrentRegInfoElement; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::SubscribeL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::SubscribeL() + { + CurrentState().SubscribeL(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::DoSubscribeL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::DoSubscribeL() + { + CSIPRefresh* refresh = CSIPRefresh::NewLC(); + CSIPMessageElements* message = CreateMessageElementsLC( + iConfigExtension.ExpiryValueL(TSIPProfileTypeInfo::EIms, + CSipProfileAgentConfigExtension::EProfileSubscriptionValue)); + + CSIPClientTransaction* transaction = + iSubscribeAssoc->SendSubscribeL(message,refresh); + + CleanupStack::Pop(message); + CleanupStack::Pop(refresh); + delete iClientTransaction; + iClientTransaction = transaction; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ReSubscribeL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ReSubscribeL() + { + CSIPSubscribeDialogAssoc* subscribeAssoc = CreateSubscribeAssocL(); + CleanupStack::PushL(subscribeAssoc); + + CSIPRefresh* refresh = CSIPRefresh::NewLC(); + CSIPMessageElements* message = CreateMessageElementsLC( + iConfigExtension.ExpiryValueL(TSIPProfileTypeInfo::EIms, + CSipProfileAgentConfigExtension::EProfileSubscriptionValue)); + + CSIPClientTransaction* transaction = + subscribeAssoc->SendSubscribeL(message,refresh); + + CleanupStack::Pop(message); + CleanupStack::Pop(refresh); + delete iClientTransaction; + iClientTransaction = transaction; + + CleanupStack::Pop(subscribeAssoc); + delete iSubscribeAssoc; + iSubscribeAssoc = subscribeAssoc; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::RefreshL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::RefreshL() + { + CurrentState().RefreshL(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::DoRefreshL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::DoRefreshL() + { + CSIPMessageElements* message = CreateMessageElementsLC( + iConfigExtension.ExpiryValueL(TSIPProfileTypeInfo::EIms, + CSipProfileAgentConfigExtension::EProfileSubscriptionValue)); + + CSIPClientTransaction* transaction = iSubscribeAssoc->UpdateL(message); + + CleanupStack::Pop(message); + delete iClientTransaction; + iClientTransaction = transaction; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ReSubscribeAfterL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ReSubscribeAfterL(TUint aRetryAfter) + { + iObserver.SubscriptionFailedL(aRetryAfter); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::SubscriptionFailedL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::SubscriptionFailedL() + { + iObserver.SubscriptionFailedL(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::RegEventNotSupportedByNetworkL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::RegEventNotSupportedByNetworkL() + { + iObserver.RegEventNotSupportedByNetworkL(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ReRegister +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ReRegister() + { + iObserver.ReRegister(); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ChangeState +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ChangeState(TState aNewState) + { + iCurrentState = aNewState; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HasTransaction +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::HasTransaction( + const CSIPTransactionBase& aTransaction) const + { + return (iClientTransaction && aTransaction == *iClientTransaction); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HasRefresh +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::HasRefresh( + const CSIPRefresh& aRefresh) const + { + return (*iSubscribeAssoc->SIPRefresh() == aRefresh); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HasDialog +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::HasDialog(const CSIPDialog& aDialog) const + { + return (aDialog == iSubscribeAssoc->Dialog()); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::RequestReceivedL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::RequestReceivedL( + CSIPServerTransaction* aTransaction, + CSIPDialog& aDialog) + { + if (HasDialog(aDialog)) + { + __ASSERT_ALWAYS(aTransaction, User::Leave(KErrCorrupt)); + __ASSERT_ALWAYS(aTransaction->RequestElements() != NULL, + User::Leave(KErrCorrupt)); + const CSIPRequestElements& request = *(aTransaction->RequestElements()); + CSIPResponseElements* response = HandleReceivedRequestLC(request); + if (response->StatusCode() == 200) + { + TRAPD(err, ParseXmlL(request)); + if (err == KErrNone) + { + TBool terminated = EFalse; + TInt retryAfter = 0; + HandleSubscriptionStateL(request,terminated,retryAfter); + aTransaction->SendResponseL(response); + CleanupStack::Pop(response); + if (terminated) + { + iObserver.TerminatedL(*iCurrentRegInfoElement,retryAfter); + } + else + { + iObserver.NotifyReceivedL(*iCurrentRegInfoElement); + } + } + else if (err == KErrNoMemory) + { + User::Leave(err); + } + else + { + response->SetStatusCodeL(400); + response->SetReasonPhraseL( + SIPStrings::StringF(SipStrConsts::EPhraseBadRequest)); + aTransaction->SendResponseL(response); + CleanupStack::Pop(response); + } + } + else + { + aTransaction->SendResponseL(response); + CleanupStack::Pop(response); + } + delete aTransaction; + } + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ResponseReceivedL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ResponseReceivedL( + CSIPClientTransaction& aTransaction) + { + if (HasTransaction(aTransaction)) + { + const CSIPResponseElements* response = aTransaction.ResponseElements(); + __ASSERT_ALWAYS(response != NULL, User::Leave(KErrCorrupt)); + HandleReceivedResponseL(*response); + } + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ErrorOccured +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ErrorOccured( + TInt /*aError*/, + CSIPTransactionBase& aTransaction) + { + if (HasTransaction(aTransaction)) + { + TRAP_IGNORE(CurrentState().ErrorOccuredL()) + } + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ErrorOccured +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ErrorOccured(TInt aError, + CSIPDialog& aDialog) + { + if (HasDialog(aDialog)) + { + const CSIPClientTransaction* transaction = + iSubscribeAssoc->SIPRefresh()->SIPTransaction(); + if (transaction && aError == KErrSIPTerminatedWithResponse) + { + TUint statusCode = transaction->ResponseElements()->StatusCode(); + TUint after = RetryAfterValue(*transaction->ResponseElements()); + TRAP_IGNORE(CurrentState().ResponseReceivedL(statusCode,after)) + } + else + { + TRAP_IGNORE(CurrentState().ErrorOccuredL()) + } + } + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HandleReceivedRequestLC +// ----------------------------------------------------------------------------- +// +CSIPResponseElements* CSIPRegEventSubscriber::HandleReceivedRequestLC( + const CSIPRequestElements& aRequest) + { + if (!MethodOK(aRequest) || + !ContentTypeOK(aRequest) || + !SubscriptionStateOK(aRequest)) + { + return CSIPResponseElements::NewLC(400, + SIPStrings::StringF(SipStrConsts::EPhraseBadRequest)); + } + + if (!EventOK(aRequest)) + { + return CSIPResponseElements::NewLC(489, + SIPStrings::StringF(SipStrConsts::EPhraseBadEvent)); + } + + return CSIPResponseElements::NewLC(200, + SIPStrings::StringF(SipStrConsts::EPhraseOk)); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HandleReceivedResponseL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::HandleReceivedResponseL( + const CSIPResponseElements& aResponse) + { + TUint statusCode = aResponse.StatusCode(); + if (statusCode >= 200) + { + TUint retryAfter = RetryAfterValue(aResponse); + delete iClientTransaction; + iClientTransaction = NULL; + CurrentState().ResponseReceivedL(statusCode,retryAfter); + } + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::RetryAfterValue +// ----------------------------------------------------------------------------- +// +TUint CSIPRegEventSubscriber::RetryAfterValue( + const CSIPResponseElements& aResponse) const + { + TUint retryAfter = 0; + CSIPRetryAfterHeader* retryAfterHeader = + static_cast( + FindHeader(aResponse.MessageElements(), + SIPStrings::StringF(SipStrConsts::ERetryAfterHeader))); + if (retryAfterHeader) + { + retryAfter = retryAfterHeader->RetryAfter(); + } + return retryAfter; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ParseXmlL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::ParseXmlL(const CSIPRequestElements& aRequest) + { + CSIPRegInfoElement* regInfoElement = CSIPRegInfoElement::NewLC(); + iXmlParser.ParseL(regInfoElement,aRequest.MessageElements().Content()); + CleanupStack::Pop(regInfoElement); + delete iCurrentRegInfoElement; + iCurrentRegInfoElement = regInfoElement; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HandleSubscriptionStateL +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::HandleSubscriptionStateL( + const CSIPRequestElements& aRequest, + TBool& aTerminated, + TInt& aRetryAfter) + { + TBool terminated = EFalse; + + CSIPHeaderBase* header = + FindHeader(aRequest.MessageElements(), + SIPStrings::StringF(SipStrConsts::ESubscriptionStateHeader)); + + __ASSERT_ALWAYS(header != NULL, User::Leave(KErrCorrupt)); + + CSIPSubscriptionStateHeader* ssHeader = + static_cast(header); + + CSIPRefresh* refresh = + const_cast(iSubscribeAssoc->SIPRefresh()); + + TPtrC8 state(ssHeader->SubStateValue()); + + if (state.CompareF(KStateActive) == 0 || + state.CompareF(KStatePending) == 0) + { + TInt expires = ssHeader->ExpiresParameter(); + if (expires > 0) + { + refresh->SetIntervalL(static_cast(expires)); + } + } + else if (state.CompareF(KStateTerminated) == 0) + { + HandleTerminatedState(*ssHeader, aRetryAfter); + terminated = ETrue; + } + else // Unknown state + { + // Just refresh after one minute + refresh->SetIntervalL(60); + } + + aTerminated = terminated; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::HandleTerminatedState +// ----------------------------------------------------------------------------- +// +void CSIPRegEventSubscriber::HandleTerminatedState( + CSIPSubscriptionStateHeader& aState, + TInt& aRetryAfter) + { + TInt retryAfter = aState.RetryAfterParameter(); + + RStringF reason = + aState.ParamValue(SIPStrings::StringF(SipStrConsts::EReason)); + + if (reason == iReasonProbation || + reason == iReasonGiveup) + { + if (retryAfter < 0) + { + // re-SUBSCRIBE after one hour + retryAfter = 3600; + } + } + else if (reason == iReasonRejected || + reason == iReasonNoresource) + { + // do NOT re-SUBSRIBE + retryAfter = -1; + } + else + { + // deactivated, timeout or anything else: re-SUBSCRIBE immediately + retryAfter = 0; + } + + aRetryAfter = retryAfter; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::CreateSubscribeAssocL +// ----------------------------------------------------------------------------- +// +CSIPSubscribeDialogAssoc* CSIPRegEventSubscriber::CreateSubscribeAssocL() + { + TPtrC8 uriDes(iUserIdentity.Uri().UriDes()); + CUri8* remoteUri = CUri8::NewLC(iUserIdentity.Uri()); + CSIPFromHeader* from = CSIPFromHeader::DecodeL(uriDes); + CleanupStack::PushL(from); + CSIPEventHeader* event = CSIPEventHeader::NewLC(KRegEventName); + + CSIPSubscribeDialogAssoc* subscribeAssoc = + CSIPSubscribeDialogAssoc::NewL(iConnection,remoteUri, + iRegistrationContext,event,from); + + CleanupStack::Pop(event); + CleanupStack::Pop(from); + CleanupStack::Pop(remoteUri); + + return subscribeAssoc; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::CreateMessageElementsLC +// ----------------------------------------------------------------------------- +// +CSIPMessageElements* +CSIPRegEventSubscriber::CreateMessageElementsLC(TUint aExpiresValue) + { + CSIPMessageElements* message = CSIPMessageElements::NewLC(); + RPointerArray headers; + CSIPHeaderBase::PushLC(&headers); + // Expires + CSIPExpiresHeader* expires = new(ELeave)CSIPExpiresHeader(aExpiresValue); + CleanupStack::PushL(expires); + headers.AppendL(expires); + CleanupStack::Pop(expires); + // Accept + CSIPAcceptHeader* accept = + CSIPAcceptHeader::NewLC(KAcceptType,KAcceptSubtype); + headers.AppendL(accept); + CleanupStack::Pop(accept); + message->SetUserHeadersL(headers); + CleanupStack::Pop(1); // headers + headers.Close(); + return message; + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::MethodOK +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::MethodOK(const CSIPRequestElements& aRequest) + { + return (aRequest.Method() == SIPStrings::StringF(SipStrConsts::ENotify)); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::ContentTypeOK +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::ContentTypeOK(const CSIPRequestElements& aRequest) + { + const CSIPContentTypeHeader* contentType = + aRequest.MessageElements().ContentType(); + if (!contentType) + { + return EFalse; + } + if (contentType->MediaType().CompareF(KAcceptType) != 0) + { + return EFalse; + } + return (contentType->MediaSubtype().CompareF(KAcceptSubtype) == 0); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::SubscriptionStateOK +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::SubscriptionStateOK( + const CSIPRequestElements& aRequest) + { + CSIPHeaderBase* header = + FindHeader(aRequest.MessageElements(), + SIPStrings::StringF(SipStrConsts::ESubscriptionStateHeader)); + return (header != NULL); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::EventOK +// ----------------------------------------------------------------------------- +// +TBool CSIPRegEventSubscriber::EventOK(const CSIPRequestElements& aRequest) + { + CSIPHeaderBase* header = + FindHeader(aRequest.MessageElements(), + SIPStrings::StringF(SipStrConsts::EEventHeader)); + if (!header) + { + return EFalse; + } + CSIPEventHeader* event = static_cast(header); + return (event->EventPackage().CompareF(KRegEventName) == 0); + } + +// ----------------------------------------------------------------------------- +// CSIPRegEventSubscriber::FindHeader +// ----------------------------------------------------------------------------- +// +CSIPHeaderBase* CSIPRegEventSubscriber::FindHeader( + const CSIPMessageElements& aMessage, + RStringF aHeaderName) const + { + TBool found = EFalse; + CSIPHeaderBase* header = NULL; + const RPointerArray& headers = aMessage.UserHeaders(); + for (TInt i=0; i < headers.Count() && !found; i++) + { + header = headers[i]; + if (header->Name() == aHeaderName) + { + found = ETrue; + } + else + { + header = NULL; + } + } + return header; + } + +// ---------------------------------------------------------------------------- +// CSIPRegEventSubscriber::CurrentState +// ---------------------------------------------------------------------------- +// +TSIPRegEventSubscriptionStateBase& CSIPRegEventSubscriber::CurrentState() + { + return iStates.At(iCurrentState); + }