#include "core/internal/base_endpoint_channel.h" #include #include "platform/api/platform.h" #include "platform/synchronized.h" #include "proto/connections_enums.pb.h" namespace location { namespace nearby { namespace connections { namespace { using Platform = platform::ImplementationPlatform; std::int32_t bytesToInt(ConstPtr bytes) { const char* int_bytes = bytes->getData(); std::int32_t result = 0; result |= (static_cast(int_bytes[0]) & 0x0FF) << 24; result |= (static_cast(int_bytes[1]) & 0x0FF) << 16; result |= (static_cast(int_bytes[2]) & 0x0FF) << 8; result |= (static_cast(int_bytes[3]) & 0x0FF); return result; } ConstPtr intToBytes(std::int32_t value) { char int_bytes[sizeof(std::int32_t)]; int_bytes[0] = static_cast((value >> 24) & 0x0FF); int_bytes[1] = static_cast((value >> 16) & 0x0FF); int_bytes[2] = static_cast((value >> 8) & 0x0FF); int_bytes[3] = static_cast((value)&0x0FF); return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes))); } ExceptionOr> readExactly(Ptr reader, std::int64_t size) { string buffer; std::int64_t remaining_size = size; while (remaining_size > 0) { ExceptionOr> read_bytes = reader->read(remaining_size); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { return ExceptionOr>(read_bytes.exception()); } } // Avoid leaks. ScopedPtr> scoped_read_bytes(read_bytes.result()); // In Java, EOFException is a sub-variant of IOException. if (scoped_read_bytes.isNull() || scoped_read_bytes->size() == 0) { return ExceptionOr>(Exception::IO); } buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size()); remaining_size -= scoped_read_bytes->size(); } return ExceptionOr>( MakeConstPtr(new ByteArray(buffer.data(), buffer.size()))); } ExceptionOr readInt(Ptr reader) { ExceptionOr> read_bytes = readExactly(reader, sizeof(std::int32_t)); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { return ExceptionOr(read_bytes.exception()); } } // Avoid leaks. ScopedPtr> scoped_read_bytes(read_bytes.result()); return ExceptionOr(bytesToInt(scoped_read_bytes.get())); } Exception::Value writeInt(Ptr writer, std::int32_t value) { return writer->write(intToBytes(value)); } } // namespace // TODO(b/150763574): Move implementatiopn to header or .inc file. BaseEndpointChannel::BaseEndpointChannel(absl::string_view channel_name, Ptr reader, Ptr writer) : last_read_timestamp_(-1), channel_name_(channel_name), system_clock_(Platform::createSystemClock()), reader_lock_(Platform::createLock()), reader_(reader), writer_lock_(Platform::createLock()), writer_(writer), encryption_context_(Platform::createAtomicReference( Ptr())), is_paused_lock_(Platform::createLock()), is_paused_condition_variable_( Platform::createConditionVariable(is_paused_lock_.get())), is_paused_(Platform::createAtomicBoolean(false)) {} BaseEndpointChannel::~BaseEndpointChannel() { // WARNING: Make sure to never access reader_ and writer_ from here. // // They're owned by the specialized *Socket classes that are in turn // owned by the *EndpointChannel children of this class, so by this point, // they've been destroyed and now point to invalid memory. // // "Ugh!" is right -- this won't be a problem once we have a standardized // Socket interface we can hold up in this class (instead of holding // specialized implementations of that hypothetical interface in each child // of this class). } ExceptionOr> BaseEndpointChannel::read() { Synchronized s(reader_lock_.get()); ExceptionOr read_int = readInt(reader_); if (!read_int.ok()) { if (Exception::IO == read_int.exception()) { return ExceptionOr>(read_int.exception()); } } if (read_int.result() < 0) { return ExceptionOr>(Exception::IO); } else if (read_int.result() > kMaxAllowedReadBytes) { return ExceptionOr>(Exception::IO); } ExceptionOr> read_bytes = readExactly(reader_, read_int.result()); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { return ExceptionOr>(read_bytes.exception()); } } // This should be ScopedPtr usually, but because of the unique requirement of // reassigning this variable when encryption is enabled, we can't make use of // the power of ScopedPtr, and instead have to do manual memory management. ConstPtr read_bytes_result = read_bytes.result(); // If encryption is enabled, decode the message. if (isEncryptionEnabled()) { std::unique_ptr decoded_bytes = encryption_context_->get()->DecodeMessageFromPeer( string(read_bytes_result->getData(), read_bytes_result->size())); // Now that we are done using read_bytes_result, we should unconditionally // destroy it, because we either reassign to the value of decoded_bytes, or // short-circuit out of here on error. read_bytes_result.destroy(); if (decoded_bytes == nullptr) { return ExceptionOr>( Exception::INVALID_PROTOCOL_BUFFER); } read_bytes_result = MakeConstPtr( new ByteArray(decoded_bytes->data(), decoded_bytes->size())); } last_read_timestamp_ = system_clock_->elapsedRealtime(); return ExceptionOr>(read_bytes_result); } Exception::Value BaseEndpointChannel::write(ConstPtr data) { Synchronized s(writer_lock_.get()); // Avoid leaks. ScopedPtr> scoped_data(data); if (isPaused()) { blockUntilUnpaused(); } ConstPtr data_to_write; // If encryption is enabled, encode the message. if (isEncryptionEnabled()) { std::unique_ptr message = encryption_context_->get()->EncodeMessageToPeer( string(scoped_data->getData(), scoped_data->size())); assert(message != nullptr); data_to_write = MakeConstPtr(new ByteArray(message->data(), message->size())); } else { // Else, just make data_to_write point to the passed-in data. data_to_write = scoped_data.release(); } // Avoid leaks. ScopedPtr> scoped_data_to_write(data_to_write); Exception::Value write_exception = writeInt( writer_, static_cast(scoped_data_to_write->size())); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { return write_exception; } } write_exception = writer_->write(scoped_data_to_write.release()); if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { return write_exception; } } Exception::Value flush_exception = writer_->flush(); if (Exception::NONE != flush_exception) { if (Exception::IO == flush_exception) { return flush_exception; } } return Exception::NONE; } void BaseEndpointChannel::close() { // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. // // In the corresponding Java code, close() calls // close(proto::connections::DisconnectionReason) while here we do the // opposite. This is because proto::connections::DisconnectionReason can be // null in Java but not in C++. Exception::Value reader_close_exception = reader_->close(); if (Exception::NONE != reader_close_exception) { if (Exception::IO == reader_close_exception) { // Add logging. } } Exception::Value writer_close_exception = writer_->close(); if (Exception::NONE != writer_close_exception) { if (Exception::IO == writer_close_exception) { // Add logging. } } closeImpl(); // TODO(tracyzhou): Add logging. } void BaseEndpointChannel::close( proto::connections::DisconnectionReason reason) { // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. // Look at the corresponding block in the close() method above for details on // the deviation. close(); // TODO(tracyzhou): Add logging. } string BaseEndpointChannel::getType() { string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : ""; switch (getMedium()) { case proto::connections::Medium::BLUETOOTH: return subtype + "BLUETOOTH"; case proto::connections::Medium::BLE: return subtype + "BLE"; case proto::connections::Medium::MDNS: return subtype + "MDNS"; case proto::connections::Medium::WIFI_HOTSPOT: return subtype + "WIFI_HOTSPOT"; case proto::connections::Medium::WIFI_LAN: return subtype + "WIFI_LAN"; default: return "UNKNOWN"; } } string BaseEndpointChannel::getName() { return channel_name_; } void BaseEndpointChannel::enableEncryption( Ptr encryption_context) { assert(!encryption_context.isNull()); encryption_context_->set(encryption_context); } bool BaseEndpointChannel::isPaused() { return is_paused_->get(); } void BaseEndpointChannel::pause() { is_paused_->set(true); } void BaseEndpointChannel::resume() { is_paused_->set(false); unblockPausedWriter(); } std::int64_t BaseEndpointChannel::getLastReadTimestamp() { return last_read_timestamp_; } bool BaseEndpointChannel::isEncryptionEnabled() { return !encryption_context_->get().isNull(); } void BaseEndpointChannel::unblockPausedWriter() { Synchronized s(is_paused_lock_.get()); // Notify to tell the thread calling wait() to check again. // NOTE: There is only ever one thread blocked by wait() at a time, because // EndpointChannel.write(Ptr) is synchronized on writer. That means // the first thread to call write(byte[]) will be blocked via // blockUntilUnpaused() and all future threads will be blocked via // synchronized(writer_lock_). is_paused_condition_variable_->notify(); } void BaseEndpointChannel::blockUntilUnpaused() { Synchronized s(is_paused_lock_.get()); // For more on how this works, see // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html while (is_paused_->get()) { Exception::Value wait_succeeded = is_paused_condition_variable_->wait(); if (Exception::NONE != wait_succeeded) { if (Exception::INTERRUPTED == wait_succeeded) { // If we were interrupted, pass the interrupt up the stack and then exit // immediately. // Thread.currentThread().interrupt(); return; } } } } } // namespace connections } // namespace nearby } // namespace location