From 362884fc52b48c862680b3bf1177b5451422a583 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 15 Aug 2014 17:02:02 -0400 Subject: [PATCH] Import GNTP notification code --- CMakeLists.txt | 1 + include/fc/network/gntp.hpp | 55 ++++++++ src/network/gntp.cpp | 274 ++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 include/fc/network/gntp.hpp create mode 100644 src/network/gntp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f7e9c7..b37c4df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,7 @@ set( fc_sources src/network/rate_limiting.cpp src/network/resolve.cpp src/network/url.cpp + src/network/gntp.cpp src/compress/smaz.cpp src/compress/lzma.cpp vendor/cyoencode-1.0.2/src/CyoDecode.c diff --git a/include/fc/network/gntp.hpp b/include/fc/network/gntp.hpp new file mode 100644 index 0000000..f512a2e --- /dev/null +++ b/include/fc/network/gntp.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +#include +#include + +namespace fc +{ + namespace detail { + class gntp_icon_impl; + } + class gntp_notifier; + + class gntp_icon { + public: + gntp_icon(const char* buffer, size_t length); + ~gntp_icon(); + private: + std::unique_ptr my; + friend class gntp_notifier; + }; + typedef std::shared_ptr gntp_icon_ptr; + + class gntp_notification_type { + public: + std::string name; + std::string display_name; + bool enabled; + gntp_icon_ptr icon; + }; + typedef std::vector gntp_notification_type_list; + + namespace detail { + class gntp_notifier_impl; + } + + typedef uint160_t gntp_guid; + + class gntp_notifier { + public: + gntp_notifier(); + ~gntp_notifier(); + void set_application_name(std::string application_name); + void set_application_icon(const gntp_icon_ptr& icon); + void register_notifications(); + gntp_guid send_notification(std::string name, std::string title, std::string text, const gntp_icon_ptr& icon = gntp_icon_ptr(), optional coalescingId = optional()); + void add_notification_type(const gntp_notification_type& notificationType); + private: + std::unique_ptr my; + }; + + +} // namespace fc diff --git a/src/network/gntp.cpp b/src/network/gntp.cpp new file mode 100644 index 0000000..f6d83fa --- /dev/null +++ b/src/network/gntp.cpp @@ -0,0 +1,274 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace fc +{ + namespace detail + { + static std::string calc_sha1_base32_of_buffer(const std::string& buffer) + { + sha1::encoder sha1_encoder; + sha1_encoder.write(buffer.c_str(), buffer.size()); + sha1 sha1_result = sha1_encoder.result(); + string sha1_result_base32 = to_base32((char*)&sha1_result, sizeof(sha1_result)); + return sha1_result_base32.c_str(); + } + + + class gntp_icon_impl + { + public: + std::string _icon_bytes; + std::string _sha1_hash; + + gntp_icon_impl(const char* buffer, size_t length) : + _icon_bytes(buffer, length), + _sha1_hash(calc_sha1_base32_of_buffer(_icon_bytes)) + { + } + }; + + class gntp_notifier_impl + { + public: + gntp_notifier_impl(); + + // there's no API to change these right now, it will always notify localhost at the default GNTP port + std::string hostname; + uint16_t port; + + std::string application_name; + gntp_icon_ptr application_icon; + + gntp_notification_type_list notification_types; // list of all notification types we're registered to send + + optional endpoint; // cache the last endpoint we've connected to + + bool connection_failed; // true after we've tried to connect and failed + bool is_registered; // true after we've registered + + void send_gntp_message(const std::string& message); + }; + + gntp_notifier_impl::gntp_notifier_impl() : + hostname("127.0.0.1"), + port(23053), + connection_failed(false), + is_registered(false) + { + } + + void gntp_notifier_impl::send_gntp_message(const std::string& message) + { + std::shared_ptr sock(new boost::asio::ip::tcp::socket(asio::default_io_service())); + + bool connected = false; + if (endpoint) + { + // we've successfully connected before, connect to the same endpoint that worked last time + try + { + asio::tcp::connect(*sock, *endpoint); + connected = true; + } + catch (exception& er) + { + ilog("Failed to connect to GNTP service using an endpoint that previously worked: ${error_report}", + ("error_report", er.to_detail_string())); + sock->close(); + // clear the cached endpoint and fall through to the full connection procedure + endpoint = optional(); + } + catch (...) + { + ilog("Failed to connect to GNTP service using an endpoint that previously worked"); + sock->close(); + // clear the cached endpoint and fall through to the full connection procedure + endpoint = optional(); + } + } + if (!connected) + { + // do the full connection procedure + auto eps = asio::tcp::resolve(hostname, boost::lexical_cast(port)); + if (eps.size() == 0) + FC_THROW("Unable to resolve host '${host}'", ("host", hostname)); + + for (uint32_t i = 0; i < eps.size(); ++i) + { + try + { + boost::system::error_code ec; + ilog("Attempting to connect to GNTP srvice"); + asio::tcp::connect(*sock, eps[i]); + endpoint = eps[i]; + connected = true; + break; + } + catch (const exception& er) + { + ilog("Failed to connect to GNTP service: ${error_reprot}", + ("error_report", er.to_detail_string()) ); + sock->close(); + } + catch (...) + { + ilog("Failed to connect to GNTP service"); + sock->close(); + } + } + } + if (!connected) + FC_THROW("Unable to connect to any resolved endpoint for ${host}:${port}", + ("host", hostname)("port", port)); + try + { + asio::ostream write_stream(sock); + write_stream.write(message.c_str(), message.size()); + write_stream.flush(); + write_stream.close(); + } + catch (exception& er) + { + FC_RETHROW_EXCEPTION(er, warn, "Caught an exception while sending data to GNTP service"); + } + catch (...) + { + FC_THROW("Caught an exception while sending data to GNTP service"); + } + } + } // end namespace detail + + gntp_icon::gntp_icon(const char* buffer, size_t length) : + my(new detail::gntp_icon_impl(buffer, length)) + { + } + gntp_icon::~gntp_icon() + { + } + + gntp_notifier::gntp_notifier() : + my(new detail::gntp_notifier_impl) + { + } + + gntp_notifier::~gntp_notifier() + { + } + + + void gntp_notifier::set_application_name(std::string appName) + { + my->application_name = appName; + } + void gntp_notifier::set_application_icon(const gntp_icon_ptr& icon) + { + my->application_icon = icon; + } + void gntp_notifier::add_notification_type(const gntp_notification_type& notification_type) + { + my->notification_types.push_back(notification_type); + } + + void gntp_notifier::register_notifications() + { + // this call will reset any errors + my->connection_failed = false; + my->is_registered = false; + + std::ostringstream message; + std::set icons_used; + + message << "GNTP/1.0 REGISTER NONE\r\n"; + message << "Application-Name: " << my->application_name << "\r\n"; + if (my->application_icon) + { + message << "Application-Icon: x-growl-resource://" << my->application_icon->my->_sha1_hash << "\r\n"; + icons_used.insert(my->application_icon); + } + + message << "Notifications-Count: " << my->notification_types.size() << "\r\n"; + for (const gntp_notification_type& notification_type : my->notification_types) + { + message << "\r\n"; + message << "Notification-Name: " << notification_type.name << "\r\n"; + if (!notification_type.display_name.empty()) + message << "Notification-Display-Name: " << notification_type.display_name << "\r\n"; + if (notification_type.icon) + { + message << "Notification-Icon: x-growl-resource://" << notification_type.icon->my->_sha1_hash << "\r\n"; + icons_used.insert(notification_type.icon); + } + message << "Notification-Enabled: " << (notification_type.enabled ? "True" : "False") << "\r\n"; + } + if (!icons_used.empty()) + { + message << "\r\n"; + for (const gntp_icon_ptr& icon : icons_used) + { + message << "Identifier: " << icon->my->_sha1_hash << "\r\n"; + message << "Length: " << icon->my->_icon_bytes.size() << "\r\n"; + message << "\r\n"; + message << icon->my->_icon_bytes; + message << "\r\n"; + } + } + + message << "\r\n\r\n"; + try + { + my->send_gntp_message(message.str()); + my->is_registered = true; + } + catch (const exception&) + { + my->connection_failed = true; + } + } + gntp_guid gntp_notifier::send_notification(std::string name, std::string title, std::string text, + const gntp_icon_ptr& icon, optional coalescingId /* = optional() */) + { + if (my->connection_failed) + return gntp_guid(); + if (!my->is_registered) + return gntp_guid(); + + gntp_guid notification_id; + rand_pseudo_bytes(notification_id.data(), 20); + + std::ostringstream message; + message << "GNTP/1.0 NOTIFY NONE\r\n"; + message << "Application-Name: " << my->application_name << "\r\n"; + message << "Notification-Name: " << name << "\r\n"; + message << "Notification-ID: " << notification_id.str() << "\r\n"; + message << "Notification-Coalescing-ID: " << (coalescingId ? coalescingId->str() : notification_id.str()) << "\r\n"; + message << "Notification-Title: " << title << "\r\n"; + message << "Notification-Text: " << text << "\r\n"; + if (icon) + message << "Notification-Icon: x-growl-resource://" << icon->my->_sha1_hash << "\r\n"; + + if (icon) + { + message << "\r\n"; + message << "Identifier: " << icon->my->_sha1_hash << "\r\n"; + message << "Length: " << icon->my->_icon_bytes.size() << "\r\n"; + message << "\r\n"; + message << icon->my->_icon_bytes; + message << "\r\n"; + } + message << "\r\n\r\n"; + my->send_gntp_message(message.str()); + return notification_id; + } + +} // namespace fc \ No newline at end of file