diff options
author | Yu Shan <shanyu@google.com> | 2022-01-19 14:45:48 -0800 |
---|---|---|
committer | Yu Shan <shanyu@google.com> | 2022-01-21 17:37:40 -0800 |
commit | 7292d87621e2bf64a0ccf8e322e0c01d6a9763a9 (patch) | |
tree | 3f2438925112f3338cfda6e51b6a15977e474572 /emulator/Conn | |
parent | a145334e60033ae04661b65f5d1946c7bcb83b20 (diff) | |
download | car-7292d87621e2bf64a0ccf8e322e0c01d6a9763a9.tar.gz |
Move Connection libs outside vhal_v2_0.
The connection libs are going to be used by AIDL VHAL as well so
move them to a common place.
Bug: 215419573
Test: Presubmit
Change-Id: Ibff7030089fa4fd891bfeaee5604d9dba3ae07df
Diffstat (limited to 'emulator/Conn')
-rw-r--r-- | emulator/Conn/CommConn/Android.bp | 33 | ||||
-rw-r--r-- | emulator/Conn/CommConn/CommConn.cpp | 79 | ||||
-rw-r--r-- | emulator/Conn/CommConn/include/CommConn.h | 116 | ||||
-rw-r--r-- | emulator/Conn/PipeComm/Android.bp | 35 | ||||
-rw-r--r-- | emulator/Conn/PipeComm/PipeComm.cpp | 105 | ||||
-rw-r--r-- | emulator/Conn/PipeComm/include/PipeComm.h | 64 | ||||
-rw-r--r-- | emulator/Conn/PipeComm/include/qemu_pipe.h | 65 | ||||
-rw-r--r-- | emulator/Conn/PipeComm/qemu_pipe.cpp | 104 | ||||
-rw-r--r-- | emulator/Conn/SocketComm/Android.bp | 34 | ||||
-rw-r--r-- | emulator/Conn/SocketComm/SocketComm.cpp | 225 | ||||
-rw-r--r-- | emulator/Conn/SocketComm/include/SocketComm.h | 124 |
11 files changed, 984 insertions, 0 deletions
diff --git a/emulator/Conn/CommConn/Android.bp b/emulator/Conn/CommConn/Android.bp new file mode 100644 index 0000000..9de4e08 --- /dev/null +++ b/emulator/Conn/CommConn/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2022 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. + +package { + default_applicable_licenses: ["device_generic_car_license"], +} + +cc_library { + name: "EmulatorCommConn", + vendor: true, + srcs: [ + "*.cpp", + ], + shared_libs: [ + "liblog", + "libprotobuf-cpp-lite", + ], + export_include_dirs: ["include"], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-libproto-native", + ], +} diff --git a/emulator/Conn/CommConn/CommConn.cpp b/emulator/Conn/CommConn/CommConn.cpp new file mode 100644 index 0000000..800a539 --- /dev/null +++ b/emulator/Conn/CommConn/CommConn.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "CommConn" + +#include <thread> + +#include <log/log.h> + +#include "CommConn.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +void CommConn::start() { + mReadThread = std::make_unique<std::thread>(std::bind(&CommConn::readThread, this)); +} + +void CommConn::stop() { + if (mReadThread->joinable()) { + mReadThread->join(); + } +} + +void CommConn::sendMessage(vhal_proto::EmulatorMessage const& msg) { + int numBytes = msg.ByteSize(); + std::vector<uint8_t> buffer(static_cast<size_t>(numBytes)); + if (!msg.SerializeToArray(buffer.data(), numBytes)) { + ALOGE("%s: SerializeToString failed!", __func__); + return; + } + + write(buffer); +} + +void CommConn::readThread() { + std::vector<uint8_t> buffer; + while (isOpen()) { + buffer = read(); + if (buffer.size() == 0) { + ALOGI("%s: Read returned empty message, exiting read loop.", __func__); + break; + } + + vhal_proto::EmulatorMessage rxMsg; + if (rxMsg.ParseFromArray(buffer.data(), static_cast<int32_t>(buffer.size()))) { + vhal_proto::EmulatorMessage respMsg; + mMessageProcessor->processMessage(rxMsg, respMsg); + + sendMessage(respMsg); + } + } +} + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/emulator/Conn/CommConn/include/CommConn.h b/emulator/Conn/CommConn/include/CommConn.h new file mode 100644 index 0000000..cbc8281 --- /dev/null +++ b/emulator/Conn/CommConn/include/CommConn.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ANDROID_EMULATORCOMMCONN_COMMCONN_H +#define ANDROID_EMULATORCOMMCONN_COMMCONN_H + +#include <string> +#include <thread> +#include <vector> + +#include "VehicleHalProto.pb.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +/** + * MessageProcess is an interface implemented by VehicleEmulator to process messages received + * over a CommConn. + */ +class MessageProcessor { + public: + virtual ~MessageProcessor() = default; + + /** + * Process a single message received over a CommConn. Populate the given respMsg with the reply + * message we should send. + */ + virtual void processMessage(vhal_proto::EmulatorMessage const& rxMsg, + vhal_proto::EmulatorMessage& respMsg) = 0; +}; + +/** + * This is the interface that both PipeComm and SocketComm use to represent a connection. The + * connection will listen for commands on a separate 'read' thread. + */ +class CommConn { + public: + CommConn(MessageProcessor* messageProcessor) : mMessageProcessor(messageProcessor) {} + + virtual ~CommConn() {} + + /** + * Start the read thread reading messages from this connection. + */ + virtual void start(); + + /** + * Closes a connection if it is open. + */ + virtual void stop(); + + /** + * Returns true if the connection is open and available to send/receive. + */ + virtual bool isOpen() = 0; + + /** + * Blocking call to read data from the connection. + * + * @return std::vector<uint8_t> Serialized protobuf data received from emulator. This will be + * an empty vector if the connection was closed or some other error occurred. + */ + virtual std::vector<uint8_t> read() = 0; + + /** + * Transmits a string of data to the emulator. + * + * @param data Serialized protobuf data to transmit. + * + * @return int Number of bytes transmitted, or -1 if failed. + */ + virtual int write(const std::vector<uint8_t>& data) = 0; + + /** + * Serialized and send the given message to the other side. + */ + void sendMessage(vhal_proto::EmulatorMessage const& msg); + + protected: + std::unique_ptr<std::thread> mReadThread; + MessageProcessor* mMessageProcessor; + + /** + * A thread that reads messages in a loop, and responds. You can stop this thread by calling + * stop(). + */ + void readThread(); +}; + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_EMULATORCOMMCONN_COMMCONN_H diff --git a/emulator/Conn/PipeComm/Android.bp b/emulator/Conn/PipeComm/Android.bp new file mode 100644 index 0000000..6f8e3b4 --- /dev/null +++ b/emulator/Conn/PipeComm/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 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. + +package { + default_applicable_licenses: ["device_generic_car_license"], +} + +cc_library { + name: "EmulatorPipeComm", + vendor: true, + srcs: [ + "*.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libprotobuf-cpp-lite", + ], + export_include_dirs: ["include"], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-libproto-native", + "EmulatorCommConn", + ], +} diff --git a/emulator/Conn/PipeComm/PipeComm.cpp b/emulator/Conn/PipeComm/PipeComm.cpp new file mode 100644 index 0000000..c54bf9f --- /dev/null +++ b/emulator/Conn/PipeComm/PipeComm.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "PipeComm" + +#include <log/log.h> + +#include "qemu_pipe.h" + +#include "PipeComm.h" + +#define CAR_SERVICE_NAME "pipe:qemud:car" + + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +PipeComm::PipeComm(MessageProcessor* messageProcessor) : CommConn(messageProcessor), mPipeFd(-1) {} + +void PipeComm::start() { + int fd = qemu_pipe_open(CAR_SERVICE_NAME); + + if (fd < 0) { + ALOGE("%s: Could not open connection to service: %s %d", __FUNCTION__, strerror(errno), fd); + return; + } + + ALOGI("%s: Starting pipe connection, fd=%d", __FUNCTION__, fd); + mPipeFd = fd; + + CommConn::start(); +} + +void PipeComm::stop() { + if (mPipeFd > 0) { + ::close(mPipeFd); + mPipeFd = -1; + } + CommConn::stop(); +} + +std::vector<uint8_t> PipeComm::read() { + static constexpr int MAX_RX_MSG_SZ = 2048; + std::vector<uint8_t> msg = std::vector<uint8_t>(MAX_RX_MSG_SZ); + int numBytes; + + numBytes = qemu_pipe_frame_recv(mPipeFd, msg.data(), msg.size()); + + if (numBytes == MAX_RX_MSG_SZ) { + ALOGE("%s: Received max size = %d", __FUNCTION__, MAX_RX_MSG_SZ); + } else if (numBytes > 0) { + msg.resize(numBytes); + return msg; + } else { + ALOGD("%s: Connection terminated on pipe %d, numBytes=%d", __FUNCTION__, mPipeFd, numBytes); + mPipeFd = -1; + } + + return std::vector<uint8_t>(); +} + +int PipeComm::write(const std::vector<uint8_t>& data) { + int retVal = 0; + + if (mPipeFd != -1) { + retVal = qemu_pipe_frame_send(mPipeFd, data.data(), data.size()); + } + + if (retVal < 0) { + retVal = -errno; + ALOGE("%s: send_cmd: (fd=%d): ERROR: %s", __FUNCTION__, mPipeFd, strerror(errno)); + } + + return retVal; +} + + +} // impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + + + diff --git a/emulator/Conn/PipeComm/include/PipeComm.h b/emulator/Conn/PipeComm/include/PipeComm.h new file mode 100644 index 0000000..24dd686 --- /dev/null +++ b/emulator/Conn/PipeComm/include/PipeComm.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ANDROID_EMULATORPIPECOMM_PIPECOMM_H +#define ANDROID_EMULATORPIPECOMM_PIPECOMM_H + +#include <mutex> +#include <vector> + +#include "CommConn.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +/** + * PipeComm opens a qemu pipe to connect to the emulator, allowing the emulator UI to access the + * Vehicle HAL and simulate changing properties. + * + * Since the pipe is a client, it directly implements CommConn, and only one PipeComm can be open + * at a time. + */ +class PipeComm : public CommConn { + public: + PipeComm(MessageProcessor* messageProcessor); + + void start() override; + void stop() override; + + std::vector<uint8_t> read() override; + int write(const std::vector<uint8_t>& data) override; + + inline bool isOpen() override { return mPipeFd > 0; } + + private: + int mPipeFd; +}; + +} // impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_EMULATORPIPECOMM_PIPECOMM_H diff --git a/emulator/Conn/PipeComm/include/qemu_pipe.h b/emulator/Conn/PipeComm/include/qemu_pipe.h new file mode 100644 index 0000000..bd3e71d --- /dev/null +++ b/emulator/Conn/PipeComm/include/qemu_pipe.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ANDROID_EMULATORPIPECOMM_QEMU_PIPE_H +#define ANDROID_EMULATORPIPECOMM_QEMU_PIPE_H + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif +// Try to open a new Qemu fast-pipe. This function returns a file descriptor +// that can be used to communicate with a named service managed by the +// emulator. +// +// This file descriptor can be used as a standard pipe/socket descriptor. +// +// 'pipeName' is the name of the emulator service you want to connect to, +// and should begin with 'pipe:' (e.g. 'pipe:camera' or 'pipe:opengles'). +// For backward compatibility, the 'pipe:' prefix can be omitted, and in +// that case, qemu_pipe_open will add it for you. + +// On success, return a valid file descriptor, or -1/errno on failure. E.g.: +// +// EINVAL -> unknown/unsupported pipeName +// ENOSYS -> fast pipes not available in this system. +// +// ENOSYS should never happen, except if you're trying to run within a +// misconfigured emulator. +// +// You should be able to open several pipes to the same pipe service, +// except for a few special cases (e.g. GSM modem), where EBUSY will be +// returned if more than one client tries to connect to it. +int qemu_pipe_open(const char* pipeName); + +// Send a framed message |buff| of |len| bytes through the |fd| descriptor. +// This really adds a 4-hexchar prefix describing the payload size. +// Returns 0 on success, and -1 on error. +int qemu_pipe_frame_send(int fd, const void* buff, size_t len); + +// Read a frame message from |fd|, and store it into |buff| of |len| bytes. +// If the framed message is larger than |len|, then this returns -1 and the +// content is lost. Otherwise, this returns the size of the message. NOTE: +// empty messages are possible in a framed wire protocol and do not mean +// end-of-stream. +int qemu_pipe_frame_recv(int fd, void* buff, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_EMULATORPIPECOMM_QEMU_PIPE_H diff --git a/emulator/Conn/PipeComm/qemu_pipe.cpp b/emulator/Conn/PipeComm/qemu_pipe.cpp new file mode 100644 index 0000000..1f541b5 --- /dev/null +++ b/emulator/Conn/PipeComm/qemu_pipe.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2011 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 <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <android-base/file.h> + +#include "qemu_pipe.h" + +using android::base::ReadFully; +using android::base::WriteFully; + +// Define QEMU_PIPE_DEBUG if you want to print error messages when an error +// occurs during pipe operations. The macro should simply take a printf-style +// formatting string followed by optional arguments. +#ifndef QEMU_PIPE_DEBUG +#define QEMU_PIPE_DEBUG(...) (void)0 +#endif + +int qemu_pipe_open(const char* pipeName) { + if (!pipeName) { + errno = EINVAL; + return -1; + } + + int fd = TEMP_FAILURE_RETRY(open("/dev/qemu_pipe", O_RDWR)); + if (fd < 0) { + QEMU_PIPE_DEBUG("%s: Could not open /dev/qemu_pipe: %s", __FUNCTION__, strerror(errno)); + return -1; + } + + // Write the pipe name, *including* the trailing zero which is necessary. + size_t pipeNameLen = strlen(pipeName); + if (WriteFully(fd, pipeName, pipeNameLen + 1U)) { + return fd; + } + + // now, add 'pipe:' prefix and try again + // Note: host side will wait for the trailing '\0' to start + // service lookup. + const char pipe_prefix[] = "pipe:"; + if (WriteFully(fd, pipe_prefix, strlen(pipe_prefix)) && + WriteFully(fd, pipeName, pipeNameLen + 1U)) { + return fd; + } + QEMU_PIPE_DEBUG("%s: Could not write to %s pipe service: %s", __FUNCTION__, pipeName, + strerror(errno)); + close(fd); + return -1; +} + +int qemu_pipe_frame_send(int fd, const void* buff, size_t len) { + char header[5]; + snprintf(header, sizeof(header), "%04zx", len); + if (!WriteFully(fd, header, 4)) { + QEMU_PIPE_DEBUG("Can't write qemud frame header: %s", strerror(errno)); + return -1; + } + if (!WriteFully(fd, buff, len)) { + QEMU_PIPE_DEBUG("Can't write qemud frame payload: %s", strerror(errno)); + return -1; + } + return 0; +} + +int qemu_pipe_frame_recv(int fd, void* buff, size_t len) { + char header[5]; + if (!ReadFully(fd, header, 4)) { + QEMU_PIPE_DEBUG("Can't read qemud frame header: %s", strerror(errno)); + return -1; + } + header[4] = '\0'; + size_t size; + if (sscanf(header, "%04zx", &size) != 1) { + QEMU_PIPE_DEBUG("Malformed qemud frame header: [%.*s]", 4, header); + return -1; + } + if (size > len) { + QEMU_PIPE_DEBUG("Oversized qemud frame (% bytes, expected <= %)", size, len); + return -1; + } + if (!ReadFully(fd, buff, size)) { + QEMU_PIPE_DEBUG("Could not read qemud frame payload: %s", strerror(errno)); + return -1; + } + return size; +} diff --git a/emulator/Conn/SocketComm/Android.bp b/emulator/Conn/SocketComm/Android.bp new file mode 100644 index 0000000..0a186d1 --- /dev/null +++ b/emulator/Conn/SocketComm/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2022 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. + +package { + default_applicable_licenses: ["device_generic_car_license"], +} + +cc_library { + name: "EmulatorSocketComm", + vendor: true, + srcs: [ + "*.cpp", + ], + shared_libs: [ + "liblog", + "libprotobuf-cpp-lite", + ], + export_include_dirs: ["include"], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-libproto-native", + "EmulatorCommConn", + ], +} diff --git a/emulator/Conn/SocketComm/SocketComm.cpp b/emulator/Conn/SocketComm/SocketComm.cpp new file mode 100644 index 0000000..1e9a9bd --- /dev/null +++ b/emulator/Conn/SocketComm/SocketComm.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "SocketComm" + +#include <android/log.h> +#include <arpa/inet.h> +#include <log/log.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "SocketComm.h" + +// Socket to use when communicating with Host PC +static constexpr int DEBUG_SOCKET = 33452; + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +SocketComm::SocketComm(MessageProcessor* messageProcessor) + : mListenFd(-1), mMessageProcessor(messageProcessor) {} + +SocketComm::~SocketComm() { +} + +void SocketComm::start() { + if (!listen()) { + return; + } + + mListenThread = std::make_unique<std::thread>(std::bind(&SocketComm::listenThread, this)); +} + +void SocketComm::stop() { + if (mListenFd > 0) { + ::close(mListenFd); + if (mListenThread->joinable()) { + mListenThread->join(); + } + mListenFd = -1; + } +} + +void SocketComm::sendMessage(vhal_proto::EmulatorMessage const& msg) { + std::lock_guard<std::mutex> lock(mMutex); + for (std::unique_ptr<SocketConn> const& conn : mOpenConnections) { + conn->sendMessage(msg); + } +} + +bool SocketComm::listen() { + int retVal; + struct sockaddr_in servAddr; + + mListenFd = socket(AF_INET, SOCK_STREAM, 0); + if (mListenFd < 0) { + ALOGE("%s: socket() failed, mSockFd=%d, errno=%d", __FUNCTION__, mListenFd, errno); + mListenFd = -1; + return false; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = INADDR_ANY; + servAddr.sin_port = htons(DEBUG_SOCKET); + + retVal = bind(mListenFd, reinterpret_cast<struct sockaddr*>(&servAddr), sizeof(servAddr)); + if(retVal < 0) { + ALOGE("%s: Error on binding: retVal=%d, errno=%d", __FUNCTION__, retVal, errno); + close(mListenFd); + mListenFd = -1; + return false; + } + + ALOGI("%s: Listening for connections on port %d", __FUNCTION__, DEBUG_SOCKET); + if (::listen(mListenFd, 1) == -1) { + ALOGE("%s: Error on listening: errno: %d: %s", __FUNCTION__, errno, strerror(errno)); + return false; + } + return true; +} + +SocketConn* SocketComm::accept() { + sockaddr_in cliAddr; + socklen_t cliLen = sizeof(cliAddr); + int sfd = ::accept(mListenFd, reinterpret_cast<struct sockaddr*>(&cliAddr), &cliLen); + + if (sfd > 0) { + char addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &cliAddr.sin_addr, addr, INET_ADDRSTRLEN); + + ALOGD("%s: Incoming connection received from %s:%d", __FUNCTION__, addr, cliAddr.sin_port); + return new SocketConn(mMessageProcessor, sfd); + } + + return nullptr; +} + +void SocketComm::listenThread() { + while (true) { + SocketConn* conn = accept(); + if (conn == nullptr) { + return; + } + + conn->start(); + { + std::lock_guard<std::mutex> lock(mMutex); + mOpenConnections.push_back(std::unique_ptr<SocketConn>(conn)); + } + } +} + +/** + * Called occasionally to clean up connections that have been closed. + */ +void SocketComm::removeClosedConnections() { + std::lock_guard<std::mutex> lock(mMutex); + std::remove_if(mOpenConnections.begin(), mOpenConnections.end(), + [](std::unique_ptr<SocketConn> const& c) { return !c->isOpen(); }); +} + +SocketConn::SocketConn(MessageProcessor* messageProcessor, int sfd) + : CommConn(messageProcessor), mSockFd(sfd) {} + +/** + * Reads, in a loop, exactly numBytes from the given fd. If the connection is closed, returns + * an empty buffer, otherwise will return exactly the given number of bytes. + */ +std::vector<uint8_t> readExactly(int fd, int numBytes) { + std::vector<uint8_t> buffer(numBytes); + int totalRead = 0; + int offset = 0; + while (totalRead < numBytes) { + int numRead = ::read(fd, &buffer.data()[offset], numBytes - offset); + if (numRead == 0) { + buffer.resize(0); + return buffer; + } + + totalRead += numRead; + } + return buffer; +} + +/** + * Reads an int, guaranteed to be non-zero, from the given fd. If the connection is closed, returns + * -1. + */ +int32_t readInt(int fd) { + std::vector<uint8_t> buffer = readExactly(fd, sizeof(int32_t)); + if (buffer.size() == 0) { + return -1; + } + + int32_t value = *reinterpret_cast<int32_t*>(buffer.data()); + return ntohl(value); +} + +std::vector<uint8_t> SocketConn::read() { + int32_t msgSize = readInt(mSockFd); + if (msgSize <= 0) { + ALOGD("%s: Connection terminated on socket %d", __FUNCTION__, mSockFd); + return std::vector<uint8_t>(); + } + + return readExactly(mSockFd, msgSize); +} + +void SocketConn::stop() { + if (mSockFd > 0) { + close(mSockFd); + mSockFd = -1; + } +} + +int SocketConn::write(const std::vector<uint8_t>& data) { + static constexpr int MSG_HEADER_LEN = 4; + int retVal = 0; + union { + uint32_t msgLen; + uint8_t msgLenBytes[MSG_HEADER_LEN]; + }; + + // Prepare header for the message + msgLen = static_cast<uint32_t>(data.size()); + msgLen = htonl(msgLen); + + if (mSockFd > 0) { + retVal = ::write(mSockFd, msgLenBytes, MSG_HEADER_LEN); + + if (retVal == MSG_HEADER_LEN) { + retVal = ::write(mSockFd, data.data(), data.size()); + } + } + + return retVal; +} + +} // impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/emulator/Conn/SocketComm/include/SocketComm.h b/emulator/Conn/SocketComm/include/SocketComm.h new file mode 100644 index 0000000..0664747 --- /dev/null +++ b/emulator/Conn/SocketComm/include/SocketComm.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ANDROID_EMULATORSOCKETCOMM_SOCKETCOMM_H +#define ANDROID_EMULATORSOCKETCOMM_SOCKETCOMM_H + +#include <mutex> +#include <thread> +#include <vector> +#include "CommConn.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +class SocketConn; + +/** + * SocketComm opens a socket, and listens for connections from clients. Typically the client will be + * adb's TCP port-forwarding to enable a host PC to connect to the VehicleHAL. + */ +class SocketComm { + public: + SocketComm(MessageProcessor* messageProcessor); + virtual ~SocketComm(); + + void start(); + void stop(); + + /** + * Serialized and send the given message to all connected clients. + */ + void sendMessage(vhal_proto::EmulatorMessage const& msg); + + private: + int mListenFd; + std::unique_ptr<std::thread> mListenThread; + std::vector<std::unique_ptr<SocketConn>> mOpenConnections; + MessageProcessor* mMessageProcessor; + std::mutex mMutex; + + /** + * Opens the socket and begins listening. + * + * @return bool Returns true on success. + */ + bool listen(); + + /** + * Blocks and waits for a connection from a client, returns a new SocketConn with the connection + * or null, if the connection has been closed. + * + * @return int Returns fd or socket number if connection is successful. + * Otherwise, returns -1 if no connection is available. + */ + SocketConn* accept(); + + void listenThread(); + + void removeClosedConnections(); +}; + +/** + * SocketConn represents a single connection to a client. + */ +class SocketConn : public CommConn { + public: + SocketConn(MessageProcessor* messageProcessor, int sfd); + virtual ~SocketConn() = default; + + /** + * Blocking call to read data from the connection. + * + * @return std::vector<uint8_t> Serialized protobuf data received from emulator. This will be + * an empty vector if the connection was closed or some other error occurred. + */ + std::vector<uint8_t> read() override; + + /** + * Closes a connection if it is open. + */ + void stop() override; + + /** + * Transmits a string of data to the emulator. + * + * @param data Serialized protobuf data to transmit. + * + * @return int Number of bytes transmitted, or -1 if failed. + */ + int write(const std::vector<uint8_t>& data) override; + + inline bool isOpen() override { return mSockFd > 0; } + + private: + int mSockFd; +}; + +} // impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_EMULATORSOCKETCOMM_SOCKETCOMM_H |