#include "core/internal/endpoint_manager.h" #include #include "core/internal/offline_frames.h" #include "proto/connections_enums.pb.h" namespace location { namespace nearby { namespace connections { namespace endpoint_manager { // A Runnable that continuously grabs the most recent EndpointChannel available // for an endpoint. Override // EndpointChannelLoopRunnable.execute(EndpointChannel) to interact with the // EndpointChannel. template class EndpointChannelLoopRunnable : public Runnable { public: EndpointChannelLoopRunnable(Ptr> endpoint_manager, const string& runnable_name, Ptr> client_proxy, const string& endpoint_id) : endpoint_manager_(endpoint_manager), runnable_name_(runnable_name), client_proxy_(client_proxy), endpoint_id_(endpoint_id) {} ~EndpointChannelLoopRunnable() override {} void run() override { // The implication of using the EndpointChannel's medium to identify it is // that this loop will break if we ever allow creating multiple // EndpointChannels to the same endpoint over the same medium. proto::connections::Medium last_failed_endpoint_channel_medium = proto::connections::UNKNOWN_MEDIUM; while (true) { // It's important to keep re-fetching the EndpointChannel for an endpoint // because it can be changed out from under us (for example, when we // upgrade from Bluetooth to Wifi). ScopedPtr> scoped_endpoint_channel( endpoint_manager_->endpoint_channel_manager_->getChannelForEndpoint( endpoint_id_)); if (scoped_endpoint_channel.isNull()) { // TODO(tracyzhou): Add logging. break; } // If we're looping back around after a failure, and there's not a new // EndpointChannel for this endpoint, there's nothing more to do here. if ((last_failed_endpoint_channel_medium != proto::connections::UNKNOWN_MEDIUM) && (scoped_endpoint_channel->getMedium() == last_failed_endpoint_channel_medium)) { // TODO(tracyzhou): Add logging. break; } ExceptionOr keep_using_channel = useHealthyEndpointChannel(scoped_endpoint_channel.get()); if (!keep_using_channel.ok()) { Exception::Value exception = keep_using_channel.exception(); if (Exception::IO == exception) { last_failed_endpoint_channel_medium = scoped_endpoint_channel->getMedium(); // TODO(tracyzhou): Add logging. continue; } if (Exception::INTERRUPTED == exception) { // Thread.currentThread().interrupt(); // TODO(tracyzhou): Add logging. break; } } if (!keep_using_channel.result()) { // TODO(tracyzhou): Add logging. break; } } // Always clear out all state related to this endpoint before terminating // this thread. endpoint_manager_->discardEndpoint(client_proxy_, endpoint_id_); } // Called whenever an EndpointChannel is available for endpointId. // Implementations are expected to read/write freely to the EndpointChannel // until an Exception::IO is thrown. Once an Exception::IO occurs, a check // will be performed to see if another EndpointChannel is available for the // given endpoint and, if so, useHealthyEndpointChannel(EndpointChannel) will // be called again. // //

Return false to exit the loop. virtual ExceptionOr useHealthyEndpointChannel( Ptr endpoint_channel) = 0; // throws Exception::IO, // Exception::INTERRUPTED protected: Ptr> endpoint_manager_; const string runnable_name_; Ptr> client_proxy_; const string endpoint_id_; }; template class ReaderRunnable : public EndpointChannelLoopRunnable { public: ReaderRunnable(Ptr> endpoint_manager, Ptr> client_proxy, const string& endpoint_id) : EndpointChannelLoopRunnable(endpoint_manager, "Read", client_proxy, endpoint_id) {} // @EndpointManagerReaderThread ExceptionOr useHealthyEndpointChannel( Ptr endpoint_channel) override { // Read as much as we can from the healthy EndpointChannel - when it is no // longer in good shape (i.e. our read from it throws an Exception), our // super class will loop back around and try our luck in case there's been // a replacement for this endpoint since we last checked with the // EndpointChannelManager. while (true) { ExceptionOr> read_bytes = endpoint_channel->read(); if (!read_bytes.ok()) { if (Exception::INVALID_PROTOCOL_BUFFER == read_bytes.exception()) { // TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager // failed to decode message from endpoint %s on channel %s, // discarding.", endpointId, endpointChannel.getType()); continue; } else if (Exception::IO == read_bytes.exception()) { 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()) { // TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager // received an invalid OfflineFrame from endpoint %s on channel %s, // discarding.", endpointId, endpointChannel.getType()); continue; } } ScopedPtr> scoped_offline_frame( offline_frame.result()); // Route the incoming offlineFrame to its registered processor. V1Frame::FrameType frame_type = OfflineFrames::getFrameType(scoped_offline_frame.get()); Ptr::IncomingOfflineFrameProcessor> incoming_offline_frame_processor = this->endpoint_manager_->getOfflineFrameProcessor(frame_type); if (incoming_offline_frame_processor.isNull()) { // TODO(tracyzhou): Add logging. continue; } incoming_offline_frame_processor->processIncomingOfflineFrame( scoped_offline_frame.release(), this->endpoint_id_, this->client_proxy_, endpoint_channel->getMedium()); } } }; template class KeepAliveManagerRunnable : public EndpointChannelLoopRunnable { public: KeepAliveManagerRunnable(Ptr> endpoint_manager, Ptr> client_proxy, const string& endpoint_id) : EndpointChannelLoopRunnable( endpoint_manager, "KeepAliveManager", client_proxy, endpoint_id) {} // @EndpointManagerKeepAliveThread ExceptionOr useHealthyEndpointChannel( Ptr endpoint_channel) override { // Check if it has been too long since we received a frame from our // endpoint. if ((endpoint_channel->getLastReadTimestamp() != -1) && ((endpoint_channel->getLastReadTimestamp() + EndpointManager::kKeepAliveReadTimeoutMillis) < this->endpoint_manager_->system_clock_->elapsedRealtime())) { // TODO(tracyzhou): Add logging. return ExceptionOr(false); } // Attempt to send the KeepAlive frame over the endpoint channel - if the // write fails, our super class will loop back around and try our luck again // in case there's been a replacement for this endpoint. Exception::Value write_exception = endpoint_channel->write(OfflineFrames::forKeepAlive()); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { return ExceptionOr(write_exception); } } // We sleep as the very last step because we want to minimize the caching of // the EndpointChannel. If we do hold on to the EndpointChannel, and it's // switched out from under us in BandwidthUpgradeManager, our write will // trigger an erroneous write to the encryption context that will cascade // into all our remote endpoint's future reads failing. Exception::Value sleep_exception = this->endpoint_manager_->thread_utils_->sleep( EndpointManager::kKeepAliveWriteIntervalMillis); if (Exception::NONE != sleep_exception) { if (Exception::INTERRUPTED == sleep_exception) { return ExceptionOr(sleep_exception); } } return ExceptionOr(true); } }; template class RegisterIncomingOfflineFrameProcessorRunnable : public Runnable { public: RegisterIncomingOfflineFrameProcessorRunnable( Ptr> endpoint_manager, V1Frame::FrameType frame_type, Ptr::IncomingOfflineFrameProcessor> processor) : endpoint_manager_(endpoint_manager), frame_type_(frame_type), processor_(processor) {} void run() override { typename EndpointManager< Platform>::IncomingOfflineFrameProcessorsMap::iterator it = endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) { // TODO(tracyzhou): Add logging. it->second = processor_; } else { endpoint_manager_->incoming_offline_frame_processors_.insert( std::make_pair(frame_type_, processor_)); } } private: Ptr> endpoint_manager_; const V1Frame::FrameType frame_type_; Ptr::IncomingOfflineFrameProcessor> processor_; }; template class UnregisterIncomingOfflineFrameProcessorRunnable : public Runnable { public: UnregisterIncomingOfflineFrameProcessorRunnable( Ptr> endpoint_manager, V1Frame::FrameType frame_type, Ptr::IncomingOfflineFrameProcessor> processor) : endpoint_manager_(endpoint_manager), frame_type_(frame_type), processor_(processor) {} void run() override { typename EndpointManager< Platform>::IncomingOfflineFrameProcessorsMap::iterator it = endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) { if (it->second != processor_) { // TODO(tracyzhou): Add logging. return; } endpoint_manager_->incoming_offline_frame_processors_.erase(it); } } private: Ptr> endpoint_manager_; const V1Frame::FrameType frame_type_; Ptr::IncomingOfflineFrameProcessor> processor_; }; template class RegisterEndpointRunnable : public Runnable { public: RegisterEndpointRunnable( Ptr> endpoint_manager, Ptr> client_proxy, const string& endpoint_id, const string& endpoint_name, const string& authentication_token, ConstPtr raw_authentication_token, bool is_incoming, Ptr endpoint_channel, Ptr connection_lifecycle_listener, Ptr latch) : endpoint_manager_(endpoint_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), endpoint_name_(endpoint_name), authentication_token_(authentication_token), raw_authentication_token_(raw_authentication_token), is_incoming_(is_incoming), endpoint_channel_(endpoint_channel), connection_lifecycle_listener_(connection_lifecycle_listener), latch_(latch) {} void run() override { endpoint_manager_->endpoint_channel_manager_->registerChannelForEndpoint( client_proxy_, endpoint_id_, endpoint_channel_); // For every endpoint, there's one Reader instance running on the // EndpointManagerReaderThread. This instance reads from the endpoint and // delegates incoming frames to various IncomingOfflineFrameProcessors. // Once the frame has been properly handled, it starts reading again for the // next frame. If the Reader fails its read and no other EndpointChannels // are available for this endpoint, a disconnection will be initiated. endpoint_manager_->startEndpointReader(MakePtr(new ReaderRunnable( endpoint_manager_, client_proxy_, endpoint_id_))); // For every endpoint, there's one KeepAliveManager instance running on the // EndpointManagerKeepAliveThread. This instance will periodically // send out a ping* to the endpoint while listening for an incoming pong**. // If it fails to send the ping, or if no pong is heard within // kKeepAliveReadTimeoutMillis milliseconds, it initiates a // disconnection. // // (*) Bluetooth requires a constant outgoing stream of messages. If there's // silence, Android will break the socket. This is why we ping. // (**) Wifi Hotspots can fail to notice a connection has been lost, and // they will happily keep writing to /dev/null. This is why we listen for // the pong. endpoint_manager_->startEndpointKeepAliveManager( MakePtr(new KeepAliveManagerRunnable( endpoint_manager_, client_proxy_, endpoint_id_))); // TODO(tracyzhou): Add logging. // It's now time to let the client know of this new connection so that they // can accept or reject it. client_proxy_->onConnectionInitiated( endpoint_id_, endpoint_name_, authentication_token_, raw_authentication_token_.release(), is_incoming_, connection_lifecycle_listener_.release()); latch_->countDown(); } private: Ptr> endpoint_manager_; Ptr> client_proxy_; const string endpoint_id_; const string endpoint_name_; const string authentication_token_; ScopedPtr> raw_authentication_token_; const bool is_incoming_; Ptr endpoint_channel_; ScopedPtr> connection_lifecycle_listener_; Ptr latch_; }; template class UnregisterEndpointRunnable : public Runnable { public: UnregisterEndpointRunnable(Ptr> endpoint_manager, Ptr> client_proxy, const string& endpoint_id, Ptr latch) : endpoint_manager_(endpoint_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), latch_(latch) {} void run() override { endpoint_manager_->removeEndpoint( client_proxy_, endpoint_id_, /*send_disconnection_notification=*/false); latch_->countDown(); } private: Ptr> endpoint_manager_; Ptr> client_proxy_; const string endpoint_id_; Ptr latch_; }; template class DiscardEndpointRunnable : public Runnable { public: DiscardEndpointRunnable(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 { endpoint_manager_->removeEndpoint( client_proxy_, endpoint_id_, /*send_disconnection_notification=*/ client_proxy_->isConnectedToEndpoint(endpoint_id_)); } private: Ptr> endpoint_manager_; Ptr> client_proxy_; const string endpoint_id_; }; template class GetOfflineFrameProcessorCallable : public Callable::IncomingOfflineFrameProcessor>> { public: typedef Ptr::IncomingOfflineFrameProcessor> ReturnType; GetOfflineFrameProcessorCallable( Ptr> endpoint_manager, V1Frame::FrameType frame_type) : endpoint_manager_(endpoint_manager), frame_type_(frame_type) {} ExceptionOr call() override { typename EndpointManager< Platform>::IncomingOfflineFrameProcessorsMap::iterator it = endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); if (it == endpoint_manager_->incoming_offline_frame_processors_.end()) { return ExceptionOr(ReturnType()); } return ExceptionOr(it->second); } private: Ptr> endpoint_manager_; const V1Frame::FrameType frame_type_; }; } // namespace endpoint_manager template bool EndpointManager::IncomingOfflineFrameProcessor::operator==( const EndpointManager::IncomingOfflineFrameProcessor& rhs) { // We're comparing addresses because these objects are callbacks which need to // be matched by exact instances. return this == &rhs; } template bool EndpointManager::IncomingOfflineFrameProcessor::operator<( const EndpointManager::IncomingOfflineFrameProcessor& rhs) { // We're comparing addresses because these objects are callbacks which need to // be matched by exact instances. return this < &rhs; } template const std::int32_t EndpointManager::kKeepAliveWriteIntervalMillis = 5000; template const std::int32_t EndpointManager::kKeepAliveReadTimeoutMillis = 30000; template const std::int32_t EndpointManager::kProcessEndpointDisconnectionTimeoutMillis = 2000; template const std::int32_t EndpointManager::kMaxConcurrentEndpoints = 50; template EndpointManager::EndpointManager( Ptr endpoint_channel_manager) : thread_utils_(Platform::createThreadUtils()), system_clock_(Platform::createSystemClock()), endpoint_channel_manager_(endpoint_channel_manager), incoming_offline_frame_processors_(), endpoint_keep_alive_manager_thread_pool_( Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)), endpoint_readers_thread_pool_( Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)), serial_executor_(Platform::createSingleThreadExecutor()) {} template EndpointManager::~EndpointManager() { // TODO(tracyzhou): Add logging. // Stop all the ongoing Runnables (as gracefully as possible). serial_executor_->shutdown(); endpoint_readers_thread_pool_->shutdown(); endpoint_keep_alive_manager_thread_pool_->shutdown(); // 'incoming_offline_frame_processors' does not own the processors. incoming_offline_frame_processors_.clear(); // TODO(tracyzhou): Add logging. } template void EndpointManager::registerIncomingOfflineFrameProcessor( V1Frame::FrameType frame_type, Ptr::IncomingOfflineFrameProcessor> processor) { runOnEndpointManagerThread(MakePtr( new endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable< Platform>(self_, frame_type, processor))); } template void EndpointManager::unregisterIncomingOfflineFrameProcessor( V1Frame::FrameType frame_type, Ptr::IncomingOfflineFrameProcessor> processor) { runOnEndpointManagerThread(MakePtr( new endpoint_manager::UnregisterIncomingOfflineFrameProcessorRunnable< Platform>(self_, frame_type, processor))); } template Ptr::IncomingOfflineFrameProcessor> EndpointManager::getOfflineFrameProcessor( V1Frame::FrameType frame_type) { typedef Ptr::IncomingOfflineFrameProcessor> PtrIncomingOfflineFrameProcessor; typedef Ptr> ResultType; ScopedPtr future_result( runOnEndpointManagerThread(MakePtr( new endpoint_manager::GetOfflineFrameProcessorCallable( self_, frame_type)))); return waitForResult("getOfflineFrameProcessor", future_result.get()); } template void EndpointManager::registerEndpoint( Ptr> client_proxy, const string& endpoint_id, const string& endpoint_name, const string& authentication_token, ConstPtr raw_authentication_token, bool is_incoming, Ptr endpoint_channel, Ptr connection_lifecycle_listener) { ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnEndpointManagerThread( MakePtr(new endpoint_manager::RegisterEndpointRunnable( self_, client_proxy, endpoint_id, endpoint_name, authentication_token, raw_authentication_token, is_incoming, endpoint_channel, connection_lifecycle_listener, latch.get()))); waitForLatch("registerEndpoint", latch.get()); } template void EndpointManager::unregisterEndpoint( Ptr> client_proxy, const string& endpoint_id) { ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnEndpointManagerThread( MakePtr(new endpoint_manager::UnregisterEndpointRunnable( self_, client_proxy, endpoint_id, latch.get()))); waitForLatch("unregisterEndpoint", latch.get()); } template void EndpointManager::discardEndpoint( Ptr> client_proxy, const string& endpoint_id) { runOnEndpointManagerThread( MakePtr(new endpoint_manager::DiscardEndpointRunnable( self_, client_proxy, endpoint_id))); } template std::vector EndpointManager::sendPayloadChunk( const PayloadTransferFrame::PayloadHeader& payload_header, const PayloadTransferFrame::PayloadChunk& payload_chunk, const std::vector& endpoint_ids) { ConstPtr payload_transfer_frame_bytes = OfflineFrames::forDataPayloadTransferFrame(payload_header, payload_chunk); return sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes, payload_header.id(), /*offset=*/payload_chunk.offset(), /*packet_type=*/"DATA"); } template void EndpointManager::sendControlMessage( const PayloadTransferFrame::PayloadHeader& payload_header, const PayloadTransferFrame::ControlMessage& control_message, const std::vector& endpoint_ids) { ConstPtr payload_transfer_frame_bytes = OfflineFrames::forControlPayloadTransferFrame(payload_header, control_message); sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes, payload_header.id(), /*offset=*/control_message.offset(), /*packet_type=*/"CONTROL"); } template void EndpointManager::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 void EndpointManager::waitForLatch(const string& method_name, Ptr latch, std::int32_t timeout_millis) { ExceptionOr await_succeeded = latch->await(timeout_millis); if (!await_succeeded.ok()) { // TODO(tracyzhou): Add logging. if (Exception::INTERRUPTED == await_succeeded.exception()) { // TODO(tracyzhou): Add logging. // Thread.currentThread().interrupt(); return; } } if (!await_succeeded.result()) { // TODO(tracyzhou): Add logging. } } template template T EndpointManager::waitForResult(const string& method_name, 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 T(); } } return result.result(); } // @EndpointManagerThread template void EndpointManager::removeEndpoint( Ptr> client_proxy, const string& endpoint_id, bool send_disconnection_notification) { // Unregistering from endpoint_channel_manager_ will also serve to terminate // the dedicated reader and KeepAlive threads we started when we registered // this endpoint. if (endpoint_channel_manager_->unregisterChannelForEndpoint(endpoint_id)) { // Notify all frame processors of the disconnection immediately and wait // for them to clean up state. Only once all processors are done cleaning // up, we can remove the endpoint from ClientProxy after which there // should be no further interactions with the endpoint. // (See b/37352254 for history) waitForEndpointDisconnectionProcessing(client_proxy, endpoint_id); client_proxy->onDisconnected(endpoint_id, send_disconnection_notification); // TODO(tracyzhou): Add logging. } } // @EndpointManagerThread template void EndpointManager::waitForEndpointDisconnectionProcessing( Ptr> client_proxy, const string& endpoint_id) { ScopedPtr> process_disconnection_barrier( Platform::createCountDownLatch(static_cast( incoming_offline_frame_processors_.size()))); for (typename IncomingOfflineFrameProcessorsMap::iterator it = incoming_offline_frame_processors_.begin(); it != incoming_offline_frame_processors_.end(); it++) { it->second->processEndpointDisconnection( client_proxy, endpoint_id, process_disconnection_barrier.get()); } waitForLatch("waitForEndpointDisconnectionProcessing", process_disconnection_barrier.get(), kProcessEndpointDisconnectionTimeoutMillis); } template std::vector EndpointManager::sendTransferFrameBytes( const std::vector& endpoint_ids, ConstPtr payload_transfer_frame_bytes, std::int64_t payload_id, std::int64_t offset, const string& packet_type) { ScopedPtr> scoped_payload_transfer_frame_bytes( payload_transfer_frame_bytes); std::vector failed_endpoint_ids; for (std::vector::const_iterator it = endpoint_ids.begin(); it != endpoint_ids.end(); it++) { const string& endpoint_id = *it; ScopedPtr> scoped_endpoint_channel( endpoint_channel_manager_->getChannelForEndpoint(endpoint_id)); if (scoped_endpoint_channel.isNull()) { // We no longer know about this endpoint (it was either explicitly // unregistered, or a read/write error made us unregister it internally). // TODO(tracyzhou): Add logging. failed_endpoint_ids.push_back(endpoint_id); continue; } Exception::Value write_exception = scoped_endpoint_channel->write( scoped_payload_transfer_frame_bytes.release()); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { // TODO(tracyzhou): Add logging. failed_endpoint_ids.push_back(endpoint_id); continue; } } } return failed_endpoint_ids; } template void EndpointManager::startEndpointReader(Ptr runnable) { endpoint_readers_thread_pool_->execute(runnable); } template void EndpointManager::startEndpointKeepAliveManager( Ptr runnable) { endpoint_keep_alive_manager_thread_pool_->execute(runnable); } template void EndpointManager::runOnEndpointManagerThread( Ptr runnable) { serial_executor_->execute(runnable); } template template Ptr> EndpointManager::runOnEndpointManagerThread( Ptr> callable) { return serial_executor_->submit(callable); } } // namespace connections } // namespace nearby } // namespace location