summaryrefslogtreecommitdiff
path: root/src/monitor-gui.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/monitor-gui.cc')
-rw-r--r--src/monitor-gui.cc487
1 files changed, 487 insertions, 0 deletions
diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc
new file mode 100644
index 0000000..682b441
--- /dev/null
+++ b/src/monitor-gui.cc
@@ -0,0 +1,487 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+
+#include <memory>
+#include <string.h>
+#include <unordered_map>
+#include <vector>
+
+#include "gui_about.hh"
+#include "gui_formapply.hh"
+#include "gui_hexdump.hh"
+#include "gui_listmodel.hh"
+#include "gui_menu.hh"
+#include "gui_main.hh"
+#include "gui_statusbar.hh"
+#include "looper.hh"
+#include "monitor.hh"
+#include "observers.hh"
+#include "resolver.hh"
+
+namespace {
+
+std::string const ACTION_CONNECT = "connect";
+std::string const ACTION_DISCONNECT = "disconnect";
+std::string const ACTION_EXIT = "exit";
+std::string const ACTION_ABOUT = "about";
+std::string const ACTION_COPY_RAW = "copy_raw";
+std::string const ACTION_COPY_TEXT = "copy_text";
+std::string const ACTION_CLEAR = "clear";
+
+bool valid_hostname(std::string const& host) {
+ return !host.empty();
+}
+
+bool parse_address(std::string const& addr, std::string* host, uint16_t* port) {
+ auto i = addr.find(':');
+ if (i == std::string::npos) {
+ if (!valid_hostname(addr)) return false;
+ if (host) *host = addr;
+ if (port) *port = 9000;
+ return true;
+ }
+ auto h = addr.substr(0, i);
+ if (!valid_hostname(h)) return false;
+ char* end = nullptr;
+ auto p = strtoul(addr.c_str() + i + 1, &end, 10);
+ if (p == 0 || p > 65535 || !end || *end) return false;
+ if (host) *host = h;
+ if (port) *port = p;
+ return true;
+}
+
+class PackageList : public GuiListModel {
+public:
+ struct Package {
+ std::string timestamp;
+ std::string from;
+ std::string to;
+ std::string size;
+
+ std::string data;
+ };
+
+ size_t rows() const override {
+ return packages_.size();
+ }
+
+ size_t columns() const override {
+ return 4;
+ }
+
+ std::string header(size_t column) const override {
+ switch (column) {
+ case 0:
+ return "Time";
+ case 1:
+ return "From";
+ case 2:
+ return "To";
+ case 3:
+ return "Data";
+ }
+ assert(false);
+ return "";
+ }
+
+ std::string data(size_t row, size_t column) const override {
+ if (row < packages_.size()) {
+ auto const& pkg = packages_[row];
+ switch (column) {
+ case 0: return pkg.timestamp;
+ case 1: return pkg.from;
+ case 2: return pkg.to;
+ case 3: return pkg.size;
+ }
+ }
+ assert(false);
+ return "";
+ }
+
+ void add_listener(Listener* listener) override {
+ listeners_.insert(listener);
+ }
+
+ void remove_listener(Listener* listener) override {
+ listeners_.erase(listener);
+ }
+
+ void package(Monitor::Package const& package) {
+ auto const index = packages_.size();
+ open_.emplace(package.id, index);
+ packages_.emplace_back();
+ auto& pkg = packages_.back();
+ format_timestamp(&pkg.timestamp, package.timestamp);
+ format_host_port(&pkg.from, package.source_host, package.source_port);
+ format_host_port(&pkg.to, package.target_host, package.target_port);
+ format_size(&pkg.size, 0, false);
+
+ notify_added(index, index);
+ }
+
+ size_t package_data(uint32_t id, char const* data, size_t size, bool last) {
+ auto it = open_.find(id);
+ if (it == open_.end()) {
+ assert(false);
+ return std::string::npos;
+ }
+ auto const index = it->second;
+ auto& pkg = packages_[index];
+ pkg.data.append(data, size);
+ format_size(&pkg.size, pkg.data.size(), last);
+ if (last) open_.erase(it);
+
+ notify_changed(index, index);
+ return index;
+ }
+
+ Package const& package(size_t index) const {
+ if (index < packages_.size()) {
+ return packages_[index];
+ }
+ assert(false);
+ return EMPTY;
+ }
+
+ void clear() {
+ auto last = packages_.size();
+ epoch_.tv_sec = 0;
+ epoch_.tv_nsec = 0;
+ if (last == 0) return;
+ packages_.clear();
+ open_.clear();
+ notify_removed(0, last - 1);
+ }
+
+private:
+ static const Package EMPTY;
+
+ static void format_size(std::string* out, size_t size, bool done) {
+ char tmp[50];
+ auto len = snprintf(tmp, sizeof(tmp), "%llu bytes", static_cast<unsigned long long>(size));
+ out->assign(tmp, len);
+ if (!done) out->append(" ...", 4);
+ }
+
+ static void format_host_port(std::string* out, std::string const& host, uint16_t port) {
+ out->assign(host);
+ out->push_back(':');
+ char tmp[10];
+ auto len = snprintf(tmp, sizeof(tmp), "%u", static_cast<unsigned>(port));
+ out->append(tmp, len);
+ }
+
+ void format_timestamp(std::string* out, struct timespec const& time) {
+ if (epoch_.tv_sec == 0 && epoch_.tv_nsec == 0) {
+ epoch_ = time;
+ }
+ auto s = time.tv_sec - epoch_.tv_sec;
+ auto n = time.tv_nsec - epoch_.tv_nsec;
+ if (n < 0) {
+ --s;
+ n += 1000000000ull;
+ }
+ char tmp[50];
+ auto len = snprintf(tmp, sizeof(tmp), "%ld.%09lu", static_cast<long>(s), static_cast<unsigned long>(n));
+ out->assign(tmp, len);
+ }
+
+ void notify_added(size_t first, size_t last) {
+ auto it = listeners_.notify();
+ while (it.has_next()) {
+ it.next()->rows_added(this, first, last);
+ }
+ }
+
+ void notify_changed(size_t first, size_t last) {
+ auto it = listeners_.notify();
+ while (it.has_next()) {
+ it.next()->rows_changed(this, first, last);
+ }
+ }
+
+ void notify_removed(size_t first, size_t last) {
+ auto it = listeners_.notify();
+ while (it.has_next()) {
+ it.next()->rows_removed(this, first, last);
+ }
+ }
+
+ std::vector<Package> packages_;
+ std::unordered_map<uint32_t, size_t> open_;
+ Observers<Listener*> listeners_;
+ struct timespec epoch_;
+
+ static const std::string empty_;
+};
+
+// static
+const std::string PackageList::empty_;
+
+class MonitorGui : GuiMenu::Listener, GuiMain::Listener, Monitor::Delegate {
+private:
+ class ConnectFormListener : public GuiFormApply::Listener {
+ public:
+ bool about_to_close(GuiForm* form) override {
+ auto address = form->get_string("address");
+ if (address.empty()) {
+ form->set_error("Empty address");
+ return false;
+ }
+ if (!parse_address(address, nullptr, nullptr)) {
+ form->set_error("Invalid address, expects HOST[:PORT]");
+ return false;
+ }
+ return true;
+ }
+ };
+
+ class ConnectFormDelegate : public GuiFormApply::Delegate {
+ public:
+ ConnectFormDelegate(Monitor* monitor)
+ : monitor_(monitor) {
+ }
+
+ void apply(GuiFormApply* form) override {
+ std::string host;
+ uint16_t port;
+ if (!parse_address(form->get_string("address"), &host, &port)) {
+ form->set_error("Invalid address, expects HOST[:PORT]");
+ form->applied(false);
+ return;
+ }
+ monitor_->connect(host, port);
+ }
+
+ private:
+ Monitor* monitor_;
+ };
+
+public:
+ MonitorGui()
+ : packages_(new PackageList()),
+ main_(GuiMain::create("TransparentProxy Monitor", 800, 400)),
+ menu_(GuiMenu::create()),
+ statusbar_(GuiStatusBar::create()),
+ looper_(main_->createLooper()),
+ has_selection_(false),
+ selection_(0) {
+ auto file = menu_->add_menu("File");
+ file->add_item(ACTION_CONNECT, "Connect...");
+ file->add_item(ACTION_DISCONNECT, "Disconnect");
+ file->add_separator();
+ file->add_item(ACTION_EXIT, "Exit");
+ auto edit = menu_->add_menu("Edit");
+ edit->add_item(ACTION_COPY_TEXT, "Copy");
+ edit->add_item(ACTION_COPY_RAW, "Copy binary");
+ edit->add_separator();
+ edit->add_item(ACTION_CLEAR, "Clear");
+ auto help = menu_->add_menu("Help");
+ help->add_item(ACTION_ABOUT, "About...");
+ main_->set_menu(menu_.get());
+ main_->set_statusbar(statusbar_.get());
+ main_->set_split(0.7);
+
+ main_->set_listmodel(packages_.get());
+
+ statusbar_->set_status("Not connected");
+ menu_->enable_item(ACTION_DISCONNECT, false);
+
+ menu_->enable_item(ACTION_COPY_RAW, false);
+ menu_->enable_item(ACTION_COPY_TEXT, false);
+
+ menu_->add_listener(this);
+ main_->add_listener(this);
+ }
+
+ bool run(int argc, char** argv) {
+ return main_->run(argc, argv);
+ }
+
+ // GuiMenu::Listener
+ void item_activated(const std::string& id) override {
+ if (id == ACTION_EXIT) {
+ main_->exit();
+ } else if (id == ACTION_ABOUT) {
+ if (!about_) {
+ about_.reset(GuiAbout::create("TransparentProxy Monitor",
+ VERSION,
+ "Joel Klinghed", "the_jk@yahoo.com",
+ nullptr));
+ }
+ main_->show(about_.get());
+ } else if (id == ACTION_CONNECT) {
+ setup_monitor();
+ auto dlg = std::unique_ptr<GuiFormApply::Delegate>(
+ new ConnectFormDelegate(monitor_.get()));
+ auto lst = std::unique_ptr<GuiFormApply::Listener>(
+ new ConnectFormListener());
+ connect_.reset(
+ GuiFormApply::create("Connect...",
+ "Enter address for monitor to connect to",
+ "Connect",
+ dlg.get()));
+ connect_->add_string("address", "Address", "");
+ connect_->add_listener(lst.get());
+ if (connect_->show(main_.get())) {
+ monitor_->attach();
+ } else {
+ monitor_->disconnect();
+ }
+ connect_.reset();
+ } else if (id == ACTION_DISCONNECT) {
+ assert(monitor_);
+ if (monitor_) {
+ monitor_->disconnect();
+ }
+ } else if (id == ACTION_COPY_TEXT) {
+ if (!has_selection_) {
+ assert(false);
+ return;
+ }
+ auto& pkg = packages_->package(selection_);
+ std::unique_ptr<AttributedText> text(AttributedText::create());
+ HexDump::write(text.get(), HexDump::ADDRESS | HexDump::CHARS, pkg.data);
+ std::string str;
+ str.append("From: ").append(pkg.from).push_back('\n');
+ str.append("To : ").append(pkg.to).push_back('\n');
+ str.append("At : ").append(pkg.timestamp).push_back('\n');
+ str.append(text->text());
+ main_->add_to_clipboard(str);
+ } else if (id == ACTION_COPY_RAW) {
+ if (!has_selection_) {
+ assert(false);
+ return;
+ }
+ auto& pkg = packages_->package(selection_);
+ main_->add_to_clipboard(pkg.data, "application/octet-stream");
+ } else if (id == ACTION_CLEAR) {
+ packages_->clear();
+ } else {
+ assert(false);
+ }
+ }
+
+ // GuiMain::Listener
+ bool about_to_exit(GuiMain* main) override {
+ assert(main_.get() == main);
+ return true;
+ }
+
+ void selected_row(GuiMain* main, size_t index) override {
+ assert(main_.get() == main);
+ if (has_selection_ && selection_ == index) return;
+ has_selection_ = true;
+ selection_ = index;
+
+ auto& pkg = packages_->package(index);
+ std::unique_ptr<AttributedText> text(AttributedText::create());
+ HexDump::write(text.get(), HexDump::ADDRESS | HexDump::CHARS, pkg.data);
+ main_->set_package(std::move(text));
+
+ menu_->enable_item(ACTION_COPY_RAW, true);
+ menu_->enable_item(ACTION_COPY_TEXT, true);
+ }
+
+ void lost_selection(GuiMain* main) override {
+ assert(main_.get() == main);
+ has_selection_ = false;
+ main_->set_package(nullptr);
+
+ menu_->enable_item(ACTION_COPY_RAW, false);
+ menu_->enable_item(ACTION_COPY_TEXT, false);
+ }
+
+ // Monitor::Delegate
+ void state(Monitor* monitor, Monitor::State state) override {
+ assert(monitor == monitor_.get());
+ if (connect_) {
+ switch (state) {
+ case Monitor::DISCONNECTED:
+ connect_->set_error("Unable to connect, is the proxy running"
+ " and listening for monitors?");
+ connect_->applied(false);
+ break;
+ case Monitor::CONNECTED:
+ connect_->applied(true);
+ break;
+ case Monitor::CONNECTING:
+ case Monitor::ATTACHED:
+ break;
+ }
+ }
+ bool allow_connect, allow_disconnect;
+ switch (state) {
+ case Monitor::DISCONNECTED:
+ statusbar_->set_status("Not connected");
+ allow_connect = true;
+ allow_disconnect = false;
+ break;
+ case Monitor::CONNECTED:
+ statusbar_->set_status("Connected");
+ allow_connect = false;
+ allow_disconnect = true;
+ break;
+ case Monitor::CONNECTING:
+ statusbar_->set_status("Connecting...");
+ allow_connect = false;
+ allow_disconnect = false;
+ break;
+ case Monitor::ATTACHED:
+ statusbar_->set_status("Connected and attached");
+ allow_connect = false;
+ allow_disconnect = true;
+ break;
+ }
+ menu_->enable_item(ACTION_CONNECT, allow_connect);
+ menu_->enable_item(ACTION_DISCONNECT, allow_disconnect);
+ }
+
+ void error(Monitor* monitor, std::string const& UNUSED(error)) override {
+ assert(monitor == monitor_.get());
+ }
+
+ void package(Monitor* monitor, Monitor::Package const& package) override {
+ assert(monitor == monitor_.get());
+ packages_->package(package);
+ }
+
+ void package_data(Monitor* monitor, uint32_t id,
+ char const* data, size_t size, bool last) override {
+ assert(monitor == monitor_.get());
+ auto index = packages_->package_data(id, data, size, last);
+ if (has_selection_ && index == selection_) {
+ selected_row(nullptr, index);
+ }
+ }
+
+private:
+ void setup_monitor() {
+ if (!resolver_) {
+ resolver_.reset(Resolver::create(looper_.get()));
+ }
+ if (!monitor_) {
+ monitor_.reset(Monitor::create(looper_.get(), resolver_.get(), this));
+ }
+ }
+
+ std::unique_ptr<PackageList> packages_;
+ std::unique_ptr<GuiMain> main_;
+ std::unique_ptr<GuiMenu> menu_;
+ std::unique_ptr<GuiStatusBar> statusbar_;
+ std::unique_ptr<GuiAbout> about_;
+ std::unique_ptr<GuiFormApply> connect_;
+ std::unique_ptr<Looper> looper_;
+ std::unique_ptr<Resolver> resolver_;
+ std::unique_ptr<Monitor> monitor_;
+ bool has_selection_;
+ size_t selection_;
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ auto gui = std::unique_ptr<MonitorGui>(new MonitorGui());
+ return gui->run(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE;
+}