Esempio n. 1
0
char* SIPClient::inviteWithPassword(char const* url, char const* username,
				    char const* password) {
  delete[] (char*)fUserName; fUserName = strDup(username);
  fUserNameSize = strlen(fUserName);

  Authenticator authenticator;
  authenticator.setUsernameAndPassword(username, password);
  char* inviteResult = invite(url, &authenticator);
  if (inviteResult != NULL) {
    // We are already authorized
    return inviteResult;
  }

  // The "realm" and "nonce" fields should have been filled in:
  if (authenticator.realm() == NULL || authenticator.nonce() == NULL) {
    // We haven't been given enough information to try again, so fail:
    return NULL;
  }

  // Try again (but with the same CallId):
  inviteResult = invite1(&authenticator);
  if (inviteResult != NULL) {
    // The authenticator worked, so use it in future requests:
    fValidAuthenticator = authenticator;
  }

  return inviteResult;
}
Esempio n. 2
0
void Connection::send_initial_auth_response() {
  Authenticator* auth = config_.auth_provider()->new_authenticator(address_);
  if (auth == NULL) {
    auth_error_ = "Authentication required but no auth provider set";
    notify_error(auth_error_);
  } else {
    AuthResponseRequest* auth_response
        = new AuthResponseRequest(auth->initial_response(), auth);
    write(new StartupHandler(this, auth_response));
  }
}
int sendBeepSound(const char* rtspURL, const char* username, const char* password) {

	FILE* fp = fopen(WAVE_FILE, "r");
	if ( fp == NULL )
	{
		LOG("wave file not exists : %s", WAVE_FILE);
		return -1;
	}
	else
	{
		fclose(fp);
	}

	// Begin by setting up our usage environment:
	TaskScheduler* scheduler = BasicTaskScheduler::createNew();
	UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

	// Begin by creating a "RTSPClient" object.  Note that there is a separate "RTSPClient" object for each stream that we wish
	// to receive (even if more than stream uses the same "rtsp://" URL).
	ourRTSPClient* rtspClient = ourRTSPClient::createNew(*env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, "SCBT BackChannel");
	if (rtspClient == NULL) {
		*env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env->getResultMsg() << "\n";
		env->reclaim(); env = NULL;
		delete scheduler; scheduler = NULL;

		return -2;
	}

	rtspClient->bRequireBackChannel = bEnableBackChannel;
	// Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream.
	// Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response.
	// Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop:
	Authenticator auth;
	auth.setUsernameAndPassword(username, password);
	rtspClient->sendDescribeCommand(continueAfterDESCRIBE, &auth);


	//continueAfterSETUP(rtspClient, 0, new char[2]);
	//startPlay(rtspClient);

	// All subsequent activity takes place within the event loop:
	env->taskScheduler().doEventLoop(&(rtspClient->scs.eventLoopWatchVariable));
	// This function call does not return, unless, at some point in time, "eventLoopWatchVariable" gets set to something non-zero.

	// If you choose to continue the application past this point (i.e., if you comment out the "return 0;" statement above),
	// and if you don't intend to do anything more with the "TaskScheduler" and "UsageEnvironment" objects,
	// then you can also reclaim the (small) memory used by these objects by uncommenting the following code:
	env->reclaim(); env = NULL;
	delete scheduler; scheduler = NULL;

	return 0;
}
Esempio n. 4
0
/**
* \brief Function to check user on database
* \return true if the user is authenticated else false
*/
bool
UserServer::isAuthenticate(bool flagForChangePwd) {

  bool existUser = false;
  AuthenticatorFactory authFactory;
  Authenticator *authenticatorInstance = authFactory.getAuthenticatorInstance();

  existUser = authenticatorInstance->authenticate(muser);
  if (existUser) {
    CheckUserState(flagForChangePwd);
  }
  return existUser;
}
void MessageWorker::kudosMessage(QString messageId, QString authorId)
{
	Authenticator* auth = Authenticator::Instance();

	if (auth->authenticated() && authorId.contains(auth->userId(), Qt::CaseInsensitive))
	{
		emit postComplete(false, tr("You cannot like your own posts."));
	}
	else
	{
		const QString kudosUrl = URLProvider::getForumURL() + QString::fromLatin1("messages/id/%1/kudos/give").arg(messageId);
		mForumRequest->makeRequest(kudosUrl, true);
	}
}
void Searcher::findUsersPosts(QString pageNo)
{
	Authenticator* auth = Authenticator::Instance();
	if (auth->authenticated())
	{
		mSearchData->clear();
		QString queryUrl =
				URLProvider::getForumURL() + QString::fromLatin1("users/%1/posts/messages?message_viewer.message_sort_order=thread_descending&page_size=10&page=%2").arg(auth->userId(), pageNo);
		mForumRequest->makeRequest(queryUrl);
	}
	else
	{
		emit searchRequestFailed(tr("You must be logged into view your posts. Swipe down from the top of the screen and choose Settings to log in."));
	}
}
Esempio n. 7
0
// NOTE: This method represents a theoretical way to use a U2F-compliant token
// to produce the result of the WebAuthn GetAssertion method. The exact mapping
// of U2F data fields to WebAuthn data fields is still a matter of ongoing
// discussion, and this should not be taken as anything but a point-in- time
// possibility.
void
WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
                    const Authenticator& aToken, CryptoBuffer& aRpIdHash,
                    const nsACString& aClientData, CryptoBuffer& aClientDataHash,
                    nsTArray<CryptoBuffer>& aAllowList,
                    const WebAuthnExtensions& aExtensions)
{
  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));

  // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
  aRequest->AddActiveToken(__func__);

  // 4.1.2.8 While issuedRequests is not empty, perform the following actions
  // depending upon the adjustedTimeout timer and responses from the
  // authenticators:

  // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
  // in issuedRequests invoke the authenticatorCancel operation on that
  // authenticator and remove its entry from the list.

  for (CryptoBuffer& allowedCredential : aAllowList) {
    bool isRegistered = false;
    nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
                                       allowedCredential.Length(),
                                       &isRegistered);

    // 4.1.2.8.b If any authenticator returns a status indicating that the user
    // cancelled the operation, delete that authenticator’s entry from
    // issuedRequests. For each remaining entry in issuedRequests invoke the
    // authenticatorCancel operation on that authenticator, and remove its entry
    // from the list.

    // 4.1.2.8.c If any authenticator returns an error status, delete the
    // corresponding entry from issuedRequests.
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    if (!isRegistered) {
      continue;
    }

    // Sign
    uint8_t* buffer;
    uint32_t bufferlen;
    rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
                      aClientDataHash.Elements(), aClientDataHash.Length(),
                      allowedCredential.Elements(), allowedCredential.Length(),
                      &buffer, &bufferlen);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    MOZ_ASSERT(buffer);
    CryptoBuffer signatureData;
    if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
      free(buffer);
      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
      return;
    }
    free(buffer);

    // 4.1.2.8.d If any authenticator returns success:

    // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.

    // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
    // populate its fields with the values returned from the authenticator as
    // well as the clientDataJSON computed earlier.

    CryptoBuffer clientDataBuf;
    if (!clientDataBuf.Assign(aClientData)) {
      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
      return;
    }

    CryptoBuffer authenticatorDataBuf;
    rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
                                      signatureData);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    RefPtr<ScopedCredential> credential = new ScopedCredential(this);
    credential->SetType(ScopedCredentialType::ScopedCred);
    credential->SetId(allowedCredential);

    AssertionPtr assertion = new WebAuthnAssertion(this);
    assertion->SetCredential(credential);
    assertion->SetClientData(clientDataBuf);
    assertion->SetAuthenticatorData(authenticatorDataBuf);
    assertion->SetSignature(signatureData);

    // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
    // authenticatorCancel operation on that authenticator and remove its entry
    // from the list.

    // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
    aRequest->SetSuccess(assertion);
    return;
  }

  // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
  // and terminate this algorithm.
  aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
Esempio n. 8
0
// NOTE: This method represents a theoretical way to use a U2F-compliant token
// to produce the result of the WebAuthn MakeCredential method. The exact
// mapping of U2F data fields to WebAuthn data fields is still a matter of
// ongoing discussion, and this should not be taken as anything but a point-in-
// time possibility.
void
WebAuthentication::U2FAuthMakeCredential(
             const RefPtr<CredentialRequest>& aRequest,
             const Authenticator& aToken, CryptoBuffer& aRpIdHash,
             const nsACString& aClientData, CryptoBuffer& aClientDataHash,
             const Account& aAccount,
             const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
             const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
             const WebAuthnExtensions& aExtensions)
{
  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
  aRequest->AddActiveToken(__func__);

  // 5.1.1 When this operation is invoked, the authenticator must perform the
  // following procedure:

  // 5.1.1.a Check if all the supplied parameters are syntactically well-
  // formed and of the correct length. If not, return an error code equivalent
  // to UnknownError and terminate the operation.

  if ((aRpIdHash.Length() != SHA256_LENGTH) ||
      (aClientDataHash.Length() != SHA256_LENGTH)) {
    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
    return;
  }

  // 5.1.1.b Check if at least one of the specified combinations of
  // ScopedCredentialType and cryptographic parameters is supported. If not,
  // return an error code equivalent to NotSupportedError and terminate the
  // operation.

  bool isValidCombination = false;

  for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
    if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
        aNormalizedParams[a].mAlgorithm.IsString() &&
        aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
          WEBCRYPTO_NAMED_CURVE_P256)) {
      isValidCombination = true;
      break;
    }
  }
  if (!isValidCombination) {
    aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    return;
  }

  // 5.1.1.c Check if a credential matching any of the supplied
  // ScopedCredential identifiers is present on this authenticator. If so,
  // return an error code equivalent to NotAllowedError and terminate the
  // operation.

  if (aExcludeList.WasPassed()) {
    const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();

    for (const ScopedCredentialDescriptor& scd : list) {
      bool isRegistered = false;

      uint8_t *data;
      uint32_t len;

      // data is owned by the Descriptor, do don't free it here.
      if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
        aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
        return;
      }

      nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        aRequest->SetFailure(rv);
        return;
      }

      if (isRegistered) {
        aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
        return;
      }
    }
  }

  // 5.1.1.d Prompt the user for consent to create a new credential. The
  // prompt for obtaining this consent is shown by the authenticator if it has
  // its own output capability, or by the user agent otherwise. If the user
  // denies consent, return an error code equivalent to NotAllowedError and
  // terminate the operation.

  // 5.1.1.d Once user consent has been obtained, generate a new credential
  // object

  // 5.1.1.e If any error occurred while creating the new credential object,
  // return an error code equivalent to UnknownError and terminate the
  // operation.

  // 5.1.1.f Process all the supported extensions requested by the client, and
  // generate an attestation statement. If no authority key is available to
  // sign such an attestation statement, then the authenticator performs self
  // attestation of the credential with its own private key. For more details
  // on attestation, see §5.3 Credential Attestation Statements.

  // No extensions are supported

  // 4.1.1.11 While issuedRequests is not empty, perform the following actions
  // depending upon the adjustedTimeout timer and responses from the
  // authenticators:

  // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
  // issuedRequests invoke the authenticatorCancel operation on that
  // authenticator and remove its entry from the list.

  uint8_t* buffer;
  uint32_t bufferlen;

  nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
                                 aClientDataHash.Elements(),
                                 aClientDataHash.Length(), &buffer, &bufferlen);

  // 4.1.1.11.b If any authenticator returns a status indicating that the user
  // cancelled the operation, delete that authenticator’s entry from
  // issuedRequests. For each remaining entry in issuedRequests invoke the
  // authenticatorCancel operation on that authenticator and remove its entry
  // from the list.

  // 4.1.1.11.c If any authenticator returns an error status, delete the
  // corresponding entry from issuedRequests.
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
    return;
  }

  MOZ_ASSERT(buffer);
  CryptoBuffer regData;
  if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
    free(buffer);
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
  free(buffer);

  // Decompose the U2F registration packet
  CryptoBuffer pubKeyBuf;
  CryptoBuffer keyHandleBuf;
  CryptoBuffer attestationCertBuf;
  CryptoBuffer signatureBuf;

  rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
                                        attestationCertBuf, signatureBuf);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  // Sign the aClientDataHash explicitly to get the format needed for
  // the AuthenticatorData parameter of WebAuthnAttestation. This might
  // be temporary while the spec settles down how to incorporate U2F.
  rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
                    aClientDataHash.Elements(), aClientDataHash.Length(),
                    keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
                    &bufferlen);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  MOZ_ASSERT(buffer);
  CryptoBuffer signatureData;
  if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
    free(buffer);
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
  free(buffer);

  CryptoBuffer clientDataBuf;
  if (!clientDataBuf.Assign(aClientData)) {
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }

  CryptoBuffer authenticatorDataBuf;
  rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
                                    signatureData);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  // 4.1.1.11.d If any authenticator indicates success:

  // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.

  // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
  // populate its fields with the values returned from the authenticator as well
  // as the clientDataJSON computed earlier.

  RefPtr<ScopedCredential> credential = new ScopedCredential(this);
  credential->SetType(ScopedCredentialType::ScopedCred);
  credential->SetId(keyHandleBuf);

  RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
  attestation->SetFormat(NS_LITERAL_STRING("u2f"));
  attestation->SetClientData(clientDataBuf);
  attestation->SetAuthenticatorData(authenticatorDataBuf);
  attestation->SetAttestation(regData);

  CredentialPtr info = new ScopedCredentialInfo(this);
  info->SetCredential(credential);
  info->SetAttestation(attestation);

  // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
  // authenticatorCancel operation on that authenticator and remove its entry
  // from the list.

  // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
  aRequest->SetSuccess(info);
}
Authenticator::Authenticator(const Authenticator& orig) {
  assign(orig.realm(), orig.nonce(), orig.username(), orig.password(), orig.fPasswordIsMD5);
}
Esempio n. 10
0
Boolean DarwinInjector
::setDestination(char const* remoteRTSPServerNameOrAddress,
		 char const* remoteFileName,
		 char const* sessionName,
		 char const* sessionInfo,
		 portNumBits remoteRTSPServerPortNumber,
		 char const* remoteUserName,
		 char const* remotePassword,
		 char const* sessionAuthor,
		 char const* sessionCopyright,
		 int timeout) {
  char* sdp = NULL;
  char* url = NULL;
  Boolean success = False; // until we learn otherwise

  do {
    // Construct a RTSP URL for the remote stream:
    char const* const urlFmt = "rtsp://%s:%u/%s";
    unsigned urlLen
      = strlen(urlFmt) + strlen(remoteRTSPServerNameOrAddress) + 5 /* max short len */ + strlen(remoteFileName);
    url = new char[urlLen];
    sprintf(url, urlFmt, remoteRTSPServerNameOrAddress, remoteRTSPServerPortNumber, remoteFileName);

    // Begin by creating our RTSP client object:
    fRTSPClient = new RTSPClientForDarwinInjector(envir(), url, fVerbosityLevel, fApplicationName, this);
    if (fRTSPClient == NULL) break;

    // Get the remote RTSP server's IP address:
    struct in_addr addr;
    {
      NetAddressList addresses(remoteRTSPServerNameOrAddress);
      if (addresses.numAddresses() == 0) break;
      NetAddress const* address = addresses.firstAddress();
      addr.s_addr = *(unsigned*)(address->data());
    }
    AddressString remoteRTSPServerAddressStr(addr);

    // Construct a SDP description for the session that we'll be streaming:
    char const* const sdpFmt =
      "v=0\r\n"
      "o=- %u %u IN IP4 127.0.0.1\r\n"
      "s=%s\r\n"
      "i=%s\r\n"
      "c=IN IP4 %s\r\n"
      "t=0 0\r\n"
      "a=x-qt-text-nam:%s\r\n"
      "a=x-qt-text-inf:%s\r\n"
      "a=x-qt-text-cmt:source application:%s\r\n"
      "a=x-qt-text-aut:%s\r\n"
      "a=x-qt-text-cpy:%s\r\n";
      // plus, %s for each substream SDP
    unsigned sdpLen = strlen(sdpFmt)
      + 20 /* max int len */ + 20 /* max int len */
      + strlen(sessionName)
      + strlen(sessionInfo)
      + strlen(remoteRTSPServerAddressStr.val())
      + strlen(sessionName)
      + strlen(sessionInfo)
      + strlen(fApplicationName)
      + strlen(sessionAuthor)
      + strlen(sessionCopyright)
      + fSubstreamSDPSizes;
    unsigned const sdpSessionId = our_random32();
    unsigned const sdpVersion = sdpSessionId;
    sdp = new char[sdpLen];
    sprintf(sdp, sdpFmt,
	    sdpSessionId, sdpVersion, // o= line
	    sessionName, // s= line
	    sessionInfo, // i= line
	    remoteRTSPServerAddressStr.val(), // c= line
	    sessionName, // a=x-qt-text-nam: line
	    sessionInfo, // a=x-qt-text-inf: line
	    fApplicationName, // a=x-qt-text-cmt: line
	    sessionAuthor, // a=x-qt-text-aut: line
	    sessionCopyright // a=x-qt-text-cpy: line
	    );
    char* p = &sdp[strlen(sdp)];
    SubstreamDescriptor* ss;
    for (ss = fHeadSubstream; ss != NULL; ss = ss->next()) {
      sprintf(p, "%s", ss->sdpLines());
      p += strlen(p);
    }

    // Do a RTSP "ANNOUNCE" with this SDP description:
    Authenticator auth;
    Authenticator* authToUse = NULL;
    if (remoteUserName[0] != '\0' || remotePassword[0] != '\0') {
      auth.setUsernameAndPassword(remoteUserName, remotePassword);
      authToUse = &auth;
    }
    fWatchVariable = 0;
    (void)fRTSPClient->sendAnnounceCommand(sdp, genericResponseHandler, authToUse);

    // Now block (but handling events) until we get a response:
    envir().taskScheduler().doEventLoop(&fWatchVariable);

    delete[] fResultString;
    if (fResultCode != 0) break; // an error occurred with the RTSP "ANNOUNCE" command

    // Next, tell the remote server to start receiving the stream from us.
    // (To do this, we first create a "MediaSession" object from the SDP description.)
    fSession = MediaSession::createNew(envir(), sdp);
    if (fSession == NULL) break;

    ss = fHeadSubstream;
    MediaSubsessionIterator iter(*fSession);
    MediaSubsession* subsession;
    ss = fHeadSubstream;
    unsigned streamChannelId = 0;
    while ((subsession = iter.next()) != NULL) {
      if (!subsession->initiate()) break;

      fWatchVariable = 0;
      (void)fRTSPClient->sendSetupCommand(*subsession, genericResponseHandler,
					  True /*streamOutgoing*/,
					  True /*streamUsingTCP*/);
      // Now block (but handling events) until we get a response:
      envir().taskScheduler().doEventLoop(&fWatchVariable);

      delete[] fResultString;
      if (fResultCode != 0) break; // an error occurred with the RTSP "SETUP" command

      // Tell this subsession's RTPSink and RTCPInstance to use
      // the RTSP TCP connection:
      ss->rtpSink()->setStreamSocket(fRTSPClient->socketNum(), streamChannelId++);
      if (ss->rtcpInstance() != NULL) {
	ss->rtcpInstance()->setStreamSocket(fRTSPClient->socketNum(),
					    streamChannelId++);
      }
      ss = ss->next();
    }
    if (subsession != NULL) break; // an error occurred above

    // Tell the RTSP server to start:
    fWatchVariable = 0;
    (void)fRTSPClient->sendPlayCommand(*fSession, genericResponseHandler);

    // Now block (but handling events) until we get a response:
    envir().taskScheduler().doEventLoop(&fWatchVariable);

    delete[] fResultString;
    if (fResultCode != 0) break; // an error occurred with the RTSP "PLAY" command

    // Finally, make sure that the output TCP buffer is a reasonable size:
    increaseSendBufferTo(envir(), fRTSPClient->socketNum(), 100*1024);

    success = True;
  } while (0);

  delete[] sdp;
  delete[] url;
  return success;
}