diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2016-03-06 23:00:42 -0600 |
---|---|---|
committer | Charles Kerr <charles.kerr@canonical.com> | 2016-03-06 23:00:42 -0600 |
commit | d911528cfb367fac34a5764ad6bce339a12f56d0 (patch) | |
tree | 32b787f791508173b3322c21fe029fa3c44cc9db /src | |
parent | 8cf3f1a9b4e9d102dfeaf5fcfa7bc4a43711d0ae (diff) | |
download | ayatana-indicator-display-d911528cfb367fac34a5764ad6bce339a12f56d0.tar.gz ayatana-indicator-display-d911528cfb367fac34a5764ad6bce339a12f56d0.tar.bz2 ayatana-indicator-display-d911528cfb367fac34a5764ad6bce339a12f56d0.zip |
add ADB server/client + tests
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/adbd-client.cpp | 258 | ||||
-rw-r--r-- | src/adbd-client.h | 73 | ||||
-rw-r--r-- | src/main.cpp | 11 |
4 files changed, 344 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 982aa49..414a750 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") # handwritten source code... set (SERVICE_LIB_HANDWRITTEN_SOURCES + adbd-client.cpp exporter.cpp rotation-lock.cpp) @@ -19,7 +20,7 @@ link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) set (SERVICE_EXEC_HANDWRITTEN_SOURCES main.cpp) add_executable (${SERVICE_EXEC} ${SERVICE_EXEC_HANDWRITTEN_SOURCES}) -target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} Threads::Threads ${GCOV_LIBS}) install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) # add warnings/coverage info on handwritten files diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp new file mode 100644 index 0000000..38f202f --- /dev/null +++ b/src/adbd-client.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/adbd-client.h> + +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> + +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <thread> + +class GAdbdClient::Impl +{ +public: + + explicit Impl(const std::string& socket_path): + m_socket_path{socket_path}, + m_cancellable{g_cancellable_new()}, + m_worker_thread{&Impl::worker_func, this} + { + } + + ~Impl() + { + // tell the worker thread to stop whatever it's doing and exit. + g_cancellable_cancel(m_cancellable); + m_sleep_cv.notify_one(); + m_worker_thread.join(); + g_clear_object(&m_cancellable); + } + + core::Signal<const PKRequest&>& on_pk_request() + { + return m_on_pk_request; + } + +private: + + // struct to carry request info from the worker thread to the GMainContext thread + struct PKIdleData + { + Impl* self = nullptr; + GCancellable* cancellable = nullptr; + const std::string public_key; + + PKIdleData(Impl* self_, GCancellable* cancellable_, std::string public_key_): + self(self_), + cancellable(G_CANCELLABLE(g_object_ref(cancellable_))), + public_key(public_key_) {} + + ~PKIdleData() {g_clear_object(&cancellable);} + + }; + + void pass_public_key_to_main_thread(const std::string& public_key) + { + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + on_public_key_request_static, + new PKIdleData{this, m_cancellable, public_key}, + [](gpointer id){delete static_cast<PKIdleData*>(id);}); + } + + static gboolean on_public_key_request_static (gpointer gdata) // runs in main thread + { + /* NB: It's possible (though unlikely) that data.self was destroyed + while this callback was pending, so we must check is-cancelled FIRST */ + auto data = static_cast<PKIdleData*>(gdata); + if (!g_cancellable_is_cancelled(data->cancellable)) + { + // notify our listeners of the request + auto self = data->self; + struct PKRequest req; + req.public_key = data->public_key; + req.respond = [self](PKResponse response){self->on_public_key_response(response);}; + self->m_on_pk_request(req); + } + + return G_SOURCE_REMOVE; + } + + void on_public_key_response(PKResponse response) + { + // set m_pkresponse and wake up the waiting worker thread + std::unique_lock<std::mutex> lk(m_pkresponse_mutex); + m_pkresponse = response; + m_pkresponse_ready = true; + m_pkresponse_cv.notify_one(); + } + + /*** + **** + ***/ + + void worker_func() // runs in worker thread + { + const auto socket_path {m_socket_path}; + + while (!g_cancellable_is_cancelled(m_cancellable)) + { + g_debug("%s creating a client socket", G_STRLOC); + auto socket = create_client_socket(socket_path); + bool got_valid_req = false; + + g_debug("%s calling read_request", G_STRLOC); + std::string reqstr; + if (socket != nullptr) + reqstr = read_request(socket); + if (!reqstr.empty()) + g_debug("%s got request [%s]", G_STRLOC, reqstr.c_str()); + + if (reqstr.substr(0,2) == "PK") { + PKResponse response = PKResponse::DENY; + const auto public_key = reqstr.substr(2); + g_debug("%s got pk [%s]", G_STRLOC, public_key.c_str()); + if (!public_key.empty()) { + got_valid_req = true; + std::unique_lock<std::mutex> lk(m_pkresponse_mutex); + m_pkresponse_ready = false; + pass_public_key_to_main_thread(public_key); + m_pkresponse_cv.wait(lk, [this](){ + return m_pkresponse_ready || g_cancellable_is_cancelled(m_cancellable); + }); + response = m_pkresponse; + g_debug("%s got response '%d'", G_STRLOC, int(response)); + } + if (!g_cancellable_is_cancelled(m_cancellable)) + send_pk_response(socket, response); + } else if (!reqstr.empty()) { + g_warning("Invalid ADB request: [%s]", reqstr.c_str()); + } + + g_clear_object(&socket); + + // If nothing interesting's happening, sleep a bit. + // (Interval copied from UsbDebuggingManager.java) + static constexpr auto sleep_interval {std::chrono::seconds(1)}; + if (!got_valid_req && !g_cancellable_is_cancelled(m_cancellable)) { + std::unique_lock<std::mutex> lk(m_sleep_mutex); + m_sleep_cv.wait_for(lk, sleep_interval); + } + } + } + + // connect to a local domain socket + GSocket* create_client_socket(const std::string& socket_path) + { + GError* error {}; + auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (error != nullptr) { + g_warning("Error creating adbd client socket: %s", error->message); + g_clear_error(&error); + g_clear_object(&socket); + return nullptr; + } + + auto address = g_unix_socket_address_new(socket_path.c_str()); + const auto connected = g_socket_connect(socket, address, m_cancellable, nullptr); + g_clear_object(&address); + if (!connected) { + g_clear_object(&socket); + return nullptr; + } + + return socket; + } + + std::string read_request(GSocket* socket) + { + char buf[4096] = {}; + g_debug("%s calling g_socket_receive()", G_STRLOC); + const auto n_bytes = g_socket_receive (socket, buf, sizeof(buf), m_cancellable, nullptr); + std::string ret; + if (n_bytes > 0) + ret.append(buf, std::string::size_type(n_bytes)); + g_debug("%s g_socket_receive got %d bytes: [%s]", G_STRLOC, int(n_bytes), ret.c_str()); + return ret; + } + + void send_pk_response(GSocket* socket, PKResponse response) + { + std::string response_str; + switch(response) { + case PKResponse::ALLOW: response_str = "OK"; break; + case PKResponse::DENY: response_str = "NO"; break; + } + g_debug("%s sending reply: [%s]", G_STRLOC, response_str.c_str()); + + GError* error {}; + g_socket_send(socket, + response_str.c_str(), + response_str.size(), + m_cancellable, + &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("GAdbdServer: Error accepting socket connection: %s", error->message); + g_clear_error(&error); + } + } + + const std::string m_socket_path; + GCancellable* m_cancellable = nullptr; + std::thread m_worker_thread; + core::Signal<const PKRequest&> m_on_pk_request; + + std::mutex m_sleep_mutex; + std::condition_variable m_sleep_cv; + + std::mutex m_pkresponse_mutex; + std::condition_variable m_pkresponse_cv; + bool m_pkresponse_ready = false; + PKResponse m_pkresponse = PKResponse::DENY; +}; + +/*** +**** +***/ + +AdbdClient::~AdbdClient() +{ +} + +GAdbdClient::GAdbdClient(const std::string& socket_path): + impl{new Impl{socket_path}} +{ +} + +GAdbdClient::~GAdbdClient() +{ +} + +core::Signal<const AdbdClient::PKRequest&>& +GAdbdClient::on_pk_request() +{ + return impl->on_pk_request(); +} + diff --git a/src/adbd-client.h b/src/adbd-client.h new file mode 100644 index 0000000..aef7674 --- /dev/null +++ b/src/adbd-client.h @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <functional> +#include <memory> +#include <string> + +#include <core/signal.h> + +/** + * Receives public key requests from ADBD and sends a response back. + * + * AdbClient only provides a receive/respond mechanism. The decision + * of what response gets sent is delegated out to a listener via + * the on_pk_request signal. + * + * The decider should connect to on_pk_request, listen for PKRequests, + * and call the request's `respond' method with the desired response. + */ +class AdbdClient +{ +public: + virtual ~AdbdClient(); + + enum class PKResponse { DENY, ALLOW }; + + struct PKRequest { + std::string public_key; + std::function<void(PKResponse)> respond; + }; + + virtual core::Signal<const PKRequest&>& on_pk_request() =0; + +protected: + AdbdClient() =default; +}; + +/** + * An AdbdClient designed to work with GLib's event loop. + * + * The on_pk_request() signal will be called in global GMainContext's thread; + * ie, just like a function passed to g_idle_add() or g_timeout_add(). + */ +class GAdbdClient: public AdbdClient +{ +public: + explicit GAdbdClient(const std::string& socket_path); + ~GAdbdClient(); + core::Signal<const PKRequest&>& on_pk_request() override; + +private: + class Impl; + std::unique_ptr<Impl> impl; +}; + diff --git a/src/main.cpp b/src/main.cpp index 86bdeb3..0c56bd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include <src/adbd-client.h> #include <src/exporter.h> #include <src/rotation-lock.h> @@ -54,6 +55,16 @@ main(int /*argc*/, char** /*argv*/) exporters.push_back(exporter); } + // We need the ADBD handler running, + // even though it doesn't have an indicator component yet + static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adb"}; + GAdbdClient adbd_client{ADB_SOCKET_PATH}; + adbd_client.on_pk_request().connect([](const AdbdClient::PKRequest& req){ + g_debug("%s got pk_request [%s]", G_STRLOC, req.public_key.c_str()); + // FIXME: actually decide what response to send back + req.respond(AdbdClient::PKResponse::ALLOW); + }); + g_main_loop_run(loop); // cleanup |