/* libobby - Network text editing library * Copyright (C) 2005-2006 0x539 dev group * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY 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, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include "zeroconf.hpp" namespace obby { class zeroconf_avahi : public zeroconf_base { public: zeroconf_avahi(); /** Uses a custom avahi poll. Note that select() does not work with * a custom poll. */ zeroconf_avahi(const AvahiPoll* poll); ~zeroconf_avahi(); virtual void publish(const std::string& name, unsigned int port); virtual void unpublish(const std::string& name); virtual void unpublish_all(); virtual void discover(); virtual void select(); virtual void select(unsigned int msecs); private: AvahiClient* m_client; AvahiSimplePoll* m_simple_poll; AvahiServiceBrowser* m_sb; AvahiEntryGroup* m_group; static void avahi_client_callback(AvahiClient* client, AvahiClientState state, void* userdata); static void avahi_browse_callback(AvahiServiceBrowser* sb, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name, const char* type, const char* domain, AvahiLookupResultFlags flags, void* userdata); static void avahi_resolve_callback(AvahiServiceResolver* r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* hostname, const AvahiAddress* addr, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags, void* userdata); static void avahi_entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, void* userdata); }; namespace avahi_select { class fd_socket: public net6::socket { public: fd_socket(int fd): net6::socket(fd) {} ~fd_socket() { invalidate(); } }; class timeout_socket: public net6::socket { public: timeout_socket(): net6::socket(-1) {} }; template class poll { private: struct watch { watch(poll* poll, int fd): socket_(fd), poll_(poll) {} fd_socket socket_; AvahiWatchEvent occured_event_; AvahiWatchCallback callback_; void* userdata_; poll* poll_; }; struct timeout { timeout(poll* poll): poll_(poll) {} timeout_socket socket_; poll* poll_; }; static net6::io_condition avahi_to_obby(AvahiWatchEvent event) { net6::io_condition cond = net6::IO_NONE; if(event & AVAHI_WATCH_IN) cond |= net6::IO_INCOMING; if(event & AVAHI_WATCH_OUT) cond |= net6::IO_OUTGOING; if(event & (AVAHI_WATCH_ERR | AVAHI_WATCH_HUP)) cond |= net6::IO_ERROR; return cond; } static AvahiWatchEvent obby_to_avahi(net6::io_condition event) { AvahiWatchEvent cond = static_cast(0); if(event & net6::IO_INCOMING) cond |= AVAHI_WATCH_IN; if(event & net6::IO_OUTGOING) cond |= AVAHI_WATCH_OUT; if(event & net6::IO_ERROR) cond |= AVAHI_WATCH_ERR; return cond; } static AvahiWatch* watch_new_s(const AvahiPoll* api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void* userdata) { return reinterpret_cast(static_cast(api->userdata)->watch_new(fd, event, callback, userdata)); } static void watch_update_s(AvahiWatch* w, AvahiWatchEvent event) { reinterpret_cast(w)->poll_->watch_update(reinterpret_cast(w), event); } static AvahiWatchEvent watch_get_events_s(AvahiWatch* w) { return reinterpret_cast(w)->poll_->watch_get_events(reinterpret_cast(w)); } static void watch_free_s(AvahiWatch* w) { reinterpret_cast(w)->poll_->watch_free(reinterpret_cast(w)); } static AvahiTimeout* timeout_new_s(const AvahiPoll* api, const struct timeval* tv, AvahiTimeoutCallback callback, void* userdata) { return reinterpret_cast(static_cast(api->userdata)->timeout_new(tv, callback, userdata)); } static void timeout_update_s(AvahiTimeout* t, const struct timeval* tv) { reinterpret_cast(t)->poll_->timeout_update(reinterpret_cast(t), tv); } static void timeout_free_s(AvahiTimeout* t) { reinterpret_cast(t)->poll_->timeout_free(reinterpret_cast(t)); } void on_io(net6::io_condition condition, watch* w) { w->occured_event = obby_to_avahi(condition); w->callback_(reinterpret_cast(w), w->socket_.cobj(), w->occured_event, w->userdata); } void on_timeout(net6::io_condition condition, timeout* t) { if(condition != net6::IO_TIMEOUT) throw std::logic_error("on_timeout: condition != TIMEOUT"); t->callback_(reinterpret_cast(t), t->userdata); } watch* watch_new(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void* userdata) { if(m_watches.find(fd) != m_watches.end()) throw std::logic_error("watch_new: FD is already watched"); std::auto_ptr w(new watch(fd)); w->occured_event_ = static_cast(0); w->callback_ = callback; w->userdata_ = userdata; w->socket_.signal_io().connect(sigc::bind(sigc::mem_fun(*this, &poll::on_io), w)); m_selector.set(w->fd_socket_, avahi_to_obby(event)); m_watches[fd] = w.get(); return w.release(); } void watch_update(watch* w, AvahiWatchEvent event) { if(m_watches.find(w->fd_socket_.cobj()) == m_watches.end()) throw std::logic_error("watch_update: FD is not watched"); m_selector.set(w->fd_socket_, avahi_to_obby(event)); } AvahiWatchEvent watch_get_events(watch* w) { return w->occured_event_; } void watch_free(watch* w) { m_watches.erase(w->socket_.cobj()); delete w; } timeout* timeout_new(const timeval* tv, AvahiTimeoutCallback callback, void* userdata) { AvahiUsec sec = avahi_age(tv); if(sec > 0) sec = 0; sec = -sec/1000; std::auto_ptr t(new timeout(this)); t->callback_ = callback; t->userdata_ = userdata; t->socket_.signal_io().connect(sigc::bind(sigc::mem_fun(*this, &poll::on_timeout), t)); m_selector.set(t->socket_, net6::IO_TIMEOUT); m_selector.set_timeout(t->socket_, sec); m_timeouts.insert(t); return reinterpret_cast(t.release()); } void timeout_update(timeout* t, const struct timeval* tv) { AvahiUsec sec = avahi_age(tv); if(sec > 0) sec = 0; sec = -sec/1000; m_selector.set(t->socket_, net6::IO_TIMEOUT); m_selector.set_timeout(t->socket_, sec); } void timeout_free(timeout* t) { m_timeouts.erase(t); delete t; } public: poll(Selector& selector): m_selector(selector) { m_poll.userdata = this; m_poll.watch_new = &poll::watch_new_s; m_poll.watch_update = &poll::watch_update_s; m_poll.watch_get_events = &poll::watch_get_events_s; m_poll.watch_free = &poll::watch_free_s; m_poll.timeout_new = &poll::timeout_new_s; m_poll.timeout_update = &poll::timeout_update_s; m_poll.timeout_free = &poll::timeout_free_s; } ~poll() { if(!m_watches.empty()) std::cerr << "~avahi_select::poll: Watches not empty!" << std::endl; if(!m_timeouts.empty()) std::cerr << "~avahi_select::poll: Timeouts not empty!" << std::endl; } AvahiPoll* get_poll() { return &m_poll; } protected: AvahiPoll m_poll; Selector m_selector; std::map m_watches; std::set m_timeouts; }; } }