#include "core/internal/service_controller_router.h" #include "core/internal/offline_service_controller.h" namespace location { namespace nearby { namespace connections { namespace service_controller_router { // Base class for the following Runnable classes. They all need a // ServiceControllerRouter object and a ClientProxy object. // ServiceControllerRouter is kept as a reference because the passed in // Ptr > should outlive it. template class ServiceControllerRouterRunnable : public Runnable { protected: ServiceControllerRouterRunnable( Ptr > service_controller_router, Ptr > client_proxy) : service_controller_router_(service_controller_router), client_proxy_(client_proxy) {} Ptr > service_controller_router_; Ptr > client_proxy_; }; template class StartAdvertisingRunnable : public ServiceControllerRouterRunnable { public: StartAdvertisingRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr start_advertising_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(start_advertising_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); Status::Value status = this->service_controller_router_->acquireServiceControllerForClient( this->client_proxy_, params_->advertising_options.strategy); if (Status::SUCCESS != status) { result_listener->onResult(status); return; } if (this->client_proxy_->isAdvertising()) { result_listener->onResult(Status::ALREADY_ADVERTISING); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->startAdvertising(this->client_proxy_, params_->name, params_->service_id, params_->advertising_options, params_->connection_lifecycle_listener)); } private: ScopedPtr > params_; }; template class StopAdvertisingRunnable : public ServiceControllerRouterRunnable { public: StopAdvertisingRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr stop_advertising_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(stop_advertising_params) {} void run() override { if (this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_) && this->client_proxy_->isAdvertising()) { this->service_controller_router_->current_service_controller_ ->stopAdvertising(this->client_proxy_); } } private: ScopedPtr > params_; }; template class StartDiscoveryRunnable : public ServiceControllerRouterRunnable { public: StartDiscoveryRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr start_discovery_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(start_discovery_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); Status::Value status = this->service_controller_router_->acquireServiceControllerForClient( this->client_proxy_, params_->discovery_options.strategy); if (Status::SUCCESS != status) { result_listener->onResult(status); return; } if (this->client_proxy_->isDiscovering()) { result_listener->onResult(Status::ALREADY_DISCOVERING); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->startDiscovery(this->client_proxy_, params_->service_id, params_->discovery_options, params_->discovery_listener)); } private: ScopedPtr > params_; }; template class StopDiscoveryRunnable : public ServiceControllerRouterRunnable { public: StopDiscoveryRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr stop_discovery_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(stop_discovery_params) {} void run() override { if (this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_) && this->client_proxy_->isDiscovering()) { this->service_controller_router_->current_service_controller_ ->stopDiscovery(this->client_proxy_); } } private: ScopedPtr > params_; }; template class SendConnectionRequestRunnable : public ServiceControllerRouterRunnable { public: SendConnectionRequestRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr request_connection_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(request_connection_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } const string& remote_endpoint_id = params_->remote_endpoint_id; if (this->client_proxy_->hasPendingConnectionToEndpoint( remote_endpoint_id) || this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->requestConnection(this->client_proxy_, params_->name, remote_endpoint_id, params_->connection_lifecycle_listener)); } private: ScopedPtr > params_; }; template class AcceptConnectionRequestRunnable : public ServiceControllerRouterRunnable { public: AcceptConnectionRequestRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr accept_connection_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(accept_connection_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } const string& remote_endpoint_id = params_->remote_endpoint_id; if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); return; } if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) { // TODO(tracyzhou): logging result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->acceptConnection(this->client_proxy_, remote_endpoint_id, params_->payload_listener)); } private: ScopedPtr > params_; }; template class RejectConnectionRequestRunnable : public ServiceControllerRouterRunnable { public: RejectConnectionRequestRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr reject_connection_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(reject_connection_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } const string& remote_endpoint_id = params_->remote_endpoint_id; if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); return; } if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) { // TODO(tracyzhou): logging result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->rejectConnection(this->client_proxy_, remote_endpoint_id)); } private: ScopedPtr > params_; }; template class InitiateBandwidthUpgradeRunnable : public ServiceControllerRouterRunnable { public: InitiateBandwidthUpgradeRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr initiate_bandwidth_upgrade_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(initiate_bandwidth_upgrade_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_) || !this->client_proxy_->isConnectedToEndpoint( params_->remote_endpoint_id)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } this->service_controller_router_->current_service_controller_ ->initiateBandwidthUpgrade(this->client_proxy_, params_->remote_endpoint_id); // The caller can listen to // ConnectionLifecycleListener.onBandwidthChanged() to determine success. result_listener->onResult(Status::SUCCESS); } private: ScopedPtr > params_; }; template class SendPayloadRunnable : public ServiceControllerRouterRunnable { public: SendPayloadRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr send_payload_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(send_payload_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } if (!ServiceControllerRouter:: clientHasConnectionToAtLeastOneEndpoint( this->client_proxy_, params_->remote_endpoint_ids)) { result_listener->onResult(Status::ENDPOINT_UNKNOWN); return; } this->service_controller_router_->current_service_controller_->sendPayload( this->client_proxy_, params_->remote_endpoint_ids, params_->payload); // At this point, we've queued up the send Payload request with the // ServiceController; any further failures (e.g. one of the endpoints is // unknown, goes away, or otherwise fails) will be returned to the client // as a PayloadTransferUpdate. result_listener->onResult(Status::SUCCESS); } private: ScopedPtr > params_; }; template class CancelPayloadRunnable : public ServiceControllerRouterRunnable { public: CancelPayloadRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr cancel_payload_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(cancel_payload_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); return; } result_listener->onResult( this->service_controller_router_->current_service_controller_ ->cancelPayload(this->client_proxy_, params_->payload_id)); } private: ScopedPtr > params_; }; template class DisconnectFromEndpointRunnable : public ServiceControllerRouterRunnable { public: DisconnectFromEndpointRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr disconnect_from_endpoint_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(disconnect_from_endpoint_params) {} void run() override { if (this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { const string& remote_endpoint_id = params_->remote_endpoint_id; if (!this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id) && !this->client_proxy_->hasPendingConnectionToEndpoint( remote_endpoint_id)) { return; } this->service_controller_router_->current_service_controller_ ->disconnectFromEndpoint(this->client_proxy_, remote_endpoint_id); } } private: ScopedPtr > params_; }; template class StopAllEndpointsRunnable : public ServiceControllerRouterRunnable { public: StopAllEndpointsRunnable( Ptr > service_controller_router, Ptr > client_proxy, ConstPtr stop_all_endpoints_params) : ServiceControllerRouterRunnable(service_controller_router, client_proxy), params_(stop_all_endpoints_params) {} void run() override { ScopedPtr > result_listener(params_->result_listener); if (this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { this->service_controller_router_->doneWithStrategySessionForClient( this->client_proxy_); } result_listener->onResult(Status::SUCCESS); } private: ScopedPtr > params_; }; template class ClientDisconnectingRunnable : public ServiceControllerRouterRunnable { public: ClientDisconnectingRunnable( Ptr> service_controller_router, Ptr> client_proxy) : ServiceControllerRouterRunnable(service_controller_router, client_proxy) {} void run() override { if (!this->service_controller_router_->clientHasAquiredServiceController( this->client_proxy_)) { return; } this->service_controller_router_->doneWithStrategySessionForClient( this->client_proxy_); // Log the completion of this client's connection. // TODO(tracyzhou): Add logging. } }; } // namespace service_controller_router template ServiceControllerRouter::ServiceControllerRouter() : current_service_controller_clients_(), current_service_controller_(new OfflineServiceController()), current_strategy_(), serializer_(Platform::createSingleThreadExecutor()) {} template ServiceControllerRouter::~ServiceControllerRouter() { // TODO(tracyzhou): Add logging. // And make sure that cleanup is the last thing we do. serializer_->shutdown(); current_service_controller_.destroy(); current_strategy_.destroy(); current_service_controller_clients_.clear(); } template void ServiceControllerRouter::startAdvertising( Ptr > client_proxy, ConstPtr start_advertising_params) { routeToServiceController( MakePtr(new service_controller_router::StartAdvertisingRunnable( MakePtr(this), client_proxy, start_advertising_params))); } template void ServiceControllerRouter::stopAdvertising( Ptr > client_proxy, ConstPtr stop_advertising_params) { routeToServiceController( MakePtr(new service_controller_router::StopAdvertisingRunnable( MakePtr(this), client_proxy, stop_advertising_params))); } template void ServiceControllerRouter::startDiscovery( Ptr > client_proxy, ConstPtr start_discovery_params) { routeToServiceController( MakePtr(new service_controller_router::StartDiscoveryRunnable( MakePtr(this), client_proxy, start_discovery_params))); } template void ServiceControllerRouter::stopDiscovery( Ptr > client_proxy, ConstPtr stop_discovery_params) { routeToServiceController( MakePtr(new service_controller_router::StopDiscoveryRunnable( MakePtr(this), client_proxy, stop_discovery_params))); } template void ServiceControllerRouter::requestConnection( Ptr > client_proxy, ConstPtr request_connection_params) { routeToServiceController(MakePtr( new service_controller_router::SendConnectionRequestRunnable( MakePtr(this), client_proxy, request_connection_params))); } template void ServiceControllerRouter::acceptConnection( Ptr > client_proxy, ConstPtr accept_connection_params) { routeToServiceController(MakePtr( new service_controller_router::AcceptConnectionRequestRunnable( MakePtr(this), client_proxy, accept_connection_params))); } template void ServiceControllerRouter::rejectConnection( Ptr > client_proxy, ConstPtr reject_connection_params) { routeToServiceController(MakePtr( new service_controller_router::RejectConnectionRequestRunnable( MakePtr(this), client_proxy, reject_connection_params))); } template void ServiceControllerRouter::initiateBandwidthUpgrade( Ptr > client_proxy, ConstPtr initiate_bandwidth_upgrade_params) { routeToServiceController(MakePtr( new service_controller_router::InitiateBandwidthUpgradeRunnable( MakePtr(this), client_proxy, initiate_bandwidth_upgrade_params))); } template void ServiceControllerRouter::sendPayload( Ptr > client_proxy, ConstPtr send_payload_params) { routeToServiceController( MakePtr(new service_controller_router::SendPayloadRunnable( MakePtr(this), client_proxy, send_payload_params))); } template void ServiceControllerRouter::cancelPayload( Ptr > client_proxy, ConstPtr cancel_payload_params) { routeToServiceController( MakePtr(new service_controller_router::CancelPayloadRunnable( MakePtr(this), client_proxy, cancel_payload_params))); } template void ServiceControllerRouter::disconnectFromEndpoint( Ptr > client_proxy, ConstPtr disconnect_from_endpoint_params) { routeToServiceController(MakePtr( new service_controller_router::DisconnectFromEndpointRunnable( MakePtr(this), client_proxy, disconnect_from_endpoint_params))); } template void ServiceControllerRouter::stopAllEndpoints( Ptr > client_proxy, ConstPtr stop_all_endpoint_params) { routeToServiceController( MakePtr(new service_controller_router::StopAllEndpointsRunnable( MakePtr(this), client_proxy, stop_all_endpoint_params))); } template void ServiceControllerRouter::clientDisconnecting( Ptr> client_proxy) { routeToServiceController(MakePtr( new service_controller_router::ClientDisconnectingRunnable( MakePtr(this), client_proxy))); } template Status::Value ServiceControllerRouter::acquireServiceControllerForClient( Ptr > client_proxy, const Strategy& strategy) { if (current_strategy_.isNull()) { // Case 1: There is no existing Strategy at all. // Set everything up for the first time. Status::Value status = updateCurrentServiceControllerAndStrategy(strategy); if (status != Status::SUCCESS) { return status; } current_service_controller_clients_.insert(client_proxy); return Status::SUCCESS; } else if (strategy == *current_strategy_) { // Case 2: The existing Strategy matches. // The new client just needs to be added to the set of clients using the // current ServiceController. current_service_controller_clients_.insert(client_proxy); return Status::SUCCESS; } else { // Case 3: The existing Strategy doesn't match. // It's only safe for a client to cause a switch if it's the only client // using the current ServiceController. bool is_the_only_client_of_service_controller = current_service_controller_clients_.size() == 1 && current_service_controller_clients_.find(client_proxy) != current_service_controller_clients_.end(); if (!is_the_only_client_of_service_controller) { // TODO(tracyzhou): logging return Status::ALREADY_HAVE_ACTIVE_STRATEGY; } // If the client still has connected endpoints, they must disconnect before // they can switch. if (!client_proxy->getConnectedEndpoints().empty()) { // TODO(tracyzhou): logging return Status::OUT_OF_ORDER_API_CALL; } // By this point, it's safe to switch the Strategy and ServiceController // (and since it's the only client, there's no need to add it to the set of // clients using the current ServiceController). return updateCurrentServiceControllerAndStrategy(strategy); } } template bool ServiceControllerRouter::clientHasAquiredServiceController( Ptr > client_proxy) { return (current_service_controller_clients_.find(client_proxy) != current_service_controller_clients_.end()); } template void ServiceControllerRouter::releaseServiceControllerForClient( Ptr > client_proxy) { current_service_controller_clients_.erase(client_proxy); if (current_service_controller_clients_.empty()) { current_service_controller_.destroy(); current_strategy_.destroy(); } } /** Clean up all state for this client. The client is now free to switch * strategies. */ template void ServiceControllerRouter::doneWithStrategySessionForClient( Ptr > client_proxy) { // Disconnect from all the connected endpoints tied to this clientProxy. std::vector pending_connected_endpoints = client_proxy->getPendingConnectedEndpoints(); for (std::vector::iterator it = pending_connected_endpoints.begin(); it != pending_connected_endpoints.end(); it++) { current_service_controller_->disconnectFromEndpoint(client_proxy, *it); } std::vector connected_endpoints = client_proxy->getConnectedEndpoints(); for (std::vector::iterator it = connected_endpoints.begin(); it != connected_endpoints.end(); it++) { current_service_controller_->disconnectFromEndpoint(client_proxy, *it); } // Stop any advertising and discovery that may be underway due to this // clientProxy. current_service_controller_->stopAdvertising(client_proxy); current_service_controller_->stopDiscovery(client_proxy); // Finally, clear all state maintained by this clientProxy. client_proxy->reset(); releaseServiceControllerForClient(client_proxy); } template void ServiceControllerRouter::routeToServiceController( Ptr runnable) { serializer_->execute(runnable); } template bool ServiceControllerRouter::clientHasConnectionToAtLeastOneEndpoint( Ptr > client_proxy, const std::vector& remote_endpoint_ids) { for (std::vector::const_iterator it = remote_endpoint_ids.begin(); it != remote_endpoint_ids.end(); it++) { if (client_proxy->isConnectedToEndpoint(*it)) { return true; } } return false; } template Status::Value ServiceControllerRouter::updateCurrentServiceControllerAndStrategy( const Strategy& strategy) { if (!strategy.isValid()) { // TODO(tracyzhou): logging return Status::ERROR; } current_service_controller_.destroy(); current_service_controller_ = MakePtr(new OfflineServiceController()); current_strategy_.destroy(); current_strategy_ = MakePtr(new Strategy(strategy)); return Status::SUCCESS; } } // namespace connections } // namespace nearby } // namespace location