diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 3934a4d..aca55aa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -287,8 +287,8 @@ set(SLIC3R_GUI_SOURCES Utils/PrintHost.hpp Utils/Bonjour.cpp Utils/Bonjour.hpp - Utils/UdpLinkServer.cpp - Utils/UdpLinkServer.hpp + Utils/Udp.cpp + Utils/Udp.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp Utils/Process.cpp diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 65cf7af..b4a84e1 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -1,5 +1,5 @@ #include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers - +#include "slic3r/Utils/Udp.hpp" #include "BonjourDialog.hpp" #include @@ -19,90 +19,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Bonjour.hpp" - -// B29 -#include "slic3r/Utils/UdpLinkServer.hpp" -#include -#include - -const int MAX_BUF_LEN = 255; -#define GET_HOST_COMMAND "mkswifi\r\n" -#define CLIENT_PORT 11121 -#define SERVER_PORT 8989 - -// B29 - -typedef struct tagDeviceInfo -{ - unsigned short usFunction; - unsigned short usVersionFlag; - unsigned int uiCompanyId; - char szDeviceSerialNo[24]; - unsigned short usServicePort; - char szExtend[38]; -} DeviceInfo; - -// B29 - -typedef struct tagWorkThreadParameter -{ - boost::asio::io_service *pIoService; - UdpLinkServer * pUdpService; -} WorkThreadParameter; - -bool g_WorkThreadExit = false; -int g_nBroastDataSendInteral = 3000; -DeviceInfo g_diDeviceInfo = {0}; -std::vector get_reply; -// B29 - -unsigned int __stdcall WorkThreadFunByDeviceServiceProcess(PVOID pParam) -{ - int nn = 0; - int nDataSize = sizeof(DeviceInfo); - WorkThreadParameter *pAllParameter = (WorkThreadParameter *) pParam; - while (true) { - if (g_WorkThreadExit) { - break; - } - - pAllParameter->pUdpService->SendData((char *) GET_HOST_COMMAND, nDataSize, true); - pAllParameter->pIoService->poll(); - // break; - for (nn = g_nBroastDataSendInteral; nn > 0; nn -= 200) { - if (g_WorkThreadExit) { - break; - } - Sleep(200); - } - } - return 0; -} -// B29 - -static void WINAPI BroastDeviceInfoRecvDataCallback(const boost::system::error_code &error, - char * pData, - int nDataLength, - char * pPeerIp, - unsigned short usPeerPort, - DWORD dwUserData1, - DWORD dwUserData2) -{ - SYSTEMTIME sm; - GetLocalTime(&sm); - char szInfo[256] = {0}; - DeviceInfo *pDeviceInfo = (DeviceInfo *) pData; - //sprintf(szInfo, "%s %s:%d time:%04d-%02d-%0d %02d:%02d:%02d\n", pData, pPeerIp, usPeerPort, sm.wYear, sm.wMonth, sm.wDay, sm.wHour, - // sm.wMinute, sm.wSecond); - //printf(szInfo); - int i = 0; - for (i = 0; i < get_reply.size(); i++) - if (get_reply[i] == pData) - break; - if (i == get_reply.size()) - get_reply.push_back(pData); -} - +#include "slic3r/Utils/Udp.hpp" namespace Slic3r { @@ -123,13 +40,29 @@ public: return new BonjourReplyEvent(*this); } }; +// B29 +class UdpReplyEvent : public wxEvent +{ +public: + UdpReply reply; + + UdpReplyEvent(wxEventType eventType, int winid, UdpReply &&reply) : wxEvent(winid, eventType), reply(std::move(reply)) {} + + virtual wxEvent *Clone() const { return new UdpReplyEvent(*this); } +}; +// B29 +wxDEFINE_EVENT(EVT_UDP_REPLY, UdpReplyEvent); wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent); +wxDECLARE_EVENT(EVT_UDP_COMPLETE, wxCommandEvent); +wxDEFINE_EVENT(EVT_UDP_COMPLETE, wxCommandEvent); + wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent); wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent); class ReplySet: public std::set {}; +class UdpReplySet : public std::set {}; struct LifetimeGuard { @@ -142,7 +75,8 @@ struct LifetimeGuard BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) , list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER)) - , replies(new ReplySet) + , replies(new ReplySet) + , udp_replies(new UdpReplySet) , label(new wxStaticText(this, wxID_ANY, "")) , timer(new wxTimer()) , timer_state(0) @@ -177,14 +111,11 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this); + Bind(EVT_UDP_REPLY, &BonjourDialog::on_udp_reply, this); + // B29 Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) { this->timer_state = 0; - g_WorkThreadExit = true; - for (int n = 0; n < get_reply.size(); n++) { - auto item = list->InsertItem(0, get_reply[n].substr(get_reply[n].find_last_of(",") + 1)); - list->SetItem(item, 1, get_reply[n].substr(get_reply[n].find("mkswifi:") + 8, get_reply[n].find(",") - 8)); - } }); Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this); @@ -206,21 +137,6 @@ bool BonjourDialog::show_and_lookup() timer->Start(1000); on_timer_process(); - // B29 - g_WorkThreadExit = false; - boost::asio::io_service ioService; - UdpLinkServer usUdpService(8989, true); - usUdpService.SetRecvDataCallback(true, BroastDeviceInfoRecvDataCallback, 0, 0); - usUdpService.Start(ioService); - g_diDeviceInfo.usFunction = 1; - g_diDeviceInfo.usVersionFlag = 0x0001; - strcpy(g_diDeviceInfo.szDeviceSerialNo, "ABCDEFG111111111"); - g_diDeviceInfo.usServicePort = 8989; - - WorkThreadParameter wtpWorkThreadParameter; - wtpWorkThreadParameter.pIoService = &ioService; - wtpWorkThreadParameter.pUdpService = &usUdpService; - boost::thread thrd(WorkThreadFunByDeviceServiceProcess, &wtpWorkThreadParameter); // The background thread needs to queue messages for this dialog // and for that it needs a valid pointer to it (mandated by the wxWidgets API). @@ -228,8 +144,34 @@ bool BonjourDialog::show_and_lookup() // so that both threads can access it safely. auto dguard = std::make_shared(this); + // B29 + Udp::TxtKeys udp_txt_keys{"version", "model"}; + + udp = Udp("octoprint") + .set_txt_keys(std::move(udp_txt_keys)) + .set_retries(3) + .set_timeout(4) + .on_udp_reply([dguard](UdpReply &&reply) { + std::lock_guard lock_guard(dguard->mutex); + auto dialog = dguard->dialog; + if (dialog != nullptr) { + auto evt = new UdpReplyEvent(EVT_UDP_REPLY, dialog->GetId(), std::move(reply)); + wxQueueEvent(dialog, evt); + } + }) + .on_complete([dguard]() { + std::lock_guard lock_guard(dguard->mutex); + auto dialog = dguard->dialog; + if (dialog != nullptr) { + auto evt = new wxCommandEvent(EVT_UDP_COMPLETE, dialog->GetId()); + wxQueueEvent(dialog, evt); + } + }) + .lookup(); + + // Note: More can be done here when we support discovery of hosts other than Octoprint and SL1 - Bonjour::TxtKeys txt_keys { "version", "model" }; + Bonjour::TxtKeys txt_keys{"version", "model"}; bonjour = Bonjour("octoprint") .set_txt_keys(std::move(txt_keys)) @@ -273,56 +215,110 @@ wxString BonjourDialog::get_selected() const void BonjourDialog::on_reply(BonjourReplyEvent &e) { - if (replies->find(e.reply) != replies->end()) { - // We already have this reply - return; - } + if (replies->find(e.reply) != replies->end()) { + // We already have this reply + return; + } - // Filter replies based on selected technology - const auto model = e.reply.txt_data.find("model"); - const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; - if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { - return; - } + // Filter replies based on selected technology + const auto model = e.reply.txt_data.find("model"); + const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; + if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { + return; + } - replies->insert(std::move(e.reply)); + replies->insert(std::move(e.reply)); - auto selected = get_selected(); + auto selected = get_selected(); - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; + wxWindowUpdateLocker freeze_guard(this); + (void) freeze_guard; - list->DeleteAllItems(); + list->DeleteAllItems(); - // The whole list is recreated so that we benefit from it already being sorted in the set. - // (And also because wxListView's sorting API is bananas.) - for (const auto &reply : *replies) { - auto item = list->InsertItem(0, reply.full_address); - list->SetItem(item, 1, reply.hostname); - list->SetItem(item, 2, reply.service_name); + // The whole list is recreated so that we benefit from it already being sorted in the set. + // (And also because wxListView's sorting API is bananas.) + for (const auto &reply : *replies) { + auto item = list->InsertItem(0, reply.full_address); + list->SetItem(item, 1, reply.hostname); + list->SetItem(item, 2, reply.service_name); - if (tech == ptFFF) { - const auto it = reply.txt_data.find("version"); - if (it != reply.txt_data.end()) { - list->SetItem(item, 3, GUI::from_u8(it->second)); - } - } - } + if (tech == ptFFF) { + const auto it = reply.txt_data.find("version"); + if (it != reply.txt_data.end()) { + list->SetItem(item, 3, GUI::from_u8(it->second)); + } + } + } - const int em = GUI::wxGetApp().em_unit(); + const int em = GUI::wxGetApp().em_unit(); - for (int i = 0; i < list->GetColumnCount(); i++) { - list->SetColumnWidth(i, wxLIST_AUTOSIZE); - if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); } - } + for (int i = 0; i < list->GetColumnCount(); i++) { + list->SetColumnWidth(i, wxLIST_AUTOSIZE); + if (list->GetColumnWidth(i) < 10 * em) { + list->SetColumnWidth(i, 10 * em); + } + } - if (!selected.IsEmpty()) { - // Attempt to preserve selection - auto hit = list->FindItem(-1, selected); - if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } - } + if (!selected.IsEmpty()) { + // Attempt to preserve selection + auto hit = list->FindItem(-1, selected); + if (hit >= 0) { + list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + } } +// B29 +void BonjourDialog::on_udp_reply(UdpReplyEvent &e) +{ + if (udp_replies->find(e.reply) != udp_replies->end()) { + // We already have this reply + return; + } + + //// Filter replies based on selected technology + // const auto model = e.reply.txt_data.find("model"); + // const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; + // if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { + // return; + //} + + udp_replies->insert(std::move(e.reply)); + + auto selected = get_selected(); + + wxWindowUpdateLocker freeze_guard(this); + (void) freeze_guard; + + list->DeleteAllItems(); + + // The whole list is recreated so that we benefit from it already being sorted in the set. + // (And also because wxListView's sorting API is bananas.) + for (const auto &reply : *udp_replies) { + auto item = list->InsertItem(0, reply.service_name); + list->SetItem(item, 1, reply.hostname); + } + + const int em = GUI::wxGetApp().em_unit(); + + for (int i = 0; i < list->GetColumnCount(); i++) { + list->SetColumnWidth(i, wxLIST_AUTOSIZE); + if (list->GetColumnWidth(i) < 10 * em) { + list->SetColumnWidth(i, 10 * em); + } + } + + if (!selected.IsEmpty()) { + // Attempt to preserve selection + auto hit = list->FindItem(-1, selected); + if (hit >= 0) { + list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + } +} + + void BonjourDialog::on_timer(wxTimerEvent &) { on_timer_process(); diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp index 8bfc076..8b8fb7b 100644 --- a/src/slic3r/GUI/BonjourDialog.hpp +++ b/src/slic3r/GUI/BonjourDialog.hpp @@ -20,9 +20,14 @@ class address; namespace Slic3r { class Bonjour; +//B29 +class Udp; class BonjourReplyEvent; +//B29 +class UdpReplyEvent; class ReplySet; - +//B29 +class UdpReplySet; class BonjourDialog: public wxDialog { @@ -39,13 +44,19 @@ public: private: wxListView *list; std::unique_ptr replies; + //B29 + std::unique_ptr udp_replies; wxStaticText *label; std::shared_ptr bonjour; + //B29 + std::shared_ptr udp; std::unique_ptr timer; unsigned timer_state; Slic3r::PrinterTechnology tech; virtual void on_reply(BonjourReplyEvent &); + //B29 + virtual void on_udp_reply(UdpReplyEvent &); void on_timer(wxTimerEvent &); void on_timer_process(); }; diff --git a/src/slic3r/Utils/Udp.cpp b/src/slic3r/Utils/Udp.cpp new file mode 100644 index 0000000..c73f11d --- /dev/null +++ b/src/slic3r/Utils/Udp.cpp @@ -0,0 +1,1228 @@ +#include "Udp.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//B29 +#include +using boost::optional; +using boost::system::error_code; +namespace endian = boost::endian; +namespace asio = boost::asio; +using boost::asio::ip::udp; +#define GET_HOST_COMMAND "mkswifi\r\n" + +namespace Slic3r { + + +// Minimal implementation of a MDNS/DNS-SD client +// This implementation is extremely simple, only the bits that are useful +// for basic MDNS discovery of OctoPi devices are present. +// However, the bits that are present are implemented with security in mind. +// Only fully correct DNS replies are allowed through. +// While decoding the decoder will bail the moment it encounters anything fishy. +// At least that's the idea. To help prove this is actually the case, +// the implementations has been tested with AFL. + + +// Relevant RFCs: +// https://tools.ietf.org/html/rfc6762.txt +// https://tools.ietf.org/html/rfc6763.txt + + +struct DnsName: public std::string +{ + enum + { + MAX_RECURSION = 10, // Keep this low + }; + + static optional decode(const std::vector &buffer, size_t &offset, unsigned depth = 0) + { + // Check offset sanity: + if (offset + 1 >= buffer.size()) { + return boost::none; + } + + // Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic: + if (depth >= MAX_RECURSION) { + return boost::none; + } + + DnsName res; + const size_t bsize = buffer.size(); + + while (true) { + const char* ptr = buffer.data() + offset; + unsigned len = static_cast(*ptr); + if (len & 0xc0) { + // This is a recursive label + unsigned len_2 = static_cast(ptr[1]); + size_t pointer = (len & 0x3f) << 8 | len_2; + const auto nested = decode(buffer, pointer, depth + 1); + if (!nested) { + return boost::none; + } else { + if (res.size() > 0) { + res.push_back('.'); + } + res.append(*nested); + offset += 2; + return std::move(res); + } + } else if (len == 0) { + // This is a name terminator + offset++; + break; + } else { + // This is a regular label + len &= 0x3f; + if (len + offset + 1 >= bsize) { + return boost::none; + } + + res.reserve(len); + if (res.size() > 0) { + res.push_back('.'); + } + + ptr++; + for (const auto end = ptr + len; ptr < end; ptr++) { + char c = *ptr; + if (c >= 0x20 && c <= 0x7f) { + res.push_back(c); + } else { + return boost::none; + } + } + + offset += len + 1; + } + } + + if (res.size() > 0) { + return std::move(res); + } else { + return boost::none; + } + } +}; + +struct DnsHeader +{ + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + + enum + { + SIZE = 12, + }; + + static DnsHeader decode(const std::vector &buffer) { + DnsHeader res; + const uint16_t *data_16 = reinterpret_cast(buffer.data()); + res.id = endian::big_to_native(data_16[0]); + res.flags = endian::big_to_native(data_16[1]); + res.qdcount = endian::big_to_native(data_16[2]); + res.ancount = endian::big_to_native(data_16[3]); + res.nscount = endian::big_to_native(data_16[4]); + res.arcount = endian::big_to_native(data_16[5]); + return res; + } + + uint32_t rrcount() const { + return ancount + nscount + arcount; + } +}; + +struct DnsQuestion +{ + enum + { + MIN_SIZE = 5, + }; + + DnsName name; + uint16_t type; + uint16_t qclass; + + DnsQuestion() + : type(0) + , qclass(0) + {} + + static optional decode(const std::vector &buffer, size_t &offset) + { + auto qname = DnsName::decode(buffer, offset); + if (!qname) { + return boost::none; + } + + DnsQuestion res; + res.name = std::move(*qname); + const uint16_t *data_16 = reinterpret_cast(buffer.data() + offset); + res.type = endian::big_to_native(data_16[0]); + res.qclass = endian::big_to_native(data_16[1]); + + offset += 4; + return std::move(res); + } +}; + +struct DnsResource +{ + DnsName name; + uint16_t type; + uint16_t rclass; + uint32_t ttl; + std::vector data; + + DnsResource() + : type(0) + , rclass(0) + , ttl(0) + {} + + static optional decode(const std::vector &buffer, size_t &offset, size_t &dataoffset) + { + const size_t bsize = buffer.size(); + if (offset + 1 >= bsize) { + return boost::none; + } + + auto rname = DnsName::decode(buffer, offset); + if (!rname) { + return boost::none; + } + + if (offset + 10 >= bsize) { + return boost::none; + } + + DnsResource res; + res.name = std::move(*rname); + const uint16_t *data_16 = reinterpret_cast(buffer.data() + offset); + res.type = endian::big_to_native(data_16[0]); + res.rclass = endian::big_to_native(data_16[1]); + res.ttl = endian::big_to_native(*reinterpret_cast(data_16 + 2)); + uint16_t rdlength = endian::big_to_native(data_16[4]); + + offset += 10; + if (offset + rdlength > bsize) { + return boost::none; + } + + dataoffset = offset; + res.data = std::vector(buffer.begin() + offset, buffer.begin() + offset + rdlength); + offset += rdlength; + + return std::move(res); + } +}; + +struct DnsRR_A +{ + enum { TAG = 0x1 }; + + asio::ip::address_v4 ip; + std::string name; + + static void decode(optional &result, const DnsResource &rr) + { + if (rr.data.size() == 4) { + DnsRR_A res; + const uint32_t ip = endian::big_to_native(*reinterpret_cast(rr.data.data())); + res.ip = asio::ip::address_v4(ip); + result = std::move(res); + } + } +}; + +struct DnsRR_AAAA +{ + enum { TAG = 0x1c }; + + asio::ip::address_v6 ip; + std::string name; + + static void decode(optional &result, const DnsResource &rr) + { + if (rr.data.size() == 16) { + DnsRR_AAAA res; + std::array ip; + std::copy_n(rr.data.begin(), 16, ip.begin()); + res.ip = asio::ip::address_v6(ip); + result = std::move(res); + } + } +}; + +struct DnsRR_SRV +{ + enum + { + TAG = 0x21, + MIN_SIZE = 8, + }; + + uint16_t priority; + uint16_t weight; + uint16_t port; + DnsName hostname; + + static optional decode(const std::vector &buffer, const DnsResource &rr, size_t dataoffset) + { + if (rr.data.size() < MIN_SIZE) { + return boost::none; + } + + DnsRR_SRV res; + + const uint16_t *data_16 = reinterpret_cast(rr.data.data()); + res.priority = endian::big_to_native(data_16[0]); + res.weight = endian::big_to_native(data_16[1]); + res.port = endian::big_to_native(data_16[2]); + + size_t offset = dataoffset + 6; + auto hostname = DnsName::decode(buffer, offset); + + if (hostname) { + res.hostname = std::move(*hostname); + return std::move(res); + } else { + return boost::none; + } + } +}; + +struct DnsRR_TXT +{ + enum + { + TAG = 0x10, + }; + + UdpReply::TxtData data; + + static optional decode(const DnsResource &rr, const Udp::TxtKeys &txt_keys) + { + const size_t size = rr.data.size(); + if (size < 2) { + return boost::none; + } + + DnsRR_TXT res; + + for (auto it = rr.data.begin(); it != rr.data.end(); ) { + unsigned val_size = static_cast(*it); + if (val_size == 0 || it + val_size >= rr.data.end()) { + return boost::none; + } + ++it; + + const auto it_end = it + val_size; + const auto it_eq = std::find(it, it_end, '='); + if (it_eq > it && it_eq < it_end - 1) { + std::string key(it_eq - it, ' '); + std::copy(it, it_eq, key.begin()); + + if (txt_keys.find(key) != txt_keys.end() || key == "path") { + // This key-value has been requested for + std::string value(it_end - it_eq - 1, ' '); + std::copy(it_eq + 1, it_end, value.begin()); + res.data.insert(std::make_pair(std::move(key), std::move(value))); + } + } + + it = it_end; + } + + return std::move(res); + } +}; + +struct DnsSDPair +{ + optional srv; + optional txt; +}; + +struct DnsSDMap : public std::map +{ + void insert_srv(std::string &&name, DnsRR_SRV &&srv) + { + auto hit = this->find(name); + if (hit != this->end()) { + hit->second.srv = std::move(srv); + } else { + DnsSDPair pair; + pair.srv = std::move(srv); + this->insert(std::make_pair(std::move(name), std::move(pair))); + } + } + + void insert_txt(std::string &&name, DnsRR_TXT &&txt) + { + auto hit = this->find(name); + if (hit != this->end()) { + hit->second.txt = std::move(txt); + } else { + DnsSDPair pair; + pair.txt = std::move(txt); + this->insert(std::make_pair(std::move(name), std::move(pair))); + } + } +}; + +struct DnsUdpMessage +{ + enum + { + MAX_SIZE = 4096, + MAX_ANS = 30, + }; + + DnsHeader header; + optional question; + + optional rr_a; + optional rr_aaaa; + std::vector rr_srv; + + DnsSDMap sdmap; + + static optional decode(const std::vector& buffer, const Udp::TxtKeys& txt_keys) + { + const auto size = buffer.size(); + if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) { + return boost::none; + } + + DnsUdpMessage res; + res.header = DnsHeader::decode(buffer); + + if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) { + return boost::none; + } + + size_t offset = DnsHeader::SIZE; + if (res.header.qdcount == 1) { + res.question = DnsQuestion::decode(buffer, offset); + } + + for (unsigned i = 0; i < res.header.rrcount(); i++) { + size_t dataoffset = 0; + auto rr = DnsResource::decode(buffer, offset, dataoffset); + if (!rr) { + return boost::none; + } + else { + res.parse_rr(buffer, std::move(*rr), dataoffset, txt_keys); + } + } + + return std::move(res); + } +private: + void parse_rr(const std::vector& buffer, DnsResource&& rr, size_t dataoffset, const Udp::TxtKeys& txt_keys) + { + switch (rr.type) { + case DnsRR_A::TAG: + DnsRR_A::decode(this->rr_a, rr); + this->rr_a->name = rr.name; + break; + case DnsRR_AAAA::TAG: + DnsRR_AAAA::decode(this->rr_aaaa, rr); + this->rr_aaaa->name = rr.name; + break; + case DnsRR_SRV::TAG: { + auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset); + if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); } + break; + } + case DnsRR_TXT::TAG: { + auto txt = DnsRR_TXT::decode(rr, txt_keys); + if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); } + break; + } + } + } +}; + +std::ostream& operator<<(std::ostream &os, const DnsUdpMessage &msg) +{ + os << boost::format("DnsUdpMessage(ID: %1%, Q: %2%, A: %3%, AAAA: %4%, services: [") + % msg.header.id + % (msg.question ? msg.question->name.c_str() : "none") + % (msg.rr_a ? msg.rr_a->ip.to_string() : "none") + % (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none"); + + enum { SRV_PRINT_MAX = 3 }; + unsigned i = 0; + for (const auto &sdpair : msg.sdmap) { + if (i > 0) { os << ", "; } + + if (i < SRV_PRINT_MAX) { + os << sdpair.first; + } else { + os << "..."; + break; + } + + i++; + } + + return os << "])"; +} + +const asio::ip::address_v4 UdpRequest::MCAST_IP4{ 0xffffffff }; +const asio::ip::address_v6 UdpRequest::MCAST_IP6 = asio::ip::make_address_v6("ff02::fb"); +const uint16_t UdpRequest::MCAST_PORT = 8989; + +optional UdpRequest::make_PTR(const std::string &service, const std::string &protocol) +{ + if (service.size() > 15 || protocol.size() > 15) { + return boost::none; + } + + std::vector data; + data.reserve(service.size() + 18); + + // Add metadata + static const unsigned char rq_meta[] = { + 0x00, 0x00, // Query ID (zero for mDNS) + 0x00, 0x00, // Flags + 0x00, 0x01, // One query + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs + }; + std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); + + // Add PTR query name + data.push_back(service.size() + 1); + data.push_back('_'); + data.insert(data.end(), service.begin(), service.end()); + data.push_back(protocol.size() + 1); + data.push_back('_'); + data.insert(data.end(), protocol.begin(), protocol.end()); + + // Add the rest of PTR record + static const unsigned char ptr_tail[] = { + 0x05, // length of "label" + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // "label" string and terminator + 0x00, 0x0c, // Type PTR + 0x00, 0xff, // Class ANY + }; + std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); + + return UdpRequest(std::move(data)); +} + +optional UdpRequest::make_A(const std::string& hostname) +{ + // todo: why is this and what is real max + if (hostname.size() > 30) { + return boost::none; + } + + std::vector data; + data.reserve(hostname.size() + 18); + + // Add metadata + static const unsigned char rq_meta[] = { + 0x00, 0x00, // Query ID (zero for mDNS) + 0x00, 0x00, // Flags + 0x00, 0x01, // One query + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs + }; + std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); + + // Add hostname without .local + data.push_back(hostname.size()); + data.insert(data.end(), hostname.begin(), hostname.end()); + + // Add the rest of A record + static const unsigned char ptr_tail[] = { + 0x05, // length of "local" + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,// "local" string and terminator + 0x00, 0x01, // Type A + 0x00, 0xff, // Class - 01 is internet 0xff is any + }; + std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); + + return UdpRequest(std::move(data)); +} + +optional UdpRequest::make_AAAA(const std::string& hostname) +{ + // todo: why is this and what is real max + if (hostname.size() > 30) { + return boost::none; + } + + std::vector data; + data.reserve(hostname.size() + 18); + + // Add metadata + static const unsigned char rq_meta[] = { + 0x00, 0x00, // Query ID (zero for mDNS) + 0x00, 0x00, // Flags + 0x00, 0x01, // One query + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs + }; + std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); + + // Add hostname without .local + data.push_back(hostname.size()); + data.insert(data.end(), hostname.begin(), hostname.end()); + + // Add the rest of A record + static const unsigned char ptr_tail[] = { + 0x05, // length of "local" + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // "local" string and terminator + 0x00, 0x1c, // Type AAAA + 0x00, 0xff, // Class - 01 is internet 0xff is any + }; + std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); + + return UdpRequest(std::move(data)); +} + +namespace { +std::string strip_service_dn(const std::string& service_name, const std::string& service_dn) +{ + if (service_name.size() <= service_dn.size()) { + return std::string(); + } + + auto needle = service_name.rfind(service_dn); + if (needle == service_name.size() - service_dn.size()) { + return service_name.substr(0, needle - 1); + } else { + return std::string(); + } +} +} // namespace + +UdpUdpSession::UdpUdpSession(Udp::ReplyFn rfn) : replyfn(rfn) +{ + buffer.resize(DnsUdpMessage::MAX_SIZE); +} + +UdpUdpSocket::UdpUdpSocket( Udp::ReplyFn replyfn, const asio::ip::address& multicast_address, const asio::ip::address& interface_address, std::shared_ptr< boost::asio::io_service > io_service) + : replyfn(replyfn) + , multicast_address(multicast_address) + , socket(*io_service, udp::endpoint(udp::v4(), 0)) + , io_service(io_service) +{ + try { + + socket.set_option(boost::asio::socket_base::reuse_address(true)); + socket.set_option(boost::asio::socket_base::broadcast(true)); + mcast_endpoint = udp::endpoint(boost::asio::ip::address_v4::broadcast(), UdpRequest::MCAST_PORT); + // open socket + //boost::asio::ip::udp::endpoint listen_endpoint(multicast_address.is_v4() ? udp::v4() : udp::v6(), UdpRequest::MCAST_PORT); + //socket.open(listen_endpoint.protocol()); + //// set socket to listen + //socket.set_option(udp::socket::reuse_address(true)); + // socket.set_option(boost::asio::socket_base::broadcast(true)); + //socket.bind(listen_endpoint); + //if (interface_address.is_v4()) { + // // listen for multicast on given interface + // socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v4(), interface_address.to_v4())); + // // send to interface + // socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v4())); + //} else { + // // listen for multicast on given interface + // socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v6(), interface_address.to_v6().scope_id())); + // // send to interface + // socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v6().scope_id())); + //} + //mcast_endpoint = udp::endpoint(multicast_address, UdpRequest::MCAST_PORT); + //mcast_endpoint = udp::endpoint(boost::asio::ip::address_v4::broadcast(), UdpRequest::MCAST_PORT); + BOOST_LOG_TRIVIAL(error) << "Socket created. Multicast: " << multicast_address << ". Interface: " << interface_address; + BOOST_LOG_TRIVIAL(info) << "Socket created. Multicast: " << multicast_address << ". Interface: " << interface_address; + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + + +UdpUdpSocket::UdpUdpSocket( Udp::ReplyFn replyfn, const asio::ip::address& multicast_address, std::shared_ptr< boost::asio::io_service > io_service) + : replyfn(replyfn) + , multicast_address(multicast_address) + , socket(*io_service) + , io_service(io_service) +{ + try { + // open socket + boost::asio::ip::udp::endpoint listen_endpoint(multicast_address.is_v4() ? udp::v4() : udp::v6(), UdpRequest::MCAST_PORT); + socket.open(listen_endpoint.protocol()); + // set socket to listen + socket.set_option(udp::socket::reuse_address(true)); + socket.bind(listen_endpoint); + socket.set_option(boost::asio::ip::multicast::join_group(multicast_address)); + mcast_endpoint = udp::endpoint(multicast_address, UdpRequest::MCAST_PORT); + + BOOST_LOG_TRIVIAL(info) << "Socket created. Multicast: " << multicast_address; + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + +void UdpUdpSocket::send() +{ + try { + //for (const auto& request : requests) { + // BOOST_LOG_TRIVIAL(error) << "Data: " << mcast_endpoint; + // socket.send_to(asio::buffer(request.m_data), mcast_endpoint); + //} + char *pBuffer = (char *) GET_HOST_COMMAND; + int nBufferSize = 72; + socket.send_to(boost::asio::buffer(pBuffer, nBufferSize), mcast_endpoint); + // Should we care if this is called while already receiving? (async_receive call from receive_handler) + async_receive(); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + +void UdpUdpSocket::async_receive() +{ + try { + // our session to hold the buffer + endpoint + auto session = create_session(); + + socket.async_receive_from(boost::asio::buffer(m_recvBuf) + , session->remote_endpoint + , boost::bind(&UdpUdpSocket::receive_handler, this, session, asio::placeholders::error, asio::placeholders::bytes_transferred)); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + +void UdpUdpSocket::receive_handler(SharedUdpSession session, const boost::system::error_code& error, size_t bytes) +{ + // let io_service to handle the datagram on session + // from boost documentation io_service::post: + // The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked. + std::string pData = m_recvBuf.data(); + io_service->post(boost::bind(&UdpUdpSession::handle_receive, session, error, bytes, pData)); + // immediately accept new datagrams + //BOOST_LOG_TRIVIAL(error) << m_recvBuf.data(); + //asio::ip::address ip ; + //BOOST_LOG_TRIVIAL(error) << "\nIP :" << pData.substr(pData.find_last_of(",") + 1) + // << "NAME :" << pData.substr(pData.find("mkswifi:") + 8, pData.find(",") - 8); + //BOOST_LOG_TRIVIAL(error) << "pData2 :" << pData.substr(pData.find("mkswifi:") + 8, pData.find(",") - 8); + //ip = pData.substr(pData.data().find_last_of(",") + 1)); + //list->SetItem(item, 1, get_reply[n].substr(get_reply[n].find("mkswifi:") + 8, get_reply[n].find(",") - 8)); + //UdpReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(txt_data)); + //replyfn(std::move(reply)); + //UdpReply::TxtData txt_data; + //asio::ip::address addr = asio::ip::address::from_string(pData.substr(pData.find_last_of(",") + 1)); + //UdpReply reply(addr, 0, std::move(pData.substr(pData.find("mkswifi:") + 8, pData.find(",") - 8)), + // pData.substr(pData.find("mkswifi:") + 8, pData.find(",") - 8), std::move(txt_data)); + //replyfn(std::move(reply)); + async_receive(); +} + +SharedUdpSession LookupUdpSocket::create_session() const { return std::shared_ptr(new LookupUdpSession(this, replyfn)); } + +void LookupUdpSession::handle_receive(const error_code &error, size_t bytes, std::string pData) +{ + assert(socket); + + if (error) { + BOOST_LOG_TRIVIAL(error) << error.message(); + return; + } + if (bytes == 0 || !replyfn) { + return; + } + asio::ip::address addr = asio::ip::address::from_string("127.0.0.1"); + UdpReply reply(addr, 80, std::move(pData.substr(pData.find_last_of(",") + 1)), + pData.substr(pData.find("mkswifi:") + 8, pData.find(",") - 8)); + replyfn(std::move(reply)); + + //buffer.resize(bytes); + //auto dns_msg = DnsUdpMessage::decode(buffer, socket->get_txt_keys()); + //if (dns_msg) { + // asio::ip::address ip = remote_endpoint.address(); + // if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; } + // else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; } + + // for (auto& sdpair : dns_msg->sdmap) { + // if (!sdpair.second.srv) { + // continue; + // } + + // const auto& srv = *sdpair.second.srv; + + // auto service_name = strip_service_dn(sdpair.first, socket->get_service_dn()); + // if (service_name.empty()) + // continue; + + // std::string path; + // std::string version; + + // UdpReply::TxtData txt_data; + // if (sdpair.second.txt) { + // txt_data = std::move(sdpair.second.txt->data); + // } + + // UdpReply reply(ip, srv.port, std::move(service_name), srv.hostname); + // replyfn(std::move(reply)); + // } + //} +} + +SharedUdpSession ResolveUdpUdpSocket::create_session() const +{ + return std::shared_ptr< ResolveUdpSession > (new ResolveUdpSession(this, replyfn)); +} + + +void ResolveUdpSession::handle_receive(const error_code &error, size_t bytes, std::string pData) +{ + assert(socket); + if (error) { + // todo: what level? do we even log? There might be callbacks when timer runs out + BOOST_LOG_TRIVIAL(error) << error.message(); + return; + } + if (bytes == 0 || !replyfn) { + // todo: log something? + return; + } + + buffer.resize(bytes); +#if 0 + std::string str; + char const hex_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + for (size_t i = 0; i < buffer.size(); i++) { + const char ch = buffer[i]; + str += hex_chars[(ch & 0xF0) >> 4]; + str += hex_chars[(ch & 0x0F) >> 0]; + } + BOOST_LOG_TRIVIAL(debug) << remote_endpoint.address()<< " " << str; +#endif + // decode buffer, txt keys are not needed for A / AAAA answer + auto dns_msg = DnsUdpMessage::decode(buffer, Udp::TxtKeys()); + if (dns_msg) { + asio::ip::address ip; + std::string answer_name; + if (dns_msg->rr_a) { + ip = dns_msg->rr_a->ip; + answer_name = dns_msg->rr_a->name; + } + else if (dns_msg->rr_aaaa) { + ip = dns_msg->rr_aaaa->ip; + answer_name = dns_msg->rr_aaaa->name; + } + else + return; // not matching query type with answer type + + if (!answer_name.empty()) { + // transform both strings to lower. Should we really do it? + std::string name_tolower = answer_name; + std::transform(name_tolower.begin(), name_tolower.end(), name_tolower.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::string hostname_tolower = socket->get_hostname(); + std::transform(hostname_tolower.begin(), hostname_tolower.end(), hostname_tolower.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (name_tolower == hostname_tolower) { + UdpReply reply(ip, 0, std::string(), answer_name); + replyfn(std::move(reply)); + } + } + } +} + +// API - private part + +struct Udp::priv +{ + const std::string service; + std::string protocol; + std::string service_dn; + TxtKeys txt_keys; + unsigned timeout; + unsigned retries; + std::string hostname; + +// std::vector replies; + + std::vector buffer; + std::thread io_thread; + Udp::ReplyFn replyfn; + Udp::CompleteFn completefn; + Udp::ResolveFn resolvefn; + + priv(std::string&& service); + + // void udp_receive_lookup(udp::endpoint from, size_t bytes); + void lookup_perform(); + void resolve_perform(); +}; + +Udp::priv::priv(std::string&& service) + : service(std::move(service)) + , protocol("tcp") + , timeout(10) + , retries(1) +{ + buffer.resize(DnsUdpMessage::MAX_SIZE); +} + +void Udp::priv::lookup_perform() +{ + service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str(); + + std::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service); + + std::vector sockets; + + // resolve intefaces - from PR#6646 + std::vector interfaces; + asio::ip::udp::resolver resolver(*io_service); + boost::system::error_code ec; + // ipv4 interfaces + auto results = resolver.resolve(udp::v4(), asio::ip::host_name(), "", ec); + if (!ec) { + for (const auto & r : results) { + const auto addr = r.endpoint().address(); + if (addr.is_loopback()) continue; + interfaces.emplace_back(std::move(addr)); + } + // create ipv4 socket for each interface + // each will send to querry to for both ipv4 and ipv6 + for (const auto& intrfc : interfaces) + sockets.emplace_back(new LookupUdpSocket(txt_keys, service, service_dn, protocol, replyfn, UdpRequest::MCAST_IP4, intrfc, io_service)); + } else { + BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv4 interfaces: " << ec.message(); + } + if (sockets.empty()) + sockets.emplace_back(new LookupUdpSocket(txt_keys, service, service_dn, protocol, replyfn, UdpRequest::MCAST_IP4, io_service)); + // ipv6 interfaces + interfaces.clear(); + //udp::resolver::query query(host, PORT, boost::asio::ip::resolver_query_base::numeric_service); + results = resolver.resolve(udp::v6(), asio::ip::host_name(), "", ec); + if (!ec) + { + for (const auto& r : results) { + const auto addr = r.endpoint().address(); + if (addr.is_loopback()) continue; + interfaces.emplace_back(std::move(addr)); + } + // create ipv6 socket for each interface + // each will send to querry to for both ipv4 and ipv6 + for (const auto& intrfc : interfaces) + sockets.emplace_back(new LookupUdpSocket(txt_keys, service, service_dn, protocol, replyfn, UdpRequest::MCAST_IP6, intrfc, io_service)); + if (interfaces.empty()) + sockets.emplace_back(new LookupUdpSocket(txt_keys, service, service_dn, protocol, replyfn, UdpRequest::MCAST_IP6, io_service)); + } else { + BOOST_LOG_TRIVIAL(info)<< "Failed to resolve ipv6 interfaces: " << ec.message(); + } + + try { + // send first queries + for (auto * socket : sockets) + socket->send(); + + // timer settings + asio::deadline_timer timer(*io_service); + retries--; + std::function timer_handler = [&](const error_code& error) { + // end + if (retries == 0 || error) { + // is this correct ending? + io_service->stop(); + if (completefn) { + completefn(); + } + // restart timer + } else { + retries--; + timer.expires_from_now(boost::posix_time::seconds(timeout)); + timer.async_wait(timer_handler); + // trigger another round of queries + for (auto * socket : sockets) + socket->send(); + } + }; + // start timer + timer.expires_from_now(boost::posix_time::seconds(timeout)); + timer.async_wait(timer_handler); + // start io_service, it will run until it has something to do - so in this case until stop is called in timer + io_service->run(); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + +void Udp::priv::resolve_perform() +{ + // reply callback is shared to every UdpUdpSession which is called on same thread as io_service->run(); + // thus no need to mutex replies in reply_callback, same should go with the timer + std::vector replies; + // examples would store [self] to the lambda (and the timer one), is it ok not to do it? (Should be c++03) + const auto reply_callback = [&rpls = replies](UdpReply&& reply) + { + if (std::find(rpls.begin(), rpls.end(), reply) == rpls.end()) + rpls.push_back(reply); + }; + + std::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service); + std::vector sockets; + + // resolve interfaces - from PR#6646 + std::vector interfaces; + asio::ip::udp::resolver resolver(*io_service); + boost::system::error_code ec; + // ipv4 interfaces + auto results = resolver.resolve(udp::v4(), asio::ip::host_name(), "", ec); + if (!ec) { + for (auto const& r : results) { + auto const addr = r.endpoint().address(); + if (addr.is_loopback()) continue; + interfaces.emplace_back(addr); + } + // create ipv4 socket for each interface + // each will send to querry to for both ipv4 and ipv6 + for (const auto& intrfc : interfaces) + sockets.emplace_back(new ResolveUdpUdpSocket(hostname, reply_callback, UdpRequest::MCAST_IP4, intrfc, io_service)); + } else { + BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv4 interfaces: " << ec.message(); + } + if (sockets.empty()) + sockets.emplace_back(new ResolveUdpUdpSocket(hostname, reply_callback, UdpRequest::MCAST_IP4, io_service)); + + // ipv6 interfaces + interfaces.clear(); + results = resolver.resolve(udp::v6(), asio::ip::host_name(), "", ec); + if (!ec) { + for (auto const& r : results) { + auto const addr = r.endpoint().address(); + if (addr.is_loopback()) continue; + interfaces.emplace_back(addr); + } + // create ipv6 socket for each interface + // each will send to querry to for both ipv4 and ipv6 + for (const auto& intrfc : interfaces) + sockets.emplace_back(new ResolveUdpUdpSocket(hostname, reply_callback, UdpRequest::MCAST_IP6, intrfc, io_service)); + if (interfaces.empty()) + sockets.emplace_back(new ResolveUdpUdpSocket(hostname, reply_callback, UdpRequest::MCAST_IP6, io_service)); + } else { + BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv6 interfaces: " << ec.message(); + } + + try { + // send first queries + for (auto * socket : sockets) + socket->send(); + + // timer settings + asio::deadline_timer timer(*io_service); + retries--; + std::function timer_handler = [&](const error_code& error) { + int replies_count = replies.size(); + // end + if (retries == 0 || error || replies_count > 0) { + // is this correct ending? + io_service->stop(); + if (replies_count > 0 && resolvefn) { + resolvefn(replies); + } + // restart timer + } else { + retries--; + timer.expires_from_now(boost::posix_time::seconds(timeout)); + timer.async_wait(timer_handler); + // trigger another round of queries + for (auto * socket : sockets) + socket->send(); + } + }; + // start timer + timer.expires_from_now(boost::posix_time::seconds(timeout)); + timer.async_wait(timer_handler); + // start io_service, it will run until it has something to do - so in this case until stop is called in timer + io_service->run(); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + } +} + + +// API - public part + +UdpReply::UdpReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname) + : ip(std::move(ip)) + , port(port) + , service_name(std::move(service_name)) + , hostname(std::move(hostname)) +{ + std::string proto; + std::string port_suffix; + if (port == 443) { proto = "https://"; } + if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); } + + std::string path = this->path(); + if (path[0] != '/') { path.insert(0, 1, '/'); } + full_address = proto + ip.to_string() + port_suffix; + if (path != "/") { full_address += path; } +} + +std::string UdpReply::path() const +{ + const auto it = "11"; + return it; +} + +bool UdpReply::operator==(const UdpReply &other) const +{ + return this->full_address == other.full_address + && this->service_name == other.service_name; +} + +bool UdpReply::operator<(const UdpReply &other) const +{ + if (this->ip != other.ip) { + // So that the common case doesn't involve string comparison + return this->ip < other.ip; + } else { + auto cmp = this->full_address.compare(other.full_address); + return cmp != 0 ? cmp < 0 : this->service_name < other.service_name; + } +} + +std::ostream& operator<<(std::ostream &os, const UdpReply &reply) +{ + os << boost::format("UdpReply(%1%, %2%, %3%, %4%") + % reply.ip.to_string() + % reply.service_name + % reply.hostname + % reply.full_address; + + + return os << ')'; +} + + +Udp::Udp(std::string service) + : p(new priv(std::move(service))) +{} + +Udp::Udp(Udp &&other) : p(std::move(other.p)) {} + +Udp::~Udp() +{ + if (p && p->io_thread.joinable()) { + p->io_thread.detach(); + } +} + +Udp& Udp::set_protocol(std::string protocol) +{ + if (p) { p->protocol = std::move(protocol); } + return *this; +} + +Udp& Udp::set_txt_keys(TxtKeys txt_keys) +{ + if (p) { p->txt_keys = std::move(txt_keys); } + return *this; +} + +Udp& Udp::set_timeout(unsigned timeout) +{ + if (p) { p->timeout = timeout; } + return *this; +} + +Udp& Udp::set_hostname(const std::string& hostname) +{ + if (p) { p->hostname = hostname; } + return *this; +} + +Udp& Udp::set_retries(unsigned retries) +{ + if (p && retries > 0) { p->retries = retries; } + return *this; +} + +Udp& Udp::on_udp_reply(ReplyFn fn) +{ + if (p) { p->replyfn = std::move(fn); } + return *this; +} + +Udp& Udp::on_complete(CompleteFn fn) +{ + if (p) { p->completefn = std::move(fn); } + return *this; +} + +Udp& Udp::on_resolve(ResolveFn fn) +{ + if (p) { p->resolvefn = std::move(fn); } + return *this; +} + +Udp::Ptr Udp::lookup() +{ + auto self = std::make_shared(std::move(*this)); + + if (self->p) { + auto io_thread = std::thread([self]() { + self->p->lookup_perform(); + }); + self->p->io_thread = std::move(io_thread); + } + + return self; +} + + +Udp::Ptr Udp::resolve() +{ + auto self = std::make_shared(std::move(*this)); + + if (self->p) { + auto io_thread = std::thread([self]() { + self->p->resolve_perform(); + }); + self->p->io_thread = std::move(io_thread); + } + + return self; +} + +void Udp::resolve_sync() +{ + if (p) + p->resolve_perform(); +} + + +} + diff --git a/src/slic3r/Utils/Udp.hpp b/src/slic3r/Utils/Udp.hpp new file mode 100644 index 0000000..00ae1cb --- /dev/null +++ b/src/slic3r/Utils/Udp.hpp @@ -0,0 +1,289 @@ +#ifndef slic3r_Udp_hpp_ +#define slic3r_Udp_hpp_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Slic3r { + + + +struct UdpReply +{ + typedef std::unordered_map TxtData; + + boost::asio::ip::address ip; + uint16_t port; + std::string service_name; + std::string hostname; + std::string full_address; + + //TxtData txt_data; + + UdpReply() = delete; + UdpReply(boost::asio::ip::address ip, + uint16_t port, + std::string service_name, + std::string hostname); + + std::string path() const; + + bool operator==(const UdpReply &other) const; + bool operator<(const UdpReply &other) const; +}; + +std::ostream& operator<<(std::ostream &, const UdpReply &); + +/// Udp lookup performer +class Udp : public std::enable_shared_from_this { +private: + struct priv; +public: + typedef std::shared_ptr Ptr; + typedef std::function ReplyFn; + typedef std::function CompleteFn; + typedef std::function&)> ResolveFn; + typedef std::set TxtKeys; + + Udp(std::string service); + Udp(Udp &&other); + ~Udp(); + + // Set requested service protocol, "tcp" by default + Udp& set_protocol(std::string protocol); + // Set which TXT key-values should be collected + // Note that "path" is always collected + Udp& set_txt_keys(TxtKeys txt_keys); + Udp& set_timeout(unsigned timeout); + Udp& set_retries(unsigned retries); + // ^ Note: By default there is 1 retry (meaning 1 broadcast is sent). + // Timeout is per one retry, ie. total time spent listening = retries * timeout. + // If retries > 1, then care needs to be taken as more than one reply from the same service may be received. + + // sets hostname queried by resolve() + Udp& set_hostname(const std::string& hostname); + + Udp& on_udp_reply(ReplyFn fn); + Udp& on_complete(CompleteFn fn); + + Udp& on_resolve(ResolveFn fn); + // lookup all devices by given TxtKeys + // each correct reply is passed back in ReplyFn, finishes with CompleteFn + Ptr lookup(); + // performs resolving of hostname into vector of ip adresses passed back by ResolveFn + // needs set_hostname and on_resolve to be called before. + Ptr resolve(); + // resolve on the current thread + void resolve_sync(); +private: + std::unique_ptr p; +}; + +struct UdpRequest +{ + static const boost::asio::ip::address_v4 MCAST_IP4; + static const boost::asio::ip::address_v6 MCAST_IP6; + static const uint16_t MCAST_PORT; + + std::vector m_data; + + static boost::optional make_PTR(const std::string& service, const std::string& protocol); + static boost::optional make_A(const std::string& hostname); + static boost::optional make_AAAA(const std::string& hostname); +private: + UdpRequest(std::vector&& data) : m_data(std::move(data)) {} +}; + + +class LookupUdpSocket; +class ResolveUdpUdpSocket; + +// Session is created for each async_receive of socket. On receive, its handle_receive method is called (Thru io_service->post). +// ReplyFn is called if correct datagram was received. +class UdpUdpSession +{ +public: + UdpUdpSession(Udp::ReplyFn rfn); + virtual void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) = 0; + std::vector buffer; + boost::asio::ip::udp::endpoint remote_endpoint; +protected: + Udp::ReplyFn replyfn; +}; +typedef std::shared_ptr SharedUdpSession; +// Session for LookupUdpSocket +class LookupUdpSession : public UdpUdpSession +{ +public: + LookupUdpSession(const LookupUdpSocket *sckt, Udp::ReplyFn rfn) : UdpUdpSession(rfn), socket(sckt) {} + void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) override; + +protected: + // const pointer to socket to get needed data as txt_keys etc. + const LookupUdpSocket *socket; +}; +// Session for ResolveUdpUdpSocket +class ResolveUdpSession : public UdpUdpSession +{ +public: + ResolveUdpSession(const ResolveUdpUdpSocket* sckt, Udp::ReplyFn rfn) : UdpUdpSession(rfn), socket(sckt) {} + void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) override; + +protected: + // const pointer to seocket to get hostname during handle_receive + const ResolveUdpUdpSocket* socket; +}; + +// Udp socket, starts receiving answers after first send() call until io_service is stopped. +class UdpUdpSocket +{ +public: + // Two constructors: 1st is with interface which must be resolved before calling this + UdpUdpSocket(Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service); + + UdpUdpSocket(Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service); + + void send(); + void async_receive(); + void cancel() { socket.cancel(); } +protected: + void receive_handler(SharedUdpSession session, const boost::system::error_code& error, size_t bytes); + virtual SharedUdpSession create_session() const = 0; + + Udp::ReplyFn replyfn; + boost::asio::ip::address multicast_address; + boost::asio::ip::udp::socket socket; + boost::asio::ip::udp::endpoint mcast_endpoint; + std::shared_ptr< boost::asio::io_service > io_service; + std::vector requests; + boost::array m_recvBuf; +}; + +class LookupUdpSocket : public UdpUdpSocket +{ +public: + LookupUdpSocket(Udp::TxtKeys txt_keys + , std::string service + , std::string service_dn + , std::string protocol + , Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpUdpSocket(replyfn, multicast_address, interface_address, io_service) + , txt_keys(txt_keys) + , service(service) + , service_dn(service_dn) + , protocol(protocol) + { + assert(!service.empty() && replyfn); + create_request(); + } + + LookupUdpSocket(Udp::TxtKeys txt_keys + , std::string service + , std::string service_dn + , std::string protocol + , Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpUdpSocket(replyfn, multicast_address, io_service) + , txt_keys(txt_keys) + , service(service) + , service_dn(service_dn) + , protocol(protocol) + { + assert(!service.empty() && replyfn); + create_request(); + } + + const Udp::TxtKeys get_txt_keys() const { return txt_keys; } + const std::string get_service() const { return service; } + const std::string get_service_dn() const { return service_dn; } + +protected: + SharedUdpSession create_session() const override; + void create_request() + { + requests.clear(); + // create PTR request + if (auto rqst = UdpRequest::make_PTR(service, protocol); rqst) + requests.push_back(std::move(rqst.get())); + } + boost::optional request; + Udp::TxtKeys txt_keys; + std::string service; + std::string service_dn; + std::string protocol; +}; + +class ResolveUdpUdpSocket : public UdpUdpSocket +{ +public: + ResolveUdpUdpSocket(const std::string& hostname + , Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpUdpSocket(replyfn, multicast_address, interface_address, io_service) + , hostname(hostname) + + { + assert(!hostname.empty() && replyfn); + create_requests(); + } + + ResolveUdpUdpSocket(const std::string& hostname + , Udp::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpUdpSocket(replyfn, multicast_address, io_service) + , hostname(hostname) + + { + assert(!hostname.empty() && replyfn); + create_requests(); + } + + std::string get_hostname() const { return hostname; } +protected: + SharedUdpSession create_session() const override; + void create_requests() + { + requests.clear(); + // UdpRequest::make_A / AAAA is now implemented to add .local correctly after the hostname. + // If that is unsufficient, we need to change make_A / AAAA and pass full hostname. + std::string trimmed_hostname = hostname; + if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos) + trimmed_hostname = trimmed_hostname.substr(0, dot_pos); + if (auto rqst = UdpRequest::make_A(trimmed_hostname); rqst) + requests.push_back(std::move(rqst.get())); + + trimmed_hostname = hostname; + if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos) + trimmed_hostname = trimmed_hostname.substr(0, dot_pos); + if (auto rqst = UdpRequest::make_AAAA(trimmed_hostname); rqst) + requests.push_back(std::move(rqst.get())); + } + + std::string hostname; +}; + +} + +#endif