| // Copyright 2024 Google LLC |
| // |
| // 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 |
| // |
| // https://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 "mpact/sim/util/renode/socket_cli.h" |
| |
| #include <fcntl.h> |
| #include <netinet/in.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include <cerrno> |
| #include <cstring> |
| #include <iostream> |
| #include <istream> |
| #include <ostream> |
| #include <thread> // NOLINT |
| #include <utility> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "mpact/sim/generic/debug_command_shell_interface.h" |
| #include "mpact/sim/util/renode/socket_streambuf.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace util { |
| namespace renode { |
| |
| using ::mpact::sim::generic::DebugCommandShellInterface; |
| |
| SocketCLI::SocketCLI(int port, DebugCommandShellInterface& dbg_shell, |
| absl::AnyInvocable<void(bool)> is_connected_cb) |
| : dbg_shell_(dbg_shell), is_connected_cb_(std::move(is_connected_cb)) { |
| // Set initial status as not connected. |
| is_connected_cb_(false); |
| good_ = true; |
| // If the port has not been specified, just return. |
| if (port == -1) { |
| good_ = false; |
| return; |
| } |
| // Create the socket on the given port. |
| server_socket_ = |
| socket(/*domain=*/AF_INET, /*type=*/SOCK_STREAM, /*protocol=*/0); |
| int one = 1; |
| int err = |
| setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| if (err != 0) { |
| LOG(ERROR) << "Failed to set socket options: " << strerror(errno); |
| good_ = false; |
| return; |
| } |
| sockaddr_in server_address_int; |
| server_address_int.sin_family = AF_INET; |
| server_address_int.sin_addr.s_addr = INADDR_ANY; |
| server_address_int.sin_port = htons(port); |
| std::memset(&server_address_int.sin_zero, 0, |
| sizeof(server_address_int.sin_zero)); |
| int res = bind(server_socket_, |
| reinterpret_cast<const sockaddr*>(&server_address_int), |
| sizeof(server_address_int)); |
| if (res != 0) { |
| good_ = false; |
| LOG(ERROR) << "Failed to bind to port " << port << ": " << strerror(errno); |
| return; |
| } |
| res = listen(server_socket_, /*backlog=*/1); |
| if (res != 0) { |
| good_ = false; |
| LOG(ERROR) << "Failed to listen on port " << port << ": " |
| << strerror(errno); |
| } |
| |
| // Launch CLI thread. |
| cli_thread_ = std::thread([this, port]() { |
| // Accept the connection and set up streams. |
| cli_fd_ = accept(server_socket_, /*addr=*/nullptr, /*addrlen=*/nullptr); |
| if (cli_fd_ == -1) { |
| good_ = false; |
| LOG(ERROR) << "Failed to accept connection on port " << port << ": " |
| << strerror(errno); |
| } else { |
| good_ = true; |
| // Create input and output streams attached to the socket from which the |
| // cli will read commands/write responses. |
| SocketStreambuf out_buf(cli_fd_); |
| SocketStreambuf in_buf(cli_fd_); |
| std::ostream os(&out_buf); |
| if (!os.good()) return; |
| std::istream is(&in_buf); |
| if (!is.good()) return; |
| // Notify that CLI is connected. |
| is_connected_cb_(true); |
| // Start the CLI. |
| os << "CLI connected:\n"; |
| dbg_shell_.Run(is, os); |
| // Notify that CLI is disconnected. |
| is_connected_cb_(false); |
| } |
| good_ = false; |
| }); |
| } |
| |
| SocketCLI::~SocketCLI() { |
| // Shutdown the connection. |
| if (cli_fd_ != -1) { |
| (void)shutdown(cli_fd_, SHUT_RDWR); |
| int err = close(cli_fd_); |
| if (err != 0) { |
| LOG(ERROR) << "Failed to close client socket " << cli_fd_ << ": " |
| << strerror(errno); |
| } |
| } |
| if (server_socket_ != -1) { |
| int res = shutdown(server_socket_, SHUT_RDWR); |
| if (res != 0) { |
| LOG(ERROR) << "Failed to shutdown server socket " << server_socket_ |
| << ": " << strerror(errno); |
| } |
| res = fcntl(server_socket_, F_GETFD); |
| if (res >= 0) { |
| int err = close(server_socket_); |
| if (err != 0) { |
| LOG(ERROR) << "Failed to close server socket " << server_socket_ << ": " |
| << strerror(errno); |
| } |
| } |
| } |
| if (cli_thread_.joinable()) cli_thread_.join(); |
| } |
| |
| } // namespace renode |
| } // namespace util |
| } // namespace sim |
| } // namespace mpact |