#include "core/internal/base_pcp_handler.h" #include #include #include #include namespace location { namespace nearby { namespace connections { namespace base_pcp_handler { // TODO(reznor): Implement this method in-terms-of removeOwnedPtrFromMap() // below. template void eraseOwnedPtrFromMap(std::map>& m, const K& k) { typename std::map>::iterator it = m.find(k); if (it != m.end()) { it->second.destroy(); m.erase(it); } } template Ptr removeOwnedPtrFromMap(std::map>& m, const K& k) { Ptr removed_ptr; typename std::map>::iterator it = m.find(k); if (it != m.end()) { removed_ptr = it->second; m.erase(it); } return removed_ptr; } template class StartAdvertisingCallable : public Callable { public: StartAdvertisingCallable( Ptr> base_pcp_handler, Ptr> client_proxy, const string& service_id, const string& local_endpoint_name, const AdvertisingOptions& options, Ptr connection_lifecycle_listener) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), service_id_(service_id), local_endpoint_name_(local_endpoint_name), options_(options), // Convert the passed in connection_lifecycle_listener Ptr into a // reference counted one. The advertising session and any connected // endpoints need a handle to the same connection_lifecycle_listener, so // there is no clear model of who actually owns the listener. connection_lifecycle_listener_( MakeRefCountedPtr(&(*connection_lifecycle_listener))) {} ExceptionOr call() override { // Ask the implementation to attempt to start advertising. ScopedPtr::StartOperationResult>> result(base_pcp_handler_->startAdvertisingImpl( client_proxy_, service_id_, client_proxy_->generateLocalEndpointId(), local_endpoint_name_, options_)); if (Status::SUCCESS != result->status_) { return ExceptionOr(result->status_); } // Now that we've succeeded, mark the client as advertising. // Previous advertising_options_ and // advertising_connection_lifecycle_listener_ is not destroyed here because // stopAdvertising() is expected to be called before startAdvertising(). base_pcp_handler_->advertising_options_ = MakePtr(new AdvertisingOptions(options_)); base_pcp_handler_->advertising_connection_lifecycle_listener_ = connection_lifecycle_listener_; client_proxy_->startedAdvertising( service_id_, base_pcp_handler_->getStrategy(), connection_lifecycle_listener_, result->mediums_); return ExceptionOr(Status::SUCCESS); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string service_id_; const string local_endpoint_name_; const AdvertisingOptions options_; Ptr connection_lifecycle_listener_; }; template class StopAdvertisingRunnable : public Runnable { public: StopAdvertisingRunnable(Ptr> base_pcp_handler, Ptr> client_proxy, Ptr latch) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), latch_(latch) {} void run() override { base_pcp_handler_->stopAdvertisingImpl(client_proxy_); client_proxy_->stoppedAdvertising(); // base_pcp_handler_->advertising_options_ is purposefully not destroyed // here. base_pcp_handler_->advertising_connection_lifecycle_listener_.destroy(); latch_->countDown(); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; Ptr latch_; }; template class StartDiscoveryCallable : public Callable { public: StartDiscoveryCallable(Ptr> base_pcp_handler, Ptr> client_proxy, const string& service_id, const DiscoveryOptions& options, Ptr discovery_listener) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), service_id_(service_id), options_(options), discovery_listener_(discovery_listener) {} ExceptionOr call() override { // Ask the implementation to attempt to start discovery. ScopedPtr::StartOperationResult>> result(base_pcp_handler_->startDiscoveryImpl(client_proxy_, service_id_, options_)); if (Status::SUCCESS != result->status_) { return ExceptionOr(result->status_); } // Now that we've succeeded, mark the client as discovering and clear out // any old endpoints we had discovered. // Previous discovery_options_ is not destroyed here because stopDiscovery() // is expected to be called before startDiscovery(). base_pcp_handler_->discovery_options_ = MakePtr(new DiscoveryOptions(options_)); for (typename BasePCPHandler::DiscoveredEndpointsMap::iterator it = base_pcp_handler_->discovered_endpoints_.begin(); it != base_pcp_handler_->discovered_endpoints_.end(); it++) { it->second.destroy(); } base_pcp_handler_->discovered_endpoints_.clear(); client_proxy_->startedDiscovery( service_id_, base_pcp_handler_->getStrategy(), discovery_listener_.release(), result->mediums_); return ExceptionOr(Status::SUCCESS); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string service_id_; const DiscoveryOptions options_; ScopedPtr> discovery_listener_; }; template class StopDiscoveryRunnable : public Runnable { public: StopDiscoveryRunnable(Ptr> base_pcp_handler, Ptr> client_proxy, Ptr latch) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), latch_(latch) {} void run() override { base_pcp_handler_->stopDiscoveryImpl(client_proxy_); client_proxy_->stoppedDiscovery(); // base_pcp_handler_->discovery_options_ is purposefully not destroyed here. latch_->countDown(); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; Ptr latch_; }; template class RequestConnectionRunnable : public Runnable { public: RequestConnectionRunnable( Ptr> base_pcp_handler, Ptr> client_proxy, const string& local_endpoint_name, const string& endpoint_id, Ptr connection_lifecycle_listener, Ptr> result) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), local_endpoint_name_(local_endpoint_name), endpoint_id_(endpoint_id), connection_lifecycle_listener_(connection_lifecycle_listener), result_(result) {} void run() override { std::int64_t start_time_millis = base_pcp_handler_->system_clock_->elapsedRealtime(); // If we already have a pending connection, then we shouldn't allow any more // outgoing connections to this endpoint. typename BasePCPHandler::PendingConnectionsMap::iterator it = base_pcp_handler_->pending_connections_.find(endpoint_id_); if (it != base_pcp_handler_->pending_connections_.end()) { // TODO(tracyzhou): Add logging. result_->set(Status::ALREADY_CONNECTED_TO_ENDPOINT); return; } // If our child class says we can't send any more outgoing connections, // listen to them. if (base_pcp_handler_->shouldEnforceTopologyConstraints() && !base_pcp_handler_->canSendOutgoingConnection(client_proxy_)) { // TODO(tracyzhou): Add logging. result_->set(Status::OUT_OF_ORDER_API_CALL); return; } Ptr::DiscoveredEndpoint> endpoint = base_pcp_handler_->getDiscoveredEndpoint(endpoint_id_); if (endpoint.isNull()) { // TODO(tracyzhou): Add logging. result_->set(Status::ENDPOINT_UNKNOWN); return; } typename BasePCPHandler::ConnectImplResult connect_impl_result = base_pcp_handler_->connectImpl(client_proxy_, endpoint); if (connect_impl_result.endpoint_channel.isNull()) { // TODO(tracyzhou): Add logging base_pcp_handler_->processPreConnectionInitiationFailure( client_proxy_, connect_impl_result.medium, endpoint_id_, connect_impl_result.endpoint_channel, false /* is_incoming */, start_time_millis, connect_impl_result.status, result_); return; } ScopedPtr> scoped_endpoint_channel( connect_impl_result.endpoint_channel); // TODO(tracyzhou): Add logging. // Generate the nonce to use for this connection. std::int32_t nonce = base_pcp_handler_->prng_.nextInt32(); // The first message we have to send, after connecting, is to tell the // endpoint about ourselves. Exception::Value write_exception = base_pcp_handler_->writeConnectionRequestFrame( scoped_endpoint_channel.get(), client_proxy_->generateLocalEndpointId(), local_endpoint_name_, nonce, base_pcp_handler_->getConnectionMediumsByPriority()); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { base_pcp_handler_->processPreConnectionInitiationFailure( client_proxy_, scoped_endpoint_channel->getMedium(), endpoint_id_, scoped_endpoint_channel.get(), false /* is_incoming */, start_time_millis, Status::ENDPOINT_IO_ERROR, result_); return; } } // TODO(tracyzhou): Add logging. // We've successfully connected to the device, and are now about to jump on // to the EncryptionRunner thread to start running our encryption protocol. // We'll mark ourselves as pending in case we get another call to // requestConnection or onIncomingConnection, so that we can cancel the // connection if needed. Ptr endpoint_channel = base_pcp_handler_->pending_connections_ .insert(std::make_pair( endpoint_id_, BasePCPHandler::PendingConnectionInfo:: newOutgoingPendingConnectionInfo( client_proxy_, endpoint->getEndpointName(), scoped_endpoint_channel.release(), nonce, start_time_millis, connection_lifecycle_listener_.release(), result_))) .first->second->endpoint_channel_.get(); // Next, we'll set up encryption. When it's done, our future will return and // requestConnection() will finish. base_pcp_handler_->encryption_runner_->startClient( client_proxy_, endpoint_id_, endpoint_channel, MakePtr(new typename BasePCPHandler::ResultListenerFacade( base_pcp_handler_))); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string local_endpoint_name_; const string endpoint_id_; ScopedPtr> connection_lifecycle_listener_; Ptr> result_; }; template class AcceptConnectionCallable : public Callable { public: AcceptConnectionCallable(Ptr> base_pcp_handler, Ptr> client_proxy, const string& endpoint_id, Ptr payload_listener) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), endpoint_id_(endpoint_id), payload_listener_(payload_listener) {} ExceptionOr call() override { // TODO(tracyzhou): Add logging. typename BasePCPHandler::PendingConnectionsMap::iterator it = base_pcp_handler_->pending_connections_.find(endpoint_id_); if (it == base_pcp_handler_->pending_connections_.end()) { // TODO(tracyzhou): Add logging. return ExceptionOr(Status::ENDPOINT_UNKNOWN); } Ptr::PendingConnectionInfo> connection_info = it->second; // By this point in the flow, connection_info->endpoint_channel_ has been // nulled out because ownership of that EndpointChannel was passed on to // EndpointChannelManager via a call to // EndpointManager::registerEndpoint(), so we now need to get access to the // EndpointChannel from the authoritative owner. ScopedPtr> scoped_endpoint_channel( base_pcp_handler_->endpoint_channel_manager_->getChannelForEndpoint( endpoint_id_)); if (scoped_endpoint_channel.isNull()) { // TODO(reznor): Add logging. base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, endpoint_id_); return ExceptionOr(Status::ENDPOINT_UNKNOWN); } Exception::Value write_exception = scoped_endpoint_channel->write( OfflineFrames::forConnectionResponse(Status::SUCCESS)); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { // TODO(tracyzhou): Add logging. base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, endpoint_id_); return ExceptionOr(Status::ENDPOINT_IO_ERROR); } } // TODO(tracyzhou): Add logging. connection_info->localEndpointAcceptedConnection( endpoint_id_, payload_listener_.release()); base_pcp_handler_->evaluateConnectionResult( client_proxy_, endpoint_id_, false /* can_close_immediately */); return ExceptionOr(Status::SUCCESS); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string endpoint_id_; ScopedPtr> payload_listener_; }; template class RejectConnectionCallable : public Callable { public: RejectConnectionCallable(Ptr> base_pcp_handler, Ptr> client_proxy, const string& endpoint_id) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), endpoint_id_(endpoint_id) {} ExceptionOr call() override { // TODO(tracyzhou): Add logging. typename BasePCPHandler::PendingConnectionsMap::iterator it = base_pcp_handler_->pending_connections_.find(endpoint_id_); if (it == base_pcp_handler_->pending_connections_.end()) { // TODO(tracyzhou): Add logging. return ExceptionOr(Status::ENDPOINT_UNKNOWN); } Ptr::PendingConnectionInfo> connection_info = it->second; // By this point in the flow, connection_info->endpoint_channel_ has been // nulled out because ownership of that EndpointChannel was passed on to // EndpointChannelManager via a call to // EndpointManager::registerEndpoint(), so we now need to get access to the // EndpointChannel from the authoritative owner. ScopedPtr> scoped_endpoint_channel( base_pcp_handler_->endpoint_channel_manager_->getChannelForEndpoint( endpoint_id_)); if (scoped_endpoint_channel.isNull()) { // TODO(reznor): Add logging. base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, endpoint_id_); return ExceptionOr(Status::ENDPOINT_UNKNOWN); } Exception::Value write_exception = scoped_endpoint_channel->write( OfflineFrames::forConnectionResponse(Status::CONNECTION_REJECTED)); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { // TODO(tracyzhou): Add logging. base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, endpoint_id_); return ExceptionOr(Status::ENDPOINT_IO_ERROR); } } // TODO(tracyzhou): Add logging. connection_info->localEndpointRejectedConnection(endpoint_id_); base_pcp_handler_->evaluateConnectionResult( client_proxy_, endpoint_id_, false /* can_close_immediately */); return ExceptionOr(Status::SUCCESS); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string endpoint_id_; }; class ReadConnectionRequestCancelableAlarmRunnable : public Runnable { public: explicit ReadConnectionRequestCancelableAlarmRunnable( Ptr endpoint_channel) : endpoint_channel_(endpoint_channel) {} void run() override { // TODO(tracyzhou): Add logging. endpoint_channel_->close(); } private: Ptr endpoint_channel_; }; template class EvaluateConnectionResultCancelableAlarmRunnable : public Runnable { public: EvaluateConnectionResultCancelableAlarmRunnable( Ptr> endpoint_manager, Ptr> client_proxy, const string& endpoint_id) : endpoint_manager_(endpoint_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id) {} void run() override { // TODO(tracyzhou): Add logging. endpoint_manager_->discardEndpoint(client_proxy_, endpoint_id_); } private: Ptr> endpoint_manager_; Ptr> client_proxy_; const string endpoint_id_; }; template class ProcessEndpointDisconnectionRunnable : public Runnable { public: ProcessEndpointDisconnectionRunnable( Ptr> base_pcp_handler, Ptr> client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), endpoint_id_(endpoint_id), process_disconnection_barrier_(process_disconnection_barrier) {} void run() override { typename BasePCPHandler< Platform>::PendingRejectedConnectionCloseAlarmsMap::iterator it = base_pcp_handler_->pending_rejected_connection_close_alarms_.find( endpoint_id_); if (it != base_pcp_handler_->pending_rejected_connection_close_alarms_.end()) { it->second->cancel(); it->second.destroy(); base_pcp_handler_->pending_rejected_connection_close_alarms_.erase(it); } base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, endpoint_id_); process_disconnection_barrier_->countDown(); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string endpoint_id_; Ptr process_disconnection_barrier_; }; template class OnConnectionResponseRunnable : public Runnable { public: OnConnectionResponseRunnable(Ptr> base_pcp_handler, Ptr> client_proxy, const string& endpoint_id, ConstPtr offline_frame, Ptr latch) : base_pcp_handler_(base_pcp_handler), client_proxy_(client_proxy), endpoint_id_(endpoint_id), offline_frame_(offline_frame), latch_(latch) {} void run() override { // TODO(tracyzhou): Add logging. if (client_proxy_->hasRemoteEndpointResponded(endpoint_id_)) { // TODO(tracyzhou): Add logging. return; } const ConnectionResponseFrame& connection_response = offline_frame_->v1().connection_response(); // TODO(tracyzhou): Assign int values to Status. if (Status::SUCCESS == connection_response.status()) { // TODO(tracyzhou): Add logging. client_proxy_->remoteEndpointAcceptedConnection(endpoint_id_); } else { // TODO(tracyzhou): Add logging. client_proxy_->remoteEndpointRejectedConnection(endpoint_id_); } base_pcp_handler_->evaluateConnectionResult( client_proxy_, endpoint_id_, /* can_close_immediately= */ true); latch_->countDown(); } private: Ptr> base_pcp_handler_; Ptr> client_proxy_; const string endpoint_id_; ScopedPtr> offline_frame_; Ptr latch_; }; template class OnEncryptionSuccessRunnable : public Runnable { public: OnEncryptionSuccessRunnable(Ptr> base_pcp_handler, const string& endpoint_id, Ptr ukey2_handshake, const string& authentication_token, ConstPtr raw_authentication_token) : base_pcp_handler_(base_pcp_handler), endpoint_id_(endpoint_id), ukey2_handshake_(ukey2_handshake), authentication_token_(authentication_token), raw_authentication_token_(raw_authentication_token) {} void run() override { // Quick fail if we've been removed from pending connections while we were // busy running UKEY2. typename BasePCPHandler::PendingConnectionsMap::iterator it = base_pcp_handler_->pending_connections_.find(endpoint_id_); if (it == base_pcp_handler_->pending_connections_.end()) { // TODO(tracyzhou): Add logging. return; } Ptr::PendingConnectionInfo> connection_info = it->second; connection_info->setUKey2Handshake(ukey2_handshake_.release()); // TODO(tracyzhou): Add logging. // Set ourselves up so that we receive all acceptance/rejection messages base_pcp_handler_->endpoint_manager_->registerIncomingOfflineFrameProcessor( V1Frame::CONNECTION_RESPONSE, base_pcp_handler_); // Now we register our endpoint so that we can listen for both sides to // accept. base_pcp_handler_->endpoint_manager_->registerEndpoint( connection_info->client_proxy_, endpoint_id_, connection_info->remote_endpoint_name_, authentication_token_, raw_authentication_token_.release(), connection_info->is_incoming_, connection_info->endpoint_channel_.release(), connection_info->connection_lifecycle_listener_.release()); if (!connection_info->request_connection_result_.isNull()) { connection_info->request_connection_result_->set(Status::SUCCESS); connection_info->request_connection_result_.clear(); } } private: Ptr> base_pcp_handler_; const string endpoint_id_; ScopedPtr> ukey2_handshake_; const string authentication_token_; ScopedPtr> raw_authentication_token_; }; template class OnEncryptionFailureRunnable : public Runnable { public: OnEncryptionFailureRunnable(Ptr> base_pcp_handler, const string& endpoint_id, Ptr endpoint_channel) : base_pcp_handler_(base_pcp_handler), endpoint_id_(endpoint_id), endpoint_channel_(endpoint_channel) {} void run() override { typename BasePCPHandler::PendingConnectionsMap::iterator it = base_pcp_handler_->pending_connections_.find(endpoint_id_); if (it == base_pcp_handler_->pending_connections_.end()) { // TODO(tracyzhou): Add logging. return; } Ptr::PendingConnectionInfo> connection_info = it->second; // We had a bug here, caused by a race with EncryptionRunner. We now verify // the EndpointChannel to avoid it. In a simultaneous connection, we clean // up one of the two EndpointChannels and then update our pendingConnections // with the winning channel's state. Closing a channel that was in the // middle of EncryptionRunner would trigger onEncryptionFailed, and, since // the map had already updated with the winning EndpointChannel, we closed // it too by accident. if (!endpointChannelsAreEqual(endpoint_channel_, connection_info->endpoint_channel_.get())) { // TODO(tracyzhou): Add logging. return; } base_pcp_handler_->processPreConnectionInitiationFailure( connection_info->client_proxy_, connection_info->endpoint_channel_->getMedium(), endpoint_id_, connection_info->endpoint_channel_.get(), connection_info->is_incoming_, connection_info->start_time_millis_, Status::ENDPOINT_IO_ERROR, connection_info->request_connection_result_); connection_info->request_connection_result_.clear(); } private: static bool endpointChannelsAreEqual(Ptr lhs, Ptr rhs) { return (lhs->getType() == rhs->getType()) && (lhs->getName() == rhs->getName()) && (lhs->getMedium() == rhs->getMedium()); } Ptr> base_pcp_handler_; const string endpoint_id_; Ptr endpoint_channel_; }; } // namespace base_pcp_handler template const std::int64_t BasePCPHandler::kConnectionRequestReadTimeoutMillis = 2 * 1000; // 2 seconds template const std::int64_t BasePCPHandler::kRejectedConnectionCloseDelayMillis = 2 * 1000; // 2 seconds template BasePCPHandler::BasePCPHandler( Ptr> endpoint_manager, Ptr> endpoint_channel_manager, Ptr> bandwidth_upgrade_manager) : endpoint_manager_(endpoint_manager), endpoint_channel_manager_(endpoint_channel_manager), bandwidth_upgrade_manager_(bandwidth_upgrade_manager), bandwidth_upgrade_medium_(Platform::createAtomicReference( proto::connections::Medium::UNKNOWN_MEDIUM)), alarm_executor_(Platform::createScheduledExecutor()), serial_executor_(Platform::createSingleThreadExecutor()), system_clock_(Platform::createSystemClock()), prng_(), pending_connections_(), discovered_endpoints_(), pending_rejected_connection_close_alarms_(), advertising_options_(), discovery_options_(), encryption_runner_(MakePtr(new EncryptionRunner())) {} template BasePCPHandler::~BasePCPHandler() { // TODO(reznor): // logger.atDebug().log("Initiating shutdown of PCPHandler(%s).", // getStrategy().getName()); // Unregister ourselves from the IncomingOfflineFrameProcessors. endpoint_manager_->unregisterIncomingOfflineFrameProcessor( V1Frame::CONNECTION_RESPONSE, MakePtr(this)); encryption_runner_.destroy(); // Stop all the ongoing Runnables (as gracefully as possible). serial_executor_->shutdown(); alarm_executor_->shutdown(); // With the alarmExecutor shut down already, we can safely clear out our // pending alarms. for (typename PendingRejectedConnectionCloseAlarmsMap::iterator it = pending_rejected_connection_close_alarms_.begin(); it != pending_rejected_connection_close_alarms_.end(); it++) { it->second.destroy(); } pending_rejected_connection_close_alarms_.clear(); for (typename DiscoveredEndpointsMap::iterator it = discovered_endpoints_.begin(); it != discovered_endpoints_.end(); it++) { it->second.destroy(); } discovered_endpoints_.clear(); // Unblock all Futures that were stored in our pendingConnections. for (typename PendingConnectionsMap::iterator it = pending_connections_.begin(); it != pending_connections_.end(); it++) { it->second.destroy(); } pending_connections_.clear(); // TODO(reznor): // logger.atVerbose().log("PCPHandler(%s) has shut down.", // getStrategy().getName()); } template Status::Value BasePCPHandler::startAdvertising( Ptr> client_proxy, const string& service_id, const string& local_endpoint_name, const AdvertisingOptions& advertising_options, Ptr connection_lifecycle_listener) { ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StartAdvertisingCallable( MakePtr(this), client_proxy, service_id, local_endpoint_name, advertising_options, connection_lifecycle_listener)))); return waitForResult("startAdvertising(" + local_endpoint_name + ")", client_proxy->getClientId(), result.get()); } template void BasePCPHandler::stopAdvertising( Ptr> client_proxy) { ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StopAdvertisingRunnable( MakePtr(this), client_proxy, latch.get()))); waitForLatch("stopAdvertising", latch.get()); } template Status::Value BasePCPHandler::startDiscovery( Ptr> client_proxy, const string& service_id, const DiscoveryOptions& discovery_options, Ptr discovery_listener) { ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StartDiscoveryCallable( MakePtr(this), client_proxy, service_id, discovery_options, discovery_listener)))); return waitForResult("startDiscovery(" + service_id + ")", client_proxy->getClientId(), result.get()); } template void BasePCPHandler::stopDiscovery( Ptr> client_proxy) { ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StopDiscoveryRunnable( MakePtr(this), client_proxy, latch.get()))); waitForLatch("stopDiscovery", latch.get()); } template Status::Value BasePCPHandler::requestConnection( Ptr> client_proxy, const string& local_endpoint_name, const string& endpoint_id, Ptr connection_lifecycle_listener) { ScopedPtr>> result( Platform::template createSettableFuture()); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::RequestConnectionRunnable( MakePtr(this), client_proxy, local_endpoint_name, endpoint_id, connection_lifecycle_listener, result.get()))); return waitForResult("requestConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); } template Status::Value BasePCPHandler::acceptConnection( Ptr> client_proxy, const string& endpoint_id, Ptr payload_listener) { ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::AcceptConnectionCallable( MakePtr(this), client_proxy, endpoint_id, payload_listener)))); return waitForResult("acceptConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); } template Status::Value BasePCPHandler::rejectConnection( Ptr> client_proxy, const string& endpoint_id) { ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::RejectConnectionCallable( MakePtr(this), client_proxy, endpoint_id)))); return waitForResult("rejectConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); } template proto::connections::Medium BasePCPHandler::getBandwidthUpgradeMedium() { return bandwidth_upgrade_medium_->get(); } template void BasePCPHandler::processIncomingOfflineFrame( ConstPtr offline_frame, const string& from_endpoint_id, Ptr> to_client_proxy, proto::connections::Medium current_medium) { onConnectionResponse(to_client_proxy, from_endpoint_id, offline_frame); } template void BasePCPHandler::processEndpointDisconnection( Ptr> client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) { runOnPCPHandlerThread(MakePtr( new base_pcp_handler::ProcessEndpointDisconnectionRunnable( MakePtr(this), client_proxy, endpoint_id, process_disconnection_barrier))); } template void BasePCPHandler::onEncryptionSuccessImpl( const string& endpoint_id, Ptr ukey2_handshake, const string& authentication_token, ConstPtr raw_authentication_token) { runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnEncryptionSuccessRunnable( MakePtr(this), endpoint_id, ukey2_handshake, authentication_token, raw_authentication_token))); } template void BasePCPHandler::onEncryptionFailureImpl( const string& endpoint_id, Ptr channel) { runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnEncryptionFailureRunnable( MakePtr(this), endpoint_id, channel))); } template void BasePCPHandler::runOnPCPHandlerThread(Ptr runnable) { serial_executor_->execute(runnable); } template Ptr BasePCPHandler::getAdvertisingOptions() { return advertising_options_; } template void BasePCPHandler::onEndpointFound( Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) { ScopedPtr::DiscoveredEndpoint>> scoped_endpoint(endpoint); // Check if we've seen this endpoint ID before. Ptr::DiscoveredEndpoint> previously_discovered_endpoint = getDiscoveredEndpoint(scoped_endpoint->getEndpointId()); if (previously_discovered_endpoint.isNull()) { const string endpoint_id = scoped_endpoint->getEndpointId(); const string service_id = scoped_endpoint->getServiceId(); const string endpoint_name = scoped_endpoint->getEndpointName(); const proto::connections::Medium medium = scoped_endpoint->getMedium(); // If this is the first medium we've discovered this endpoint over, then add // it to the map. discovered_endpoints_.insert( std::make_pair(endpoint_id, scoped_endpoint.release())); // And, as it's the first time, report it to the client. client_proxy->onEndpointFound(endpoint_id, service_id, endpoint_name, medium); } else if (previously_discovered_endpoint->getEndpointName() != scoped_endpoint->getEndpointName()) { // If we've already seen this endpoint before, check if there was a name // change. If there was, report the previous endpoint as lost. // TODO(tracyzhou): Add logging. onEndpointLost(client_proxy, previously_discovered_endpoint); onEndpointFound(client_proxy, scoped_endpoint.release()); } else { // Otherwise, we need to see if the medium we discovered the endpoint over // this time is better than the medium we originally discovered the endpoint // over. if (isPreferred(scoped_endpoint.get(), previously_discovered_endpoint)) { base_pcp_handler::eraseOwnedPtrFromMap(discovered_endpoints_, scoped_endpoint->getEndpointId()); discovered_endpoints_.insert(std::make_pair( scoped_endpoint->getEndpointId(), scoped_endpoint.release())); } } } template void BasePCPHandler::onEndpointLost( Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) { ScopedPtr::DiscoveredEndpoint>> scoped_endpoint(endpoint); // Look up the DiscoveredEndpoint we have in our cache. Ptr::DiscoveredEndpoint> discoveredEndpoint = getDiscoveredEndpoint(scoped_endpoint->getEndpointId()); if (discoveredEndpoint.isNull()) { // TODO(tracyzhou): Add logging. return; } // Validate that the cached endpoint has the same name as the one reported as // onLost. If the name differs, then no-op. This likely means that the remote // device changed their name. We reported onFound for the new name and are // just now figuring out that we lost the old name. if (discoveredEndpoint->getEndpointName() != scoped_endpoint->getEndpointName()) { // TODO(tracyzhou): Add logging. return; } base_pcp_handler::eraseOwnedPtrFromMap(discovered_endpoints_, scoped_endpoint->getEndpointId()); client_proxy->onEndpointLost(scoped_endpoint->getServiceId(), scoped_endpoint->getEndpointId()); } template bool BasePCPHandler::hasOutgoingConnections( Ptr> client_proxy) { for (typename PendingConnectionsMap::iterator it = pending_connections_.begin(); it != pending_connections_.end(); it++) { if (!it->second->is_incoming_) { return true; } } return client_proxy->getNumOutgoingConnections() > 0; } template bool BasePCPHandler::hasIncomingConnections( Ptr> client_proxy) { for (typename PendingConnectionsMap::iterator it = pending_connections_.begin(); it != pending_connections_.end(); it++) { if (it->second->is_incoming_) { return true; } } return client_proxy->getNumIncomingConnections() > 0; } template bool BasePCPHandler::canSendOutgoingConnection( Ptr> client_proxy) { return true; } template bool BasePCPHandler::canReceiveIncomingConnection( Ptr> client_proxy) { return true; } template Exception::Value BasePCPHandler::writeConnectionRequestFrame( Ptr endpoint_channel, const string& local_endpoint_id, const string& local_endpoint_name, std::int32_t nonce, const std::vector& supported_mediums) { Exception::Value write_exception = endpoint_channel->write(OfflineFrames::forConnectionRequest( local_endpoint_id, local_endpoint_name, nonce, supported_mediums)); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { return write_exception; } } return Exception::NONE; } template template Ptr> BasePCPHandler::runOnPCPHandlerThread( Ptr> callable) { return serial_executor_->submit(callable); } template void BasePCPHandler::onConnectionResponse( Ptr> client_proxy, const string& endpoint_id, ConstPtr connection_response_offline_frame) { ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnConnectionResponseRunnable( MakePtr(this), client_proxy, endpoint_id, connection_response_offline_frame, latch.get()))); waitForLatch("onConnectionResponse()", latch.get()); } template bool BasePCPHandler::isPreferred( Ptr::DiscoveredEndpoint> new_endpoint, Ptr::DiscoveredEndpoint> old_endpoint) { std::vector mediums = getConnectionMediumsByPriority(); // As we iterate through the list of mediums, we see if we run into the new // endpoint's medium or the old endpoint's medium first. for (std::vector::const_iterator it = mediums.begin(); it != mediums.end(); it++) { const proto::connections::Medium& medium = *it; if (medium == new_endpoint->getMedium()) { // The new endpoint's medium came first. It's preferred! return true; } if (medium == old_endpoint->getMedium()) { // The old endpoint's medium came first. Stick with the old endpoint! return false; } } // TODO(tracyzhou): Add logging. assert(false); return false; } template bool BasePCPHandler::shouldEnforceTopologyConstraints() { // Topology constraints only matter for the advertiser. // For discoverers, we'll always enforce them. if (getAdvertisingOptions().isNull()) { return true; } return getAdvertisingOptions()->enforce_topology_constraints; } template bool BasePCPHandler::autoUpgradeBandwidth() { if (getAdvertisingOptions().isNull()) { return true; } return getAdvertisingOptions()->auto_upgrade_bandwidth; } template Exception::Value BasePCPHandler::onIncomingConnection( Ptr> client_proxy, const string& remote_device_name, Ptr endpoint_channel, proto::connections::Medium medium) { ScopedPtr> scoped_endpoint_channel(endpoint_channel); std::int64_t start_time_millis = system_clock_->elapsedRealtime(); // Fixes an NPE in ClientProxy.onConnectionResult. The crash happened when // the client stopped advertising and we nulled out state, followed by an // incoming connection where we attempted to check that state. if (!client_proxy->isAdvertising()) { NEARBY_LOG(WARNING, "Ignoring incoming connection because client %" PRId64 " is no longer advertising.", client_proxy->getClientId()); return Exception::IO; } // Endpoints connecting to us will always tell us about themselves first. ExceptionOr> read_offline_frame = readConnectionRequestFrame(scoped_endpoint_channel.get()); if (!read_offline_frame.ok()) { if (Exception::IO == read_offline_frame.exception()) { // TODO(tracyzhou): Add logging. processPreConnectionInitiationFailure( client_proxy, medium, "", scoped_endpoint_channel.get(), /* is_incoming= */ true, start_time_millis, Status::ERROR, Ptr>()); return Exception::NONE; } } // TODO(tracyzhou): Add logging. ScopedPtr> scoped_read_offline_frame( read_offline_frame.result()); const ConnectionRequestFrame& connection_request = scoped_read_offline_frame->v1().connection_request(); if (client_proxy->isConnectedToEndpoint(connection_request.endpoint_id())) { return Exception::IO; } // If we've already sent out a connection request to this endpoint, then this // is where we need to decide which connection to break. if (breakTie(client_proxy, connection_request.endpoint_id(), connection_request.nonce(), scoped_endpoint_channel.get())) { return Exception::NONE; } // If our child class says we can't accept any more incoming connections, // listen to them. if (shouldEnforceTopologyConstraints() && !canReceiveIncomingConnection(client_proxy)) { return Exception::IO; } // We've successfully connected to the device, and are now about to jump on to // the EncryptionRunner thread to start running our encryption protocol. We'll // mark ourselves as pending in case we get another call to requestConnection // or onIncomingConnection, so that we can cancel the connection if needed. endpoint_channel = pending_connections_ .insert(std::make_pair( connection_request.endpoint_id(), PendingConnectionInfo::newIncomingPendingConnectionInfo( client_proxy, connection_request.endpoint_name(), scoped_endpoint_channel.release(), connection_request.nonce(), start_time_millis, advertising_connection_lifecycle_listener_, OfflineFrames::connectionRequestMediumsToMediums( connection_request)))) .first->second->endpoint_channel_.get(); // Next, we'll set up encryption. encryption_runner_->startServer( client_proxy, connection_request.endpoint_id(), endpoint_channel, MakePtr(new typename BasePCPHandler::ResultListenerFacade( MakePtr(this)))); return Exception::NONE; } template bool BasePCPHandler::breakTie(Ptr> client_proxy, const string& endpoint_id, std::int32_t incoming_nonce, Ptr endpoint_channel) { typename PendingConnectionsMap::iterator it = pending_connections_.find(endpoint_id); if (it != pending_connections_.end()) { Ptr::PendingConnectionInfo> pending_connection_info = it->second; // TODO(tracyzhou): Add logging. // Break the lowest connection. In the (extremely) rare case of a tie, break // both. if (pending_connection_info->nonce_ > incoming_nonce) { // Our connection won! Clean up their connection. endpoint_channel->close(); // TODO(tracyzhou): Add logging. return true; } else if (pending_connection_info->nonce_ < incoming_nonce) { // Aw, we lost. Clean up our connection, and then we'll let their // connection continue on. processTieBreakLoss(client_proxy, endpoint_id, pending_connection_info); // TODO(tracyzhou): Add logging. } else { // Oh. Huh. We both lost. Well, that's awkward. We'll clean up both and // just force the devices to retry. endpoint_channel->close(); processTieBreakLoss(client_proxy, endpoint_id, pending_connection_info); // TODO(tracyzhou): Add logging. return true; } } return false; } template void BasePCPHandler::processTieBreakLoss( Ptr> client_proxy, const string& endpoint_id, Ptr connection_info) { processPreConnectionInitiationFailure( client_proxy, connection_info->endpoint_channel_->getMedium(), endpoint_id, connection_info->endpoint_channel_.get(), connection_info->is_incoming_, connection_info->start_time_millis_, Status::ENDPOINT_IO_ERROR, connection_info->request_connection_result_); connection_info->request_connection_result_.clear(); processPreConnectionResultFailure(client_proxy, endpoint_id); } template void BasePCPHandler::initiateBandwidthUpgrade( Ptr> client_proxy, const string& endpoint_id, const std::vector& supported_mediums) { // When we successfully connect to a remote endpoint and a bandwidth upgrade // medium has not yet been decided, we'll pick the highest bandwidth medium // supported by both us and the remote endpoint. Once we pick a medium, all // future connections will use it too. eg. If we chose Wifi LAN, we'll attempt // to upgrade the 2nd, 3rd, etc remote endpoints with Wifi LAN even if they're // on a different network (or had a better medium). This is a quick and easy // way to prevent mediums, like Wifi Hotspot, from interfering with active // connections (although it's suboptimal for bandwidth throughput). When all // endpoints disconnect, we reset the bandwidth upgrade medium. if (bandwidth_upgrade_medium_->get() == proto::connections::Medium::UNKNOWN_MEDIUM) { bandwidth_upgrade_medium_->set(chooseBestUpgradeMedium(supported_mediums)); } if (autoUpgradeBandwidth() && (bandwidth_upgrade_medium_->get() != proto::connections::Medium::UNKNOWN_MEDIUM)) { bandwidth_upgrade_manager_->initiateBandwidthUpgradeForEndpoint( client_proxy, endpoint_id, bandwidth_upgrade_medium_->get()); } } template proto::connections::Medium BasePCPHandler::chooseBestUpgradeMedium( const std::vector& their_supported_mediums) { // If the remote side did not report their supported mediums, choose an // appropriate default. std::vector their_mediums = their_supported_mediums; if (their_supported_mediums.empty()) { their_mediums.push_back(getDefaultUpgradeMedium()); } // Otherwise, pick the best medium we support. std::vector my_mediums = getConnectionMediumsByPriority(); for (std::vector::iterator my_medium = my_mediums.begin(); my_medium != my_mediums.end(); my_medium++) { for (std::vector::iterator their_medium = their_mediums.begin(); their_medium != their_mediums.end(); their_medium++) { if (*my_medium == *their_medium) { return *my_medium; } } } return proto::connections::Medium::UNKNOWN_MEDIUM; } template void BasePCPHandler::processPreConnectionInitiationFailure( Ptr> client_proxy, proto::connections::Medium medium, const string& endpoint_id, Ptr endpoint_channel, bool is_incoming, std::int64_t start_time_millis, Status::Value status, Ptr> request_connection_result) { // Only *remove* this -- as opposed to *destroying* it by invoking // eraseOwnedPtrFromMap() -- because if endpoint_channel is non-null, it's // owned by the PendingConnectionInfo in pending_connections_, which means // destroying the PendingConnectionInfo right now will lead to a dangling // pointer access when we invoke endpoint_channel->close() below. ScopedPtr> failed_pending_connection( base_pcp_handler::removeOwnedPtrFromMap(pending_connections_, endpoint_id)); if (!endpoint_channel.isNull()) { endpoint_channel->close(); } if (!request_connection_result.isNull()) { request_connection_result->set(status); } } template void BasePCPHandler::processPreConnectionResultFailure( Ptr> client_proxy, const string& endpoint_id) { base_pcp_handler::eraseOwnedPtrFromMap(pending_connections_, endpoint_id); endpoint_manager_->discardEndpoint(client_proxy, endpoint_id); client_proxy->onConnectionResult(endpoint_id, Status::ERROR); } template Ptr::DiscoveredEndpoint> BasePCPHandler::getDiscoveredEndpoint(const string& endpoint_id) { typename DiscoveredEndpointsMap::iterator it = discovered_endpoints_.find(endpoint_id); if (it == discovered_endpoints_.end()) { return Ptr::DiscoveredEndpoint>(); } return it->second; } template void BasePCPHandler::evaluateConnectionResult( Ptr> client_proxy, const string& endpoint_id, bool can_close_immediately) { // Short-circuit immediately if we're not in an actionable state yet. We will // be called again once the other side has made their decision. if (!client_proxy->isConnectionAccepted(endpoint_id) && !client_proxy->isConnectionRejected(endpoint_id)) { if (!client_proxy->hasLocalEndpointResponded(endpoint_id)) { // TODO(tracyzhou): Add logging. } else if (!client_proxy->hasRemoteEndpointResponded(endpoint_id)) { // TODO(tracyzhou): Add logging. } return; } // Clean up the endpoint channel from our list of 'pending' connections. It's // no longer pending. typename PendingConnectionsMap::iterator it = pending_connections_.find(endpoint_id); if (it == pending_connections_.end()) { // TODO(tracyzhou): Add logging. return; } ScopedPtr::PendingConnectionInfo>> connection_info(it->second); pending_connections_.erase(it); bool is_connection_accepted = client_proxy->isConnectionAccepted(endpoint_id); Status::Value response_code; if (is_connection_accepted) { // TODO(tracyzhou): Add logging. response_code = Status::SUCCESS; // Both sides have accepted, so we can now start talking over encrypted // channels std::unique_ptr encryption_context = connection_info->ukey2_handshake_->ToConnectionContext(); // Java code throws an HandshakeException. if (encryption_context == nullptr) { // TODO(tracyzhou): Add logging. processPreConnectionResultFailure(client_proxy, endpoint_id); return; } endpoint_channel_manager_->encryptChannelForEndpoint( endpoint_id, MakeRefCountedPtr(encryption_context.release())); } else { // TODO(tracyzhou): Add logging. response_code = Status::CONNECTION_REJECTED; } // Invoke the client callback to let it know of the connection result. client_proxy->onConnectionResult(endpoint_id, response_code); // If the connection failed, clean everything up and short circuit. if (!is_connection_accepted) { // Clean up the channel in EndpointManager if it's no longer required. if (can_close_immediately) { endpoint_manager_->discardEndpoint(client_proxy, endpoint_id); } else { pending_rejected_connection_close_alarms_.insert(std::make_pair( endpoint_id, MakePtr(new CancelableAlarm( "BasePCPHandler.evaluateConnectionResult() delayed close", MakePtr( new base_pcp_handler:: EvaluateConnectionResultCancelableAlarmRunnable( endpoint_manager_, client_proxy, endpoint_id)), kRejectedConnectionCloseDelayMillis, alarm_executor_.get())))); } return; } // Kick off the bandwidth upgrade for incoming connections. if (connection_info->is_incoming_) { initiateBandwidthUpgrade(client_proxy, endpoint_id, connection_info->supported_mediums_); } } template ExceptionOr> BasePCPHandler::readConnectionRequestFrame( Ptr endpoint_channel) { if (endpoint_channel.isNull()) { return ExceptionOr>(Exception::IO); } // To avoid a device connecting but never sending their introductory frame, we // time out the connection after a certain amount of time. CancelableAlarm timeout_alarm( "PCPHandler(" + this->getStrategy().getName() + ").readConnectionRequestFrame", MakePtr( new base_pcp_handler::ReadConnectionRequestCancelableAlarmRunnable( endpoint_channel)), kConnectionRequestReadTimeoutMillis, alarm_executor_.get()); // Do a blocking read to try and find the ConnectionRequestFrame ExceptionOr> read_bytes = endpoint_channel->read(); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { timeout_alarm.cancel(); return ExceptionOr>(read_bytes.exception()); } } ScopedPtr> scoped_read_bytes(read_bytes.result()); ExceptionOr> offline_frame = OfflineFrames::fromBytes(scoped_read_bytes.get()); if (!offline_frame.ok()) { if (Exception::INVALID_PROTOCOL_BUFFER == offline_frame.exception()) { timeout_alarm.cancel(); // In Java code, INVALID_PROTOCOL_BUFFER is a subtype of IO exception. return ExceptionOr>(Exception::IO); } } timeout_alarm.cancel(); ScopedPtr> scoped_offline_frame( offline_frame.result()); if (V1Frame::CONNECTION_REQUEST != OfflineFrames::getFrameType(scoped_offline_frame.get())) { return ExceptionOr>(Exception::IO); } return ExceptionOr>(scoped_offline_frame.release()); } template void BasePCPHandler::waitForLatch(const string& method_name, Ptr latch) { Exception::Value await_exception = latch->await(); if (Exception::NONE != await_exception) { if (Exception::INTERRUPTED == await_exception) { // TODO(tracyzhou): Add logging. // Thread.currentThread().interrupt(); } } } template Status::Value BasePCPHandler::waitForResult( const string& method_name, std::int64_t client_id, Ptr> result_future) { ExceptionOr result = result_future->get(); if (!result.ok()) { Exception::Value exception = result.exception(); if (Exception::INTERRUPTED == exception || Exception::EXECUTION == exception) { // TODO(tracyzhou): Add logging. if (Exception::INTERRUPTED == exception) { // Thread.currentThread().interrupt(); } return Status::ERROR; } } return result.result(); } ///////////////////// BasePCPHandler::PendingConnectionInfo /////////////////// template Ptr::PendingConnectionInfo> BasePCPHandler::PendingConnectionInfo:: newIncomingPendingConnectionInfo( Ptr> client_proxy, const string& remote_endpoint_name, Ptr endpoint_channel, std::int32_t nonce, std::int64_t start_time_millis, Ptr connection_lifecycle_listener, const std::vector& supported_mediums) { return MakePtr(new PendingConnectionInfo( client_proxy, remote_endpoint_name, endpoint_channel, nonce, true, start_time_millis, connection_lifecycle_listener, Ptr>(), supported_mediums)); } template Ptr::PendingConnectionInfo> BasePCPHandler::PendingConnectionInfo:: newOutgoingPendingConnectionInfo( Ptr> client_proxy, const string& remote_endpoint_name, Ptr endpoint_channel, std::int32_t nonce, std::int64_t start_time_millis, Ptr connection_lifecycle_listener, Ptr> request_connection_result) { return MakePtr(new PendingConnectionInfo( client_proxy, remote_endpoint_name, endpoint_channel, nonce, false, start_time_millis, connection_lifecycle_listener, request_connection_result, std::vector())); } template BasePCPHandler::PendingConnectionInfo::PendingConnectionInfo( Ptr> client_proxy, const string& remote_endpoint_name, Ptr endpoint_channel, std::int32_t nonce, bool is_incoming, std::int64_t start_time_millis, Ptr connection_lifecycle_listener, Ptr> request_connection_result, const std::vector& supported_mediums) : client_proxy_(client_proxy), remote_endpoint_name_(remote_endpoint_name), endpoint_channel_(endpoint_channel), nonce_(nonce), is_incoming_(is_incoming), start_time_millis_(start_time_millis), connection_lifecycle_listener_(connection_lifecycle_listener), request_connection_result_(request_connection_result), supported_mediums_(supported_mediums), ukey2_handshake_() {} template BasePCPHandler::PendingConnectionInfo::~PendingConnectionInfo() { if (!request_connection_result_.isNull()) { request_connection_result_->set(Status::ERROR); } if (!endpoint_channel_.isNull()) { endpoint_channel_->close(proto::connections::DisconnectionReason::SHUTDOWN); } // Done with operational cleanup, now deallocate memory as needed. ukey2_handshake_.destroy(); } template void BasePCPHandler::PendingConnectionInfo::setUKey2Handshake( Ptr ukey2_handshake) { this->ukey2_handshake_ = ukey2_handshake; } template void BasePCPHandler::PendingConnectionInfo:: localEndpointAcceptedConnection(const string& endpoint_id, Ptr payload_listener) { if (!ukey2_handshake_->VerifyHandshake()) { NEARBY_LOG( FATAL, "Failed to verify UKEY2 handshake with %s after accepting locally.", endpoint_id.c_str()); } client_proxy_->localEndpointAcceptedConnection(endpoint_id, payload_listener); } template void BasePCPHandler::PendingConnectionInfo:: localEndpointRejectedConnection(const string& endpoint_id) { client_proxy_->localEndpointRejectedConnection(endpoint_id); } } // namespace connections } // namespace nearby } // namespace location