From patchwork Sun Jun 30 16:25:12 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1529 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6309160BC7 for ; Sun, 30 Jun 2019 18:25:31 +0200 (CEST) X-Halon-ID: a0ff70a1-9b53-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id a0ff70a1-9b53-11e9-8ab4-005056917a89; Sun, 30 Jun 2019 18:25:10 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sun, 30 Jun 2019 18:25:12 +0200 Message-Id: <20190630162514.20522-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> References: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 1/3] libcamera: timer: Stop timer when it is deleted X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 30 Jun 2019 16:25:31 -0000 If a timer is running while it's deleted it is still register with the event dispatcher. This causes a segmentation fault when the timer time-out and its signal is emitted. Fix this my stopping the timer when it's deleted. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- include/libcamera/timer.h | 1 + src/libcamera/timer.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/libcamera/timer.h b/include/libcamera/timer.h index 97dcc01f493dc70d..f082339b1fed9de7 100644 --- a/include/libcamera/timer.h +++ b/include/libcamera/timer.h @@ -17,6 +17,7 @@ class Timer { public: Timer(); + ~Timer(); void start(unsigned int msec); void stop(); diff --git a/src/libcamera/timer.cpp b/src/libcamera/timer.cpp index 1cce45085c61be2f..0dcb4e767be30b32 100644 --- a/src/libcamera/timer.cpp +++ b/src/libcamera/timer.cpp @@ -43,6 +43,11 @@ Timer::Timer() { } +Timer::~Timer() +{ + stop(); +} + /** * \brief Start or restart the timer with a timeout of \a msec * \param[in] msec The timer duration in milliseconds From patchwork Sun Jun 30 16:25:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1531 Return-Path: Received: from bin-mail-out-05.binero.net (bin-mail-out-05.binero.net [195.74.38.228]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F1D7C61EB4 for ; Sun, 30 Jun 2019 18:25:32 +0200 (CEST) X-Halon-ID: a16ab8a0-9b53-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id a16ab8a0-9b53-11e9-8ab4-005056917a89; Sun, 30 Jun 2019 18:25:11 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sun, 30 Jun 2019 18:25:13 +0200 Message-Id: <20190630162514.20522-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> References: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 2/3] libcamera: ipc: unix: Add a IPC mechanism based on Unix sockets X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 30 Jun 2019 16:25:33 -0000 To be able to isolate an IPA component in a separate process an IPC mechanism is needed to communicate with it. Add an IPC mechanism based on Unix sockets which allows users to pass both data and file descriptors to and from the IPA process. The implementation allows users to send both data and file descriptors in the same message. This allows users to more easily implement serialization and deserialization of objects as all elements belonging to an object can be sent in one message. Signed-off-by: Niklas Söderlund --- src/libcamera/include/ipc_unixsocket.h | 57 +++++ src/libcamera/ipc_unixsocket.cpp | 312 +++++++++++++++++++++++++ src/libcamera/meson.build | 2 + 3 files changed, 371 insertions(+) create mode 100644 src/libcamera/include/ipc_unixsocket.h create mode 100644 src/libcamera/ipc_unixsocket.cpp diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h new file mode 100644 index 0000000000000000..84d786e8424ccf54 --- /dev/null +++ b/src/libcamera/include/ipc_unixsocket.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * ipc_unixsocket.h - IPC mechanism based on Unix sockets + */ + +#ifndef __LIBCAMERA_IPC_UNIXSOCKET_H__ +#define __LIBCAMERA_IPC_UNIXSOCKET_H__ + +#include +#include +#include + +#include + +namespace libcamera { + +class IPCUnixSocket +{ +public: + struct Payload { + std::vector data; + std::vector fds; + }; + + IPCUnixSocket(); + ~IPCUnixSocket(); + + int create(); + int bind(int fd); + void close(); + bool isBound(); + + int send(const Payload &payload); + int receive(Payload *payload); + + Signal readyRead; + +private: + struct Header { + uint32_t data; + uint8_t fds; + }; + + int sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num); + int recvData(void *buffer, size_t length, int32_t *fds, unsigned int num); + + void dataNotifier(EventNotifier *notifier); + + int fd_; + EventNotifier *notifier_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_IPC_UNIXSOCKET_H__ */ diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp new file mode 100644 index 0000000000000000..f9ef4dafa4db0652 --- /dev/null +++ b/src/libcamera/ipc_unixsocket.cpp @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets + */ + +#include "ipc_unixsocket.h" + +#include +#include +#include + +#include "log.h" + +/** + * \file ipc_unixsocket.h + * \brief IPC mechanism based on Unix sockets + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPCUnixSocket) + +/** + * \struct IPCUnixSocket::Payload + * \brief Container for an IPC payload + * + * Holds an array of bytes and an array of file descriptors that can be + * transported across a IPC boundary. + */ + +/** + * \var IPCUnixSocket::Payload::data + * \brief Array of bytes to cross IPC boundary + */ + +/** + * \var IPCUnixSocket::Payload::fds + * \brief Array of file descriptors to cross IPC boundary + */ + +/** + * \class IPCUnixSocket + * \brief IPC mechanism based on Unix sockets + * + * The Unix socket IPC allows bidirectional communication between two processes + * through unnamed Unix sockets. It implements datagram-based communication, + * transporting entire payloads with guaranteed ordering. + * + * The IPC design is asynchronous, a message is queued to a receiver which gets + * notified that a message is ready to be consumed by a signal. The queuer of + * the message gets no notification when a message is delivered nor processed. + * If such interactions are needed a protocol specific to the users use-case + * should be implemented on top of the IPC objects. + * + * The IPC design can transmit messages in any direction and the only difference + * from a master and slave operation is how they are created and bound to one + * another. After the two parts are setup the operation to send and receive + * messages are the same for both. + * + * Establishment of an IPC channel is asymmetrical. The side that initiates + * communication first instantiates a local side socket and creates the channel + * with create(). The method returns a file descriptor for the remote side of + * the channel, which is passed to the remote process through an out-of-band + * communication method. The remote side then instantiates a socket, and binds + * it to the other side by passing the file descriptor to bind(). At that point + * the channel is operation and communication is bidirectional and symmmetrical. + */ + +IPCUnixSocket::IPCUnixSocket() + : fd_(-1), notifier_(nullptr) +{ +} + +IPCUnixSocket::~IPCUnixSocket() +{ + close(); +} + +/** + * \brief Create an new IPC channel + * + * This method creates a new IPC channel. The socket instance is bound to the + * local side of the channel, and the method returns a file descriptor bound to + * the remote side. The caller is responsible for passing the file descriptor to + * the remote process, where it can be used with IPCUnixSocket::bind() to bind + * the remote side socket. + * + * \return A file descriptor on success, negative error code on failure + */ +int IPCUnixSocket::create() +{ + int sockets[2]; + int ret; + + ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets); + if (ret) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to create socket pair: " << strerror(-ret); + return ret; + } + + ret = bind(sockets[0]); + if (ret) + return ret; + + return sockets[1]; +} + +/** + * \brief Bind to an existing IPC channel + * \param[in] fd File descriptor + * + * This method binds the socket instance to an existing IPC channel identified + * by the file descriptor \a fd. The file descriptor is obtained from the + * IPCUnixSocket::create() method. + * + * \return 0 on success or a negative error code otherwise + */ +int IPCUnixSocket::bind(int fd) +{ + if (isBound()) + return -EINVAL; + + fd_ = fd; + notifier_ = new EventNotifier(fd_, EventNotifier::Read); + notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier); + + return 0; +} + +/** + * \brief Close the IPC channel + * + * No communication is possible after close() has been called. + */ +void IPCUnixSocket::close() +{ + if (!isBound()) + return; + + delete notifier_; + notifier_ = nullptr; + + ::close(fd_); + + fd_ = -1; +} + +/** + * \brief Check if the IPC channel is bound + * \return True if the IPC channel is bound, false otherwise + */ +bool IPCUnixSocket::isBound() +{ + return fd_ != -1; +} + +/** + * \brief Send a message payload + * \param[in] payload Message payload to send + * + * This method queues the message payload for transmission to the other end of + * the IPC channel. This method returns immediately, before the message is + * delivered to the remote side. + * + * \return 0 on success or a negative error code otherwise + */ +int IPCUnixSocket::send(const Payload &payload) +{ + Header hdr; + int ret; + + if (!isBound()) + return -ENOTCONN; + + hdr.data = payload.data.size(); + hdr.fds = payload.fds.size(); + + ret = ::send(fd_, &hdr, sizeof(hdr), 0); + if (ret < 0) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to send: " << strerror(-ret); + return ret; + } + + return sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds); +} + +/** + * \brief Receive a message payload + * \param[out] payload Payload where to write the received message + * + * This method receives the message payload from the IPC channel and writes it + * to the \a payload. This method blocks until one message is received, if an + * asynchronous behavior is desired this method should be called when the + * readyRead signal is emitted. + * + * \return 0 on success or a negative error code otherwise + */ +int IPCUnixSocket::receive(Payload *payload) +{ + Header hdr; + int ret; + + if (!isBound()) + return -ENOTCONN; + + if (!payload) + return -EINVAL; + + ret = ::recv(fd_, &hdr, sizeof(hdr), 0); + if (ret < 0) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to recv header: " << strerror(-ret); + return ret; + } + + payload->data.resize(hdr.data); + payload->fds.resize(hdr.fds); + + ret = recvData(payload->data.data(), hdr.data, payload->fds.data(), hdr.fds); + if (ret) + return ret; + + return 0; +} + +/** + * \var IPCUnixSocket::readyRead + * \brief A Signal emitted when a message is ready to be read + */ + +int IPCUnixSocket::sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num) +{ + struct iovec iov[1]; + iov[0].iov_base = const_cast(buffer); + iov[0].iov_len = length; + + char buf[CMSG_SPACE(num * sizeof(uint32_t))]; + memset(buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + struct msghdr msg; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg; + msg.msg_controllen = cmsg->cmsg_len; + msg.msg_flags = 0; + memcpy(CMSG_DATA(cmsg), fds, num * sizeof(uint32_t)); + + if (sendmsg(fd_, &msg, 0) < 0) { + int ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to sendmsg: " << strerror(-ret); + return ret; + } + + return 0; +} + +int IPCUnixSocket::recvData(void *buffer, size_t length, int32_t *fds, unsigned int num) +{ + struct iovec iov[1]; + iov[0].iov_base = buffer; + iov[0].iov_len = length; + + char buf[CMSG_SPACE(num * sizeof(uint32_t))]; + memset(buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + struct msghdr msg; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg; + msg.msg_controllen = cmsg->cmsg_len; + msg.msg_flags = 0; + + if (recvmsg(fd_, &msg, 0) < 0) { + int ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to recvmsg: " << strerror(-ret); + return ret; + } + + memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t)); + + return 0; +} + +void IPCUnixSocket::dataNotifier(EventNotifier *notifier) +{ + readyRead.emit(this); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 985aa7e8ab0eb6ce..45bd9d1793aa0b19 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -13,6 +13,7 @@ libcamera_sources = files([ 'ipa_interface.cpp', 'ipa_manager.cpp', 'ipa_module.cpp', + 'ipc_unixsocket.cpp', 'log.cpp', 'media_device.cpp', 'media_object.cpp', @@ -38,6 +39,7 @@ libcamera_headers = files([ 'include/formats.h', 'include/ipa_manager.h', 'include/ipa_module.h', + 'include/ipc_unixsocket.h', 'include/log.h', 'include/media_device.h', 'include/media_object.h', From patchwork Sun Jun 30 16:25:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1532 Return-Path: Received: from bin-mail-out-05.binero.net (bin-mail-out-05.binero.net [195.74.38.228]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5B1A761EB4 for ; Sun, 30 Jun 2019 18:25:33 +0200 (CEST) X-Halon-ID: a25856ea-9b53-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id a25856ea-9b53-11e9-8ab4-005056917a89; Sun, 30 Jun 2019 18:25:12 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sun, 30 Jun 2019 18:25:14 +0200 Message-Id: <20190630162514.20522-4-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> References: <20190630162514.20522-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 3/3] test: ipc: unix: Add test for IPCUnixSocket X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 30 Jun 2019 16:25:33 -0000 Test that the IPC supports sending data and file descriptors over the IPC medium. To be able to execute the test two parts are needed, one to drive the test and act as the libcamera (master) and a one to act as the IPA (slave). The master drives the testing posting requests to the slave to process and sometimes respond to. A few different tests are performed. - Master sends an array to the slave which responds with a reversed copy of the array. The master verifies that a reversed array is returned. - Master sends a list of file descriptors and ask the slave to calculate and respond with the sum of the size of the files. The master verifies that the calculated size is correct. - Master sends a pre-computed size and a list of file descriptors and ask the slave to verify that the pre-computed size matches the sum of the size of the file descriptors. Signed-off-by: Niklas Söderlund --- test/ipc/meson.build | 12 ++ test/ipc/unixsocket.cpp | 390 ++++++++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 3 files changed, 403 insertions(+) create mode 100644 test/ipc/meson.build create mode 100644 test/ipc/unixsocket.cpp diff --git a/test/ipc/meson.build b/test/ipc/meson.build new file mode 100644 index 0000000000000000..ca8375f35df91731 --- /dev/null +++ b/test/ipc/meson.build @@ -0,0 +1,12 @@ +ipc_tests = [ + [ 'unixsocket', 'unixsocket.cpp' ], +] + +foreach t : ipc_tests + exe = executable(t[0], t[1], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + + test(t[0], exe, suite : 'ipc', is_parallel : false) +endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp new file mode 100644 index 0000000000000000..f433102af1b6d2a3 --- /dev/null +++ b/test/ipc/unixsocket.cpp @@ -0,0 +1,390 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket.cpp - Unix socket IPC test + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ipc_unixsocket.h" +#include "test.h" + +#define CMD_CLOSE 0 +#define CMD_REVERSE 1 +#define CMD_LEN_CALC 2 +#define CMD_LEN_CMP 3 + +using namespace std; +using namespace libcamera; + +int calculateLength(int fd) +{ + lseek(fd, 0, 0); + int size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, 0); + + return size; +} + +class UnixSocketTestSlave +{ +public: + UnixSocketTestSlave() + : exitCode_(EXIT_FAILURE), exit_(false) + { + dispatcher_ = CameraManager::instance()->eventDispatcher(); + ipc_.readyRead.connect(this, &UnixSocketTestSlave::readyRead); + } + + int run(int fd) + { + if (ipc_.bind(fd)) { + cerr << "Failed to connect to IPC channel" << endl; + return EXIT_FAILURE; + } + + while (!exit_) + dispatcher_->processEvents(); + + ipc_.close(); + + return exitCode_; + } + +private: + void readyRead(IPCUnixSocket *ipc) + { + IPCUnixSocket::Payload message, response; + int ret; + + if (ipc->receive(&message)) { + cerr << "Receive message failed" << endl; + return; + } + + const uint8_t cmd = message.data[0]; + cout << "Slave received command " << static_cast(cmd) << endl; + + switch (cmd) { + case CMD_CLOSE: + stop(0); + break; + + case CMD_REVERSE: { + response.data = message.data; + std::reverse(response.data.begin() + 1, response.data.end()); + + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Reverse fail" << endl; + stop(ret); + } + break; + } + + case CMD_LEN_CALC: { + int size = 0; + for (int fd : message.fds) + size += calculateLength(fd); + + response.data.resize(1 + sizeof(size)); + response.data[0] = cmd; + memcpy(response.data.data() + 1, &size, sizeof(size)); + + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Calc fail" << endl; + stop(ret); + } + break; + } + + case CMD_LEN_CMP: { + int size = 0; + for (int fd : message.fds) + size += calculateLength(fd); + + int cmp; + memcpy(&cmp, message.data.data() + 1, sizeof(cmp)); + + if (cmp != size) { + cerr << "Compare fail" << endl; + stop(-ERANGE); + } + break; + } + + default: + cerr << "Unknown command " << cmd << endl; + stop(-EINVAL); + break; + } + } + + void stop(int code) + { + exitCode_ = code; + exit_ = true; + } + + IPCUnixSocket ipc_; + EventDispatcher *dispatcher_; + int exitCode_; + bool exit_; +}; + +class UnixSocketTest : public Test +{ +protected: + int slaveStart(int fd) + { + pid_ = fork(); + + if (pid_ == -1) + return TestFail; + + if (!pid_) { + std::string arg = std::to_string(fd); + execl("/proc/self/exe", "/proc/self/exe", + arg.c_str(), nullptr); + + /* Only get here if exec fails. */ + exit(TestFail); + } + + return TestPass; + } + + int slaveStop() + { + int status; + + if (pid_ < 0) + return TestFail; + + if (waitpid(pid_, &status, 0) < 0) + return TestFail; + + if (!WIFEXITED(status) || WEXITSTATUS(status)) + return TestFail; + + return TestPass; + } + + int testReverse() + { + IPCUnixSocket::Payload message, response; + int ret; + + message.data = { CMD_REVERSE, 1, 2, 3, 4, 5 }; + + ret = call(message, &response); + if (ret) + return ret; + + std::reverse(response.data.begin() + 1, response.data.end()); + if (message.data != response.data) + return TestFail; + + return 0; + } + + int testCalc() + { + IPCUnixSocket::Payload message, response; + int sizeOut, sizeIn, ret; + + sizeOut = prepareFDs(&message, 2); + if (sizeOut < 0) + return sizeOut; + + message.data.push_back(CMD_LEN_CALC); + + ret = call(message, &response); + if (ret) + return ret; + + memcpy(&sizeIn, response.data.data() + 1, sizeof(sizeIn)); + if (sizeOut != sizeIn) + return TestFail; + + return 0; + } + + int testCmp() + { + IPCUnixSocket::Payload message; + int size; + + size = prepareFDs(&message, 7); + if (size < 0) + return size; + + message.data.resize(1 + sizeof(size)); + message.data[0] = CMD_LEN_CMP; + memcpy(message.data.data() + 1, &size, sizeof(size)); + + if (ipc_.send(message)) + return TestFail; + + return 0; + } + + int init() + { + callResponse_ = nullptr; + return 0; + } + + int run() + { + int slavefd = ipc_.create(); + if (slavefd < 0) + return TestFail; + + if (slaveStart(slavefd)) { + cerr << "Failed to start slave" << endl; + return TestFail; + } + + ipc_.readyRead.connect(this, &UnixSocketTest::readyRead); + + /* Test reversing a string, this test sending only data. */ + if (testReverse()) { + cerr << "Reveres array test failed" << endl; + return TestFail; + } + + /* Test offloading a calculation, this test sending only FDs. */ + if (testCalc()) { + cerr << "Calc test failed" << endl; + return TestFail; + } + + /* Test fire and forget, this tests sending data and FDs. */ + if (testCmp()) { + cerr << "Cmp test failed" << endl; + return TestFail; + } + + /* Close slave connection. */ + IPCUnixSocket::Payload close; + close.data.push_back(CMD_CLOSE); + if (ipc_.send(close)) { + cerr << "Closing IPC channel failed" << endl; + return TestFail; + } + + ipc_.close(); + if (slaveStop()) { + cerr << "Failed to stop slave" << endl; + return TestFail; + } + + return TestPass; + } + +private: + int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response) + { + Timer timeout; + int ret; + + if (callResponse_) + return -EBUSY; + + callDone_ = false; + callResponse_ = response; + + ret = ipc_.send(message); + if (ret) + return ret; + + timeout.start(200); + while (!callDone_) { + if (!timeout.isRunning()) { + cerr << "Call timeout!" << endl; + callResponse_ = nullptr; + return -ETIMEDOUT; + } + + CameraManager::instance()->eventDispatcher()->processEvents(); + } + + callResponse_ = nullptr; + + return 0; + } + + void readyRead(IPCUnixSocket *ipc) + { + IPCUnixSocket::Payload message; + + if (!callResponse_) { + cerr << "Read ready without expecting data, fail." << endl; + return; + } + + if (ipc->receive(callResponse_)) { + cerr << "Receive message failed" << endl; + return; + } + + const uint8_t cmd = callResponse_->data[0]; + cout << "Master received command " << static_cast(cmd) << endl; + + callDone_ = true; + } + + int prepareFDs(IPCUnixSocket::Payload *message, unsigned int num) + { + int fd = open("/proc/self/exe", O_RDONLY); + if (fd < 0) + return fd; + + int size = 0; + for (unsigned int i = 0; i < num; i++) { + int clone = dup(fd); + if (clone < 0) + return clone; + + size += calculateLength(clone); + message->fds.push_back(clone); + } + + close(fd); + + return size; + } + + pid_t pid_; + IPCUnixSocket ipc_; + bool callDone_; + IPCUnixSocket::Payload *callResponse_; +}; + +/* + * Can't use TEST_REGISTER() as single binary needs to act as both proxy + * master and slave. + */ +int main(int argc, char **argv) +{ + if (argc == 2) { + int ipcfd = std::stoi(argv[1]); + UnixSocketTestSlave slave; + return slave.run(ipcfd); + } + + return UnixSocketTest().execute(); +} diff --git a/test/meson.build b/test/meson.build index c36ac24796367501..3666f6b2385bd4ca 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ subdir('libtest') subdir('camera') subdir('ipa') +subdir('ipc') subdir('media_device') subdir('pipeline') subdir('stream')