/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "tcp_client.h" #include "constants.h" #include #include #include #include #include #include static constexpr int kDefaultPort = 5554; static constexpr int kProtocolVersion = 1; static constexpr int kHandshakeTimeoutMs = 2000; static constexpr size_t kHandshakeLength = 4; // Extract the big-endian 8-byte message length into a 64-bit number. static uint64_t ExtractMessageLength(const void* buffer) { uint64_t ret = 0; for (int i = 0; i < 8; ++i) { ret |= uint64_t{reinterpret_cast(buffer)[i]} << (56 - i * 8); } return ret; } // Encode the 64-bit number into a big-endian 8-byte message length. static void EncodeMessageLength(uint64_t length, void* buffer) { for (int i = 0; i < 8; ++i) { reinterpret_cast(buffer)[i] = length >> (56 - i * 8); } } ClientTcpTransport::ClientTcpTransport() { service_ = Socket::NewServer(Socket::Protocol::kTcp, kDefaultPort); // A workaround to notify recovery to continue its work. android::base::SetProperty("sys.usb.ffs.ready", "1"); } ssize_t ClientTcpTransport::Read(void* data, size_t len) { if (len > SSIZE_MAX) { return -1; } size_t total_read = 0; do { // Read a new message while (message_bytes_left_ == 0) { if (socket_ == nullptr) { ListenFastbootSocket(); } char buffer[8]; if (socket_->ReceiveAll(buffer, 8, 0) == 8) { message_bytes_left_ = ExtractMessageLength(buffer); } else { // If connection is closed by host, Receive will return 0 immediately. socket_.reset(nullptr); // In DATA phase, return error. if (downloading_) { return -1; } } } size_t read_length = len - total_read; if (read_length > message_bytes_left_) { read_length = message_bytes_left_; } ssize_t bytes_read = socket_->ReceiveAll(reinterpret_cast(data) + total_read, read_length, 0); if (bytes_read == -1) { socket_.reset(nullptr); return -1; } else { message_bytes_left_ -= bytes_read; total_read += bytes_read; } // There are more than one DATA phases if the downloading buffer is too // large, like a very big system image. All of data phases should be // received until the whole buffer is filled in that case. } while (downloading_ && total_read < len); return total_read; } ssize_t ClientTcpTransport::Write(const void* data, size_t len) { if (socket_ == nullptr || len > SSIZE_MAX) { return -1; } // Use multi-buffer writes for better performance. char header[8]; EncodeMessageLength(len, header); if (!socket_->Send(std::vector{{header, 8}, {data, len}})) { socket_.reset(nullptr); return -1; } // In DATA phase if (android::base::StartsWith(reinterpret_cast(data), RESPONSE_DATA)) { downloading_ = true; } else { downloading_ = false; } return len; } int ClientTcpTransport::Close() { if (socket_ == nullptr) { return -1; } socket_.reset(nullptr); return 0; } int ClientTcpTransport::Reset() { return Close(); } void ClientTcpTransport::ListenFastbootSocket() { while (true) { socket_ = service_->Accept(); // Handshake char buffer[kHandshakeLength + 1]; buffer[kHandshakeLength] = '\0'; if (socket_->ReceiveAll(buffer, kHandshakeLength, kHandshakeTimeoutMs) != kHandshakeLength) { PLOG(ERROR) << "No Handshake message received"; socket_.reset(nullptr); continue; } if (memcmp(buffer, "FB", 2) != 0) { PLOG(ERROR) << "Unrecognized initialization message"; socket_.reset(nullptr); continue; } int version = 0; if (!android::base::ParseInt(buffer + 2, &version) || version < kProtocolVersion) { LOG(ERROR) << "Unknown TCP protocol version " << buffer + 2 << ", our version: " << kProtocolVersion; socket_.reset(nullptr); continue; } std::string handshake_message(android::base::StringPrintf("FB%02d", kProtocolVersion)); if (!socket_->Send(handshake_message.c_str(), kHandshakeLength)) { PLOG(ERROR) << "Failed to send initialization message"; socket_.reset(nullptr); continue; } break; } }