From 14929e9d156351221b6da192710437bb50409f29 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Wed, 7 Feb 2018 11:37:15 +0100
Subject: [PATCH 1/9] Http client via libcurl

---
 cmake/modules/FindCURL.cmake |  59 ++++++++
 xs/CMakeLists.txt            |   7 +
 xs/src/slic3r/Utils/Http.cpp | 261 +++++++++++++++++++++++++++++++++++
 xs/src/slic3r/Utils/Http.hpp |  53 +++++++
 4 files changed, 380 insertions(+)
 create mode 100644 cmake/modules/FindCURL.cmake
 create mode 100644 xs/src/slic3r/Utils/Http.cpp
 create mode 100644 xs/src/slic3r/Utils/Http.hpp

diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake
new file mode 100644
index 000000000..b8724858c
--- /dev/null
+++ b/cmake/modules/FindCURL.cmake
@@ -0,0 +1,59 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#.rst:
+# FindCURL
+# --------
+#
+# Find curl
+#
+# Find the native CURL headers and libraries.
+#
+# ::
+#
+#   CURL_INCLUDE_DIRS   - where to find curl/curl.h, etc.
+#   CURL_LIBRARIES      - List of libraries when using curl.
+#   CURL_FOUND          - True if curl found.
+#   CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8)
+
+# Look for the header file.
+find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
+mark_as_advanced(CURL_INCLUDE_DIR)
+
+# Look for the library (sorted from most current/relevant entry to least).
+find_library(CURL_LIBRARY NAMES
+    curl
+  # Windows MSVC Makefile:
+    libcurl_a
+  # Windows MSVC prebuilts:
+    curllib
+    libcurl_imp
+    curllib_static
+  # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
+    libcurl
+)
+mark_as_advanced(CURL_LIBRARY)
+
+if(CURL_INCLUDE_DIR)
+  foreach(_curl_version_header curlver.h curl.h)
+    if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}")
+      file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"")
+
+      string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}")
+      unset(curl_version_str)
+      break()
+    endif()
+  endforeach()
+endif()
+
+find_package_handle_standard_args(CURL
+                                  REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR
+                                  VERSION_VAR CURL_VERSION_STRING)
+
+if(CURL_FOUND)
+  set(CURL_LIBRARIES ${CURL_LIBRARY})
+  set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
+
+  message(STATUS "  Curl libraries:        = ${CURL_LIBRARIES}")
+  message(STATUS "  Curl include dirs:     = ${CURL_INCLUDE_DIRS}")
+endif()
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index caabeab6b..d2f252030 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -199,6 +199,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/2DBed.hpp
     ${LIBDIR}/slic3r/GUI/wxExtensions.cpp
     ${LIBDIR}/slic3r/GUI/wxExtensions.hpp
+    ${LIBDIR}/slic3r/Utils/Http.cpp
+    ${LIBDIR}/slic3r/Utils/Http.hpp
 )
 
 add_library(admesh STATIC
@@ -522,6 +524,11 @@ if (SLIC3R_PRUSACONTROL)
     target_link_libraries(XS ${wxWidgets_LIBRARIES})
 endif()
 
+find_package(CURL REQUIRED)
+include_directories(${CURL_INCLUDE_DIRS})
+target_link_libraries(XS ${CURL_LIBRARIES})
+
+
 ## OPTIONAL packages
 
 # Find eigen3 or use bundled version
diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
new file mode 100644
index 000000000..45a350a59
--- /dev/null
+++ b/xs/src/slic3r/Utils/Http.cpp
@@ -0,0 +1,261 @@
+#include "Http.hpp"
+
+#include <cstdlib>
+#include <functional>
+#include <thread>
+#include <iostream>
+#include <tuple>
+#include <boost/format.hpp>
+
+#include <curl/curl.h>
+
+#include "../../libslic3r/libslic3r.h"
+
+
+namespace Slic3r {
+
+
+// Private
+
+class CurlGlobalInit
+{
+	static const CurlGlobalInit instance;
+
+	CurlGlobalInit()  { ::curl_global_init(CURL_GLOBAL_DEFAULT); }
+	~CurlGlobalInit() { ::curl_global_cleanup(); }
+};
+
+struct Http::priv
+{
+	enum {
+		DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
+	};
+
+	::CURL *curl;
+	::curl_httppost *form;
+	::curl_httppost *form_end;
+	::curl_slist *headerlist;
+	std::string buffer;
+	size_t limit;
+
+	std::thread io_thread;
+	Http::CompleteFn completefn;
+	Http::ErrorFn errorfn;
+
+	priv(const std::string &url);
+	~priv();
+
+	static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
+	std::string body_size_error();
+	void http_perform();
+};
+
+Http::priv::priv(const std::string &url) :
+	curl(::curl_easy_init()),
+	form(nullptr),
+	form_end(nullptr),
+	headerlist(nullptr)
+{
+	if (curl == nullptr) {
+		throw std::runtime_error(std::string("Could not construct Curl object"));
+	}
+
+	::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());   // curl makes a copy internally
+	::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION);
+}
+
+Http::priv::~priv()
+{
+	::curl_easy_cleanup(curl);
+	::curl_formfree(form);
+	::curl_slist_free_all(headerlist);
+}
+
+size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
+{
+	auto self = static_cast<priv*>(userp);
+	const char *cdata = static_cast<char*>(data);
+	const size_t realsize = size * nmemb;
+
+	const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
+	if (self->buffer.size() + realsize > limit) {
+		// This makes curl_easy_perform return CURLE_WRITE_ERROR
+		return 0;
+	}
+
+	self->buffer.append(cdata, realsize);
+
+	return realsize;
+}
+
+std::string Http::priv::body_size_error()
+{
+	return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
+}
+
+void Http::priv::http_perform()
+{
+	::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
+	::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+	::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
+	::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
+
+#ifndef NDEBUG
+	::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+#endif
+
+	if (headerlist != nullptr) {
+		::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
+	}
+
+	if (form != nullptr) {
+		::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
+	}
+
+	CURLcode res = ::curl_easy_perform(curl);
+	long http_status = 0;
+	::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
+
+	if (res != CURLE_OK) {
+		std::string error;
+		if (res == CURLE_WRITE_ERROR) {
+			error = std::move(body_size_error());
+		} else {
+			error = ::curl_easy_strerror(res);
+		};
+
+		if (errorfn) {
+			errorfn(std::move(buffer), std::move(error), http_status);
+		}
+	} else {
+		if (completefn) {
+			completefn(std::move(buffer), http_status);
+		}
+	}
+}
+
+Http::Http(const std::string &url) : p(new priv(url)) {}
+
+
+// Public
+
+Http::Http(Http &&other) : p(std::move(other.p)) {}
+
+Http::~Http()
+{
+	if (p && p->io_thread.joinable()) {
+		p->io_thread.detach();
+	}
+}
+
+
+Http& Http::size_limit(size_t sizeLimit)
+{
+	if (p) { p->limit = sizeLimit; }
+	return *this;
+}
+
+Http& Http::header(std::string name, const std::string &value)
+{
+	if (!p) { return * this; }
+
+	if (name.size() > 0) {
+		name.append(": ").append(value);
+	} else {
+		name.push_back(':');
+	}
+	p->headerlist = curl_slist_append(p->headerlist, name.c_str());
+	return *this;
+}
+
+Http& Http::remove_header(std::string name)
+{
+	if (p) {
+		name.push_back(':');
+		p->headerlist = curl_slist_append(p->headerlist, name.c_str());
+	}
+
+	return *this;
+}
+
+Http& Http::ca_file(const std::string &name)
+{
+	if (p) {
+		::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
+	}
+
+	return *this;
+}
+
+Http& Http::form_add(const std::string &name, const std::string &contents)
+{
+	if (p) {
+		::curl_formadd(&p->form, &p->form_end,
+			CURLFORM_COPYNAME, name.c_str(),
+			CURLFORM_COPYCONTENTS, contents.c_str(),
+			CURLFORM_END
+		);
+	}
+
+	return *this;
+}
+
+Http& Http::form_add_file(const std::string &name, const std::string &filename)
+{
+	if (p) {
+		::curl_formadd(&p->form, &p->form_end,
+			CURLFORM_COPYNAME, name.c_str(),
+			CURLFORM_FILE, filename.c_str(),
+			CURLFORM_CONTENTTYPE, "application/octet-stream",
+			CURLFORM_END
+		);
+	}
+
+	return *this;
+}
+
+Http& Http::on_complete(CompleteFn fn)
+{
+	if (p) { p->completefn = std::move(fn); }
+	return *this;
+}
+
+Http& Http::on_error(ErrorFn fn)
+{
+	if (p) { p->errorfn = std::move(fn); }
+	return *this;
+}
+
+Http::Ptr Http::perform()
+{
+	auto self = std::make_shared<Http>(std::move(*this));
+
+	if (self->p) {
+		auto io_thread = std::thread([self](){
+				self->p->http_perform();
+			});
+		self->p->io_thread = std::move(io_thread);
+	}
+
+	return self;
+}
+
+void Http::perform_sync()
+{
+	if (p) { p->http_perform(); }
+}
+
+Http Http::get(std::string url)
+{
+	return std::move(Http{std::move(url)});
+}
+
+Http Http::post(std::string url)
+{
+	Http http{std::move(url)};
+	curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
+	return http;
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
new file mode 100644
index 000000000..c591e17c5
--- /dev/null
+++ b/xs/src/slic3r/Utils/Http.hpp
@@ -0,0 +1,53 @@
+#ifndef slic3r_Http_hpp_
+#define slic3r_Http_hpp_
+
+#include <memory>
+#include <string>
+#include <functional>
+
+
+namespace Slic3r {
+
+
+/// Represetns a Http request
+class Http : public std::enable_shared_from_this<Http> {
+private:
+	struct priv;
+public:
+	typedef std::shared_ptr<Http> Ptr;
+	typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
+	typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
+
+	Http(Http &&other);
+
+	static Http get(std::string url);
+	static Http post(std::string url);
+	~Http();
+
+	Http(const Http &) = delete;
+	Http& operator=(const Http &) = delete;
+	Http& operator=(Http &&) = delete;
+
+	Http& size_limit(size_t sizeLimit);
+	Http& header(std::string name, const std::string &value);
+	Http& remove_header(std::string name);
+	Http& ca_file(const std::string &filename);
+	Http& form_add(const std::string &name, const std::string &contents);
+	Http& form_add_file(const std::string &name, const std::string &filename);
+
+	Http& on_complete(CompleteFn fn);
+	Http& on_error(ErrorFn fn);
+
+	Ptr perform();
+	void perform_sync();
+
+private:
+	Http(const std::string &url);
+
+	std::unique_ptr<priv> p;
+};
+
+
+}
+
+#endif

From 751e86cd4de3e768e2a1f095ca396ca1b5a3c607 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 23 Feb 2018 16:48:11 +0100
Subject: [PATCH 2/9] libcurl linking and cmake usage improvements

---
 CMakeLists.txt                   | 15 +++++++++++++++
 cmake/msvc/slic3r.wperl64d.props |  9 +++++++++
 cmake/msvc/xs.wperl64d.props     | 11 +++++++++++
 xs/CMakeLists.txt                | 11 +++++++++++
 xs/src/xsinit.h                  |  9 +++++++++
 5 files changed, 55 insertions(+)
 create mode 100644 cmake/msvc/slic3r.wperl64d.props
 create mode 100644 cmake/msvc/xs.wperl64d.props

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43d7dee70..b5f7cdec2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,21 @@ message("PATH: $ENV{PATH}")
 message("PERL5LIB: $ENV{PERL5LIB}")
 find_package(Perl REQUIRED)
 
+# CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...)
+# We pick it from environment if it is not defined in another way
+if(NOT DEFINED CMAKE_PREFIX_PATH)
+	if(DEFINED ENV{CMAKE_PREFIX_PATH})
+		set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
+	endif()
+endif()
+
+if (MSVC)
+	# By default the startup project in MSVC is the 'ALL_BUILD' cmake-created project,
+	# but we want 'slic3r' as the startup one because debugging run command is associated with it.
+	# (Unfortunatelly it cannot be associated with ALL_BUILD using CMake.)
+	set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT slic3r)
+endif ()
+
 add_subdirectory(xs)
 
 enable_testing ()
diff --git a/cmake/msvc/slic3r.wperl64d.props b/cmake/msvc/slic3r.wperl64d.props
new file mode 100644
index 000000000..68dac2085
--- /dev/null
+++ b/cmake/msvc/slic3r.wperl64d.props
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <LocalDebuggerCommand>C:\wperl64d\bin\perl.exe</LocalDebuggerCommand>
+    <LocalDebuggerCommandArguments>slic3r.pl</LocalDebuggerCommandArguments>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+    <LocalDebuggerWorkingDirectory>..\..</LocalDebuggerWorkingDirectory>
+  </PropertyGroup>
+</Project>
diff --git a/cmake/msvc/xs.wperl64d.props b/cmake/msvc/xs.wperl64d.props
new file mode 100644
index 000000000..101923581
--- /dev/null
+++ b/cmake/msvc/xs.wperl64d.props
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets">
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <ExecutablePath>$(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);c:\wperl64d\bin\;</ExecutablePath>
+  </PropertyGroup>
+  <ItemDefinitionGroup />
+  <ItemGroup />
+</Project>
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index d2f252030..e4e0e1c6c 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -603,6 +603,17 @@ elseif (NOT MSVC)
     target_link_libraries(slic3r -lstdc++)
 endif ()
 
+if (MSVC)
+    # Here we associate some additional properties with the MSVC projects to enable compilation and debugging out of the box.
+    # It seems a props file needs to be copied to the same dir as the proj file, otherwise MSVC doesn't load it up.
+    # For copying, the configure_file() function seems to work much better than the file() function.
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+    set_target_properties(XS PROPERTIES VS_USER_PROPS "xs.wperl64d.props")
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/slic3r.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+    set_target_properties(slic3r PROPERTIES VS_USER_PROPS "slic3r.wperl64d.props")
+endif ()
+
+
 # Installation
 install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS)
 install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r)
diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h
index 49981b74b..96c4b74d7 100644
--- a/xs/src/xsinit.h
+++ b/xs/src/xsinit.h
@@ -59,6 +59,15 @@ extern "C" {
     #undef seek
     #undef send
     #undef write
+    #undef open
+    #undef close
+    #undef seekdir
+    #undef setbuf
+    #undef fread
+    #undef fseek
+    #undef fputc
+    #undef fwrite
+    #undef fclose
 #endif /* _MSC_VER */
 }
 #endif

From f67d70941eba392badbfc08cf38c69acfd8ef065 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Thu, 1 Mar 2018 16:14:36 +0100
Subject: [PATCH 3/9] Doc: Add dependency build scripts and building how-tos

---
 doc/How to build - UNIX.md                 |   2 +
 doc/How to build - Windows.md              |  86 +++++++++++++
 doc/deps-build/unix-static/Makefile        | 132 +++++++++++++++++++
 doc/deps-build/windows/slic3r-makedeps.ps1 | 141 +++++++++++++++++++++
 4 files changed, 361 insertions(+)
 create mode 100644 doc/How to build - UNIX.md
 create mode 100644 doc/How to build - Windows.md
 create mode 100644 doc/deps-build/unix-static/Makefile
 create mode 100644 doc/deps-build/windows/slic3r-makedeps.ps1

diff --git a/doc/How to build - UNIX.md b/doc/How to build - UNIX.md
new file mode 100644
index 000000000..77ce54419
--- /dev/null
+++ b/doc/How to build - UNIX.md	
@@ -0,0 +1,2 @@
+# Building Slic3r PE on Linux/UNIX
+
diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md
new file mode 100644
index 000000000..8209954bd
--- /dev/null
+++ b/doc/How to build - Windows.md	
@@ -0,0 +1,86 @@
+# Building Slic3r PE on Microsoft Windows
+
+The currently supported way of building Slic3r PE on Windows is with MS Visual Studio 2013
+using our Perl binary distribution (compiled from official Perl sources).
+You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/).
+
+Other setups (such as mingw + Strawberry Perl) _may_ work, but we cannot guarantee this will work
+and cannot provide guidance.
+
+
+### Geting the dependencies
+
+First, download and upnack our Perl + wxWidgets binary distribution:
+
+  - 32 bit, release mode: [wperl32-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl32-5.24.0-2018-03-02.7z)
+  - 64 bit, release mode: [wperl64-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64-5.24.0-2018-03-02.7z)
+  - 64 bit, release mode + debug symbols: [wperl64d-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64d-5.24.0-2018-03-02.7z)
+
+It is recommended to unpack this package into `C:\`.
+
+Apart from wxWidgets and Perl, you will also need additional dependencies:
+
+  - Boost
+  - Intel TBB
+  - libcurl
+
+We have prepared a binary package of the listed libraries:
+
+  - 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-32.7z)
+  - 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-64.7z)
+
+It is recommended you unpack this package into `C:\local\` as the environment
+setup script expects it there.
+
+Alternatively you can also compile the additional dependencies yourself.
+There is a [powershell script](./deps-build/windows/slic3r-makedeps.ps1) which automates this process.
+
+### Building Slic3r PE
+
+Once the dependencies are set up in their respective locations,
+go to the `wperl*` directory extracted earlier and launch the `cmdline.lnk` file
+which opens a command line prompt with appropriate environment variables set up.
+
+In this command line, `cd` into the directory with Slic3r sources
+and use these commands to build the Slic3r from the command line:
+
+    perl Build.PL
+    perl Build.PL --gui
+    mkdir build
+    cd build
+    cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
+    nmake
+    ctest --verbose    # TODO: ???
+    cd ..
+    perl slic3r.pl
+
+The above commands use `nmake` Makefiles.
+You may also build Slic3r PE with other build tools:
+
+
+### Building with Visual Studio
+
+To build, lanuch and/or debug Slic3r PE with Visual Studio (64 bits), replace the `cmake` command with:
+
+    cmake .. -G "Visual Studio 12 Win64" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b
+
+For the 32-bit variant, use:
+
+    cmake .. -G "Visual Studio 12" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b
+
+After `cmake` has finished, go to the `Slic3r\build` directory and open the `Slic3r.sln` solution file.
+This should open Visual Studio and load all the Slic3r solution containing all the projects.
+Make sure you use Visual Studio 2013 to open the solution.
+
+You can then use the usual Visual Studio controls to build Slic3r.
+If you want to run or debug Slic3r from within Visual Studio, make sure the `slic3r` project is activated.
+There are multiple projects in the Slic3r solution, but only the `slic3r` project is configured with the right
+commands to run Slic3r.
+
+
+### Building with ninja
+
+To use [Ninja](TODO), replace the `cmake` and `nmake` commands with:
+
+    cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
+    ninja
diff --git a/doc/deps-build/unix-static/Makefile b/doc/deps-build/unix-static/Makefile
new file mode 100644
index 000000000..e7588b994
--- /dev/null
+++ b/doc/deps-build/unix-static/Makefile
@@ -0,0 +1,132 @@
+
+#
+# This makefile downloads, configures and builds Slic3r PE dependencies for Unix.
+# (That is, all dependencies except perl + wxWidgets.)
+# The libraries are installed in DESTDIR, which you can customize like so:
+#
+#     DESTDIR=foo/bar make
+#
+# The default DESTDIR is ~/slic3r-destdir
+# If the DESTDIR doesn't exits, the makefile tries to create it
+#
+# To pass the DESTDIR path along to cmake, set the use CMAKE_PREFIX_PATH variable
+# and set it to $DESTDIR/usr/local
+#
+# You can also customize the NPROC variable in the same way to configure the number
+# of cores the build process uses. By default this is set to what the `nproc` command says.
+#
+
+
+DESTDIR ?= $(HOME)/slic3r-destdir
+NPROC ?= $(shell nproc)
+
+
+BOOST = boost_1_66_0
+TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b
+TBB = tbb-$(TBB_SHA)
+OPENSSL = openssl-OpenSSL_1_1_0g
+CURL = curl-7.58.0
+
+.PHONY: all destdir boost libcurl libopenssl libtbb
+
+all: destdir boost libtbb libcurl
+	@echo
+	@echo "All done!"
+	@echo
+
+destdir:
+	mkdir -p $(DESTDIR)
+
+
+
+boost: $(BOOST).tar.gz
+	tar -zxvf $(BOOST).tar.gz
+	cd $(BOOST) && ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex --prefix=$(DESTDIR)/usr/local
+	cd $(BOOST) && ./b2 \
+		-j $(NPROC) \
+		link=static \
+		variant=release \
+		threading=multi \
+		boost.locale.icu=off \
+		cxxflags=-fPIC cflags=-fPIC \
+		install
+
+$(BOOST).tar.gz:
+	curl -L -o $@ https://dl.bintray.com/boostorg/release/1.66.0/source/$@
+
+
+
+libtbb: $(TBB).tar.gz
+	tar -zxvf $(TBB).tar.gz
+	mkdir -p $(TBB)/mybuild
+	cd $(TBB)/mybuild && cmake .. -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON
+	$(MAKE) -C $(TBB)/mybuild -j$(NPROC)
+	$(MAKE) -C $(TBB)/mybuild install DESTDIR=$(DESTDIR)
+
+$(TBB).tar.gz:
+	curl -L -o $@ https://github.com/wjakob/tbb/archive/$(TBB_SHA).tar.gz
+
+
+
+libopenssl: $(OPENSSL).tar.gz
+	tar -zxvf $(OPENSSL).tar.gz
+	cd $(OPENSSL) && ./config --openssldir=/etc/ssl shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack'
+	make -C $(OPENSSL) depend
+	make -C $(OPENSSL) -j$(NPROC)
+	make -C $(OPENSSL) install DESTDIR=$(DESTDIR)
+
+$(OPENSSL).tar.gz:
+	curl -L -o $@ 'https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz'
+
+
+
+libcurl: libopenssl $(CURL).tar.gz
+	tar -zxvf $(CURL).tar.gz
+# XXX: disable shared?
+# Setting PKG_CONFIG_PATH should make libcurl find our previously built openssl
+	cd $(CURL) && PKG_CONFIG_PATH=$(DESTDIR)/usr/local/lib/pkgconfig ./configure \
+		--enable-static \
+		--enable-shared \
+		--with-pic \
+		--enable-ipv6 \
+		--enable-versioned-symbols \
+		--enable-threaded-resolver \
+		--with-random=/dev/urandom \
+		--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \
+		--disable-ldap \
+		--disable-ldaps \
+		--disable-manual \
+		--disable-rtsp \
+		--disable-dict \
+		--disable-telnet \
+		--disable-pop3 \
+		--disable-imap \
+		--disable-smb \
+		--disable-smtp \
+		--disable-gopher \
+		--disable-crypto-auth \
+		--without-gssapi \
+		--without-libpsl \
+		--without-libidn2 \
+		--without-gnutls \
+		--without-polarssl \
+		--without-mbedtls \
+		--without-cyassl \
+		--without-nss \
+		--without-axtls \
+		--without-brotli \
+		--without-libmetalink \
+		--without-libssh \
+		--without-libssh2 \
+		--without-librtmp \
+		--without-nghttp2 \
+		--without-zsh-functions-dir
+	$(MAKE) -C $(CURL) -j$(NPROC)
+	$(MAKE) -C $(CURL) install DESTDIR=$(DESTDIR)
+
+$(CURL).tar.gz:
+	curl -L -o $@ https://curl.haxx.se/download/$@
+
+
+clean:
+	rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz
diff --git a/doc/deps-build/windows/slic3r-makedeps.ps1 b/doc/deps-build/windows/slic3r-makedeps.ps1
new file mode 100644
index 000000000..8b39cae30
--- /dev/null
+++ b/doc/deps-build/windows/slic3r-makedeps.ps1
@@ -0,0 +1,141 @@
+#!powershell
+#
+# This script downloads, configures and builds Slic3r PE dependencies for Unix.
+# (That is, all dependencies except perl + wxWidgets.)
+#
+# To use this script, launch the Visual Studio command line,
+# `cd` into the directory containing this script and use this command:
+#
+#     powershell .\slic3r-makedeps.ps1
+#
+# The dependencies will be downloaded and unpacked into the current dir.
+# This script WILL NOT try to guess the build architecture (64 vs 32 bits),
+# it will by default build the 64-bit variant. To build the 32-bit variant, use:
+#
+#     powershell .\slic3r-makedeps.ps1 -b32
+#
+# Built libraries are installed into $destdir,
+# which by default is C:\local\slic3r-destdir-$bits
+# You can customize the $destdir using:
+#
+#     powershell .\slic3r-makedeps.ps1 -destdir C:\foo\bar
+#
+# To pass the $destdir path along to cmake, set the use CMAKE_PREFIX_PATH variable
+# and set it to $destdir\usr\local
+#
+# Script requirements: PowerShell 3.0, .NET 4.5
+#
+
+
+param(
+    [switch]$b32 = $false,
+    [string]$destdir = ""
+)
+
+if ($destdir -eq "") {
+    $destdir = "C:\local\slic3r-destdir-" + ('32', '64')[!$b32]
+}
+
+$BOOST = 'boost_1_63_0'
+$CURL = 'curl-7.28.0'
+$TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b'
+$TBB = "tbb-$TBB_SHA"
+
+
+try
+{
+
+
+# Set up various settings and utilities:
+[Environment]::CurrentDirectory = Get-Location
+$NPROC = (Get-WmiObject -class Win32_processor).NumberOfLogicalProcessors
+Add-Type -A System.IO.Compression.FileSystem
+#   This fxies SSL/TLS errors, credit goes to Ansible; see their `win_get_url.ps1` file
+$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
+if ([Net.SecurityProtocolType].GetMember('Tls11').Count -gt 0) {
+    $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
+}
+if ([Net.SecurityProtocolType].GetMember('Tls12').Count -gt 0) {
+    $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
+}
+[Net.ServicePointManager]::SecurityProtocol = $security_protcols
+$webclient = New-Object System.Net.WebClient
+
+
+# Ensure DESTDIR exists:
+mkdir $destdir -ea 0
+mkdir "$destdir\usr\local" -ea 0
+
+
+# Download sources:
+echo 'Downloading sources ...'
+if (!(Test-Path "$BOOST.zip")) { $webclient.DownloadFile("https://dl.bintray.com/boostorg/release/1.63.0/source/$BOOST.zip", "$BOOST.zip") }
+if (!(Test-Path "$TBB.zip")) { $webclient.DownloadFile("https://github.com/wjakob/tbb/archive/$TBB_SHA.zip", "$TBB.zip") }
+if (!(Test-Path "$CURL.zip")) { $webclient.DownloadFile("https://curl.haxx.se/download/$CURL.zip", ".\$CURL.zip") }
+
+
+# Unpack sources:
+echo 'Unpacking ...'
+if (!(Test-Path $BOOST)) { [IO.Compression.ZipFile]::ExtractToDirectory("$BOOST.zip", '.') }
+if (!(Test-Path $TBB)) { [IO.Compression.ZipFile]::ExtractToDirectory("$TBB.zip", '.') }
+if (!(Test-Path $CURL)) { [IO.Compression.ZipFile]::ExtractToDirectory("$CURL.zip", '.') }
+
+
+# Build libraries:
+echo 'Building ...'
+
+# Build boost
+pushd "$BOOST"
+.\bootstrap
+$adr_mode = ('32', '64')[!$b32]
+.\b2 `
+    -j "$NPROC" `
+    --with-system `
+    --with-filesystem `
+    --with-thread `
+    --with-log `
+    --with-locale `
+    --with-regex `
+    "--prefix=$destdir/usr/local" `
+    "address-model=$adr_mode" `
+    toolset=msvc-12.0 `
+    link=static `
+    variant=release `
+    threading=multi `
+    boost.locale.icu=off `
+    install
+popd
+
+# Build TBB
+pushd "$TBB"
+mkdir 'mybuild' -ea 0
+cd 'mybuild'
+$generator = ('Visual Studio 12', 'Visual Studio 12 Win64')[!$b32]
+cmake .. `
+    -G "$generator" `
+    -DCMAKE_CONFIGURATION_TYPES=Release `
+    -DTBB_BUILD_SHARED=OFF `
+    -DTBB_BUILD_TESTS=OFF "-DCMAKE_INSTALL_PREFIX:PATH=$destdir\usr\local"
+msbuild /P:Configuration=Release INSTALL.vcxproj
+popd
+
+# Build libcurl:
+pushd "$CURL\winbuild"
+$machine = ("x86", "x64")[!$b32]
+nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=$machine"
+Copy-Item -R -Force ..\builds\libcurl-*-winssl\include\* "$destdir\usr\local\include\"
+Copy-Item -R -Force ..\builds\libcurl-*-winssl\lib\* "$destdir\usr\local\lib\"
+popd
+
+
+echo ""
+echo "All done!"
+echo ""
+
+
+}
+catch [Exception]
+{
+    # This prints errors in a verbose manner
+    echo $_.Exception|format-list -force
+}

From b897209e0d10d1c96767cc0f2709a1eaa7a7046c Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Fri, 2 Mar 2018 21:16:00 +0100
Subject: [PATCH 4/9] Removed -DCURL_STATIC on OSX, added dynamic linking of
 OpenSSL on Linux, even if libcurl is linked statically.

---
 xs/CMakeLists.txt | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index e4e0e1c6c..a294dc11e 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -528,6 +528,15 @@ find_package(CURL REQUIRED)
 include_directories(${CURL_INCLUDE_DIRS})
 target_link_libraries(XS ${CURL_LIBRARIES})
 
+if (SLIC3R_STATIC AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    # As of now, our build system produces a statically linked libcurl,
+    # which links the OpenSSL library dynamically.
+    find_package(OpenSSL REQUIRED)
+    message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
+    message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
+    include_directories(${OPENSSL_INCLUDE_DIR})
+    target_link_libraries(XS ${OPENSSL_LIBRARIES})
+endif()
 
 ## OPTIONAL packages
 

From 79ee7c9a36b4901804e87ecb17dd6821a23ba258 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 5 Mar 2018 18:32:09 +0100
Subject: [PATCH 5/9] Fix #608  Credit: Dylan "smellyfis" Thies

---
 xs/CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index a294dc11e..06456d607 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -624,5 +624,5 @@ endif ()
 
 
 # Installation
-install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS)
-install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r)
+install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS)
+install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r)

From 7cfc5204c85c68e07ca351ac810a7f14a8e65eaf Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Wed, 7 Feb 2018 11:37:15 +0100
Subject: [PATCH 6/9] WIP: OctoPrint

---
 lib/Slic3r/GUI/MainFrame.pm       |  12 +++-
 lib/Slic3r/GUI/Plater.pm          |  48 ++------------
 xs/CMakeLists.txt                 |   3 +
 xs/src/libslic3r/PrintConfig.cpp  |  11 +++-
 xs/src/libslic3r/PrintConfig.hpp  |   2 +
 xs/src/perlglue.cpp               |   1 +
 xs/src/slic3r/GUI/GUI.cpp         |  16 ++++-
 xs/src/slic3r/GUI/GUI.hpp         |   3 +
 xs/src/slic3r/GUI/Preset.cpp      |   2 +-
 xs/src/slic3r/Utils/OctoPrint.cpp | 105 ++++++++++++++++++++++++++++++
 xs/src/slic3r/Utils/OctoPrint.hpp |  35 ++++++++++
 xs/xsp/Utils_OctoPrint.xsp        |  14 ++++
 xs/xsp/my.map                     |   4 ++
 13 files changed, 210 insertions(+), 46 deletions(-)
 create mode 100644 xs/src/slic3r/Utils/OctoPrint.cpp
 create mode 100644 xs/src/slic3r/Utils/OctoPrint.hpp
 create mode 100644 xs/xsp/Utils_OctoPrint.xsp

diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 572bdac32..034cc2dd5 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -409,7 +409,15 @@ sub _init_menubar {
             wxTheApp->about;
         });
     }
-    
+
+    my $hokusMenu = Wx::Menu->new;    # XXX: tmp
+    {
+        $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub {
+            # Slic3r::Http::download();
+            Slic3r::OctoPrint::send_gcode("10.0.0.46", "70E4CFD0E0D7423CB6B1CF055DBAEFA5", "/home/vojta/prog/tisk/jesterka/jesterka.gcode");
+        });
+    }
+
     # menubar
     # assign menubar to frame after appending items, otherwise special items
     # will not be handled correctly
@@ -424,6 +432,8 @@ sub _init_menubar {
         # (Select application language from the list of installed languages)
         Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event});
         $menubar->Append($helpMenu, L("&Help"));
+        $menubar->Append($hokusMenu, "Hoku&s");
+        # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing.
         $self->SetMenuBar($menubar);
     }
 }
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 5cedfbb08..d48e31462 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -52,7 +52,7 @@ sub new {
     my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
     $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
         bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
-        serial_port serial_speed octoprint_host octoprint_apikey
+        serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
         nozzle_diameter single_extruder_multi_material 
         wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour
     )]);
@@ -1458,8 +1458,13 @@ sub on_export_completed {
     wxTheApp->notify($message);
     
     $self->do_print if $do_print;
+
     # Send $self->{send_gcode_file} to OctoPrint.
-    $self->send_gcode if $send_gcode;
+    if ($send_gcode) {
+        my $op = Slic3r::OctoPrint->new($self->{config});
+        $op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file});
+    }
+
     $self->{print_file} = undef;
     $self->{send_gcode_file} = undef;
     $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost));
@@ -1488,45 +1493,6 @@ sub do_print {
     my $filament_names = wxTheApp->{preset_bundle}->filament_presets;
     $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats };
     $printer_panel->load_print_job($self->{print_file}, $filament_stats);
-    
-    $self->GetFrame->select_tab(1);
-}
-
-# Send $self->{send_gcode_file} to OctoPrint.
-#FIXME Currently this call blocks the UI. Make it asynchronous.
-sub send_gcode {
-    my ($self) = @_;
-    
-    $self->statusbar->StartBusy;
-    
-    my $ua = LWP::UserAgent->new;
-    $ua->timeout(180);
-    
-    my $res = $ua->post(
-        "http://" . $self->{config}->octoprint_host . "/api/files/local",
-        Content_Type => 'form-data',
-        'X-Api-Key' => $self->{config}->octoprint_apikey,
-        Content => [
-            file => [ 
-                # On Windows, the path has to be encoded in local code page for perl to be able to open it.
-                Slic3r::encode_path($self->{send_gcode_file}),
-                # Remove the UTF-8 flag from the perl string, so the LWP::UserAgent can insert 
-                # the UTF-8 encoded string into the request as a byte stream.
-                Slic3r::path_to_filename_raw($self->{send_gcode_file})
-            ],
-            print => $self->{send_gcode_file_print} ? 1 : 0,
-        ],
-    );
-    
-    $self->statusbar->StopBusy;
-    
-    if ($res->is_success) {
-        $self->statusbar->SetStatusText(L("G-code file successfully uploaded to the OctoPrint server"));
-    } else {
-        my $message = L("Error while uploading to the OctoPrint server: ") . $res->status_line;
-        Slic3r::GUI::show_error($self, $message);
-        $self->statusbar->SetStatusText($message);
-    }
 }
 
 sub export_stl {
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 06456d607..b73f8336d 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -201,6 +201,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/wxExtensions.hpp
     ${LIBDIR}/slic3r/Utils/Http.cpp
     ${LIBDIR}/slic3r/Utils/Http.hpp
+    ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
+    ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
 )
 
 add_library(admesh STATIC
@@ -340,6 +342,7 @@ set(XS_XSP_FILES
     ${XSP_DIR}/Surface.xsp
     ${XSP_DIR}/SurfaceCollection.xsp
     ${XSP_DIR}/TriangleMesh.xsp
+    ${XSP_DIR}/Utils_OctoPrint.xsp
     ${XSP_DIR}/XS.xsp
 )
 foreach (file ${XS_XSP_FILES})
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index 4077668f8..2b95ffa84 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -904,10 +904,17 @@ PrintConfigDef::PrintConfigDef()
     def->cli = "octoprint-apikey=s";
     def->default_value = new ConfigOptionString("");
     
+    def = this->add("octoprint_cafile", coString);
+    def->label = "HTTPS CA file";
+    def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
+                   "If left blank, the default OS CA certificate repository is used.";
+    def->cli = "octoprint-cafile=s";
+    def->default_value = new ConfigOptionString("");
+
     def = this->add("octoprint_host", coString);
-    def->label = L("Host or IP");
+    def->label = L("Hostname, IP or URL");
     def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
-                   "the hostname or IP address of the OctoPrint instance.");
+                   "the hostname, IP address or URL of the OctoPrint instance.");
     def->cli = "octoprint-host=s";
     def->default_value = new ConfigOptionString("");
 
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index 7d2fb0145..c589d917a 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -684,6 +684,7 @@ class HostConfig : public StaticPrintConfig
 public:
     ConfigOptionString              octoprint_host;
     ConfigOptionString              octoprint_apikey;
+    ConfigOptionString              octoprint_cafile;
     ConfigOptionString              serial_port;
     ConfigOptionInt                 serial_speed;
     
@@ -692,6 +693,7 @@ protected:
     {
         OPT_PTR(octoprint_host);
         OPT_PTR(octoprint_apikey);
+        OPT_PTR(octoprint_cafile);
         OPT_PTR(serial_port);
         OPT_PTR(serial_speed);
     }
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index cb2ef7368..d7c9a590a 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
 REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
 REGISTER_CLASS(PresetHints, "GUI::PresetHints");
 REGISTER_CLASS(TabIface, "GUI::Tab");
+REGISTER_CLASS(OctoPrint, "OctoPrint");
 
 SV* ConfigBase__as_hash(ConfigBase* THIS)
 {
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index b0d3f7629..f053d7b16 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -5,9 +5,9 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
-
 #include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/classification.hpp>
+#include <boost/format.hpp>
 
 #if __APPLE__
 #import <IOKit/pwr_mgt/IOPMLib.h>
@@ -570,4 +570,18 @@ wxString from_u8(std::string str)
 	return wxString::FromUTF8(str.c_str());
 }
 
+wxWindow *get_widget_by_id(int id)
+{
+    if (g_wxMainFrame == nullptr) {
+        throw std::runtime_error("Main frame not set");
+    }
+
+    wxWindow *window = g_wxMainFrame->FindWindow(id);
+    if (window == nullptr) {
+        throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str());
+    }
+
+    return window;
+}
+
 } }
diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp
index 3e519b691..e913a9450 100644
--- a/xs/src/slic3r/GUI/GUI.hpp
+++ b/xs/src/slic3r/GUI/GUI.hpp
@@ -6,6 +6,7 @@
 #include "Config.hpp"
 
 class wxApp;
+class wxWindow;
 class wxFrame;
 class wxWindow;
 class wxMenuBar;
@@ -118,6 +119,8 @@ wxString	L_str(std::string str);
 // Return wxString from std::string in UTF8
 wxString	from_u8(std::string str);
 
+wxWindow *get_widget_by_id(int id);
+
 }
 }
 
diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
index c28c989fb..52717e1fc 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -224,7 +224,7 @@ const std::vector<std::string>& Preset::printer_options()
     if (s_opts.empty()) {
         s_opts = {
             "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", 
-            "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", 
+            "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
             "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
             "between_objects_gcode", "printer_notes"
         };
diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
new file mode 100644
index 000000000..019a4e238
--- /dev/null
+++ b/xs/src/slic3r/Utils/OctoPrint.cpp
@@ -0,0 +1,105 @@
+#include "OctoPrint.hpp"
+
+#include <iostream>
+#include <boost/format.hpp>
+
+#include <wx/frame.h>
+#include <wx/event.h>
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "Http.hpp"
+
+
+namespace Slic3r {
+
+
+OctoPrint::OctoPrint(DynamicPrintConfig *config) :
+    host(config->opt_string("octoprint_host")),
+    apikey(config->opt_string("octoprint_apikey")),
+    cafile(config->opt_string("octoprint_cafile"))
+{}
+
+std::string  OctoPrint::test() const
+{
+    // Since the request is performed synchronously here,
+    // it is ok to refer to `res` from within the closure
+    std::string res;
+
+    auto http = Http::get(std::move(make_url("api/version")));
+    set_auth(http);
+    http.on_error([&](std::string, std::string error, unsigned status) {
+            res = format_error(error, status);
+        })
+        .perform_sync();
+
+    return res;
+}
+
+void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const
+{
+    auto http = Http::post(std::move(make_url("api/files/local")));
+    set_auth(http);
+    http.form_add("print", print ? "true" : "false")
+        .form_add_file("file", filename)
+        .on_complete([=](std::string body, unsigned status) {
+            wxWindow *window = GUI::get_widget_by_id(windowId);
+            wxCommandEvent* evt = new wxCommandEvent(completeEvt);
+            evt->SetString("G-code file successfully uploaded to the OctoPrint server");
+            evt->SetInt(100);
+            wxQueueEvent(window, evt);
+        })
+        .on_error([=](std::string body, std::string error, unsigned status) {
+            wxWindow *window = GUI::get_widget_by_id(windowId);
+
+            wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
+            evt_complete->SetInt(100);
+            wxQueueEvent(window, evt_complete);
+
+            wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
+            evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
+            wxQueueEvent(window, evt_error);
+        })
+        .perform();
+}
+
+void OctoPrint::set_auth(Http &http) const
+{
+    http.header("X-Api-Key", apikey);
+
+    if (! cafile.empty()) {
+        http.ca_file(cafile);
+    }
+}
+
+std::string OctoPrint::make_url(const std::string &path) const
+{
+    if (host.find("http://") == 0 || host.find("https://") == 0) {
+        if (host.back() == '/') {
+            return std::move((boost::format("%1%%2%") % host % path).str());
+        } else {
+            return std::move((boost::format("%1%/%2%") % host % path).str());
+        }
+    } else {
+        return std::move((boost::format("http://%1%/%2%") % host % path).str());
+    }
+}
+
+std::string OctoPrint::format_error(std::string error, unsigned status)
+{
+    if (status != 0) {
+        std::string res{"HTTP "};
+        res.append(std::to_string(status));
+
+        if (status == 401) {
+            res.append(": Invalid API key");
+        }
+
+        return std::move(res);
+    } else {
+        return std::move(error);
+    }
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
new file mode 100644
index 000000000..c5ed70ab5
--- /dev/null
+++ b/xs/src/slic3r/Utils/OctoPrint.hpp
@@ -0,0 +1,35 @@
+#ifndef slic3r_OctoPrint_hpp_
+#define slic3r_OctoPrint_hpp_
+
+#include <string>
+
+#include "Http.hpp"
+
+namespace Slic3r {
+
+
+class DynamicPrintConfig;
+class Http;
+
+class OctoPrint
+{
+public:
+    OctoPrint(DynamicPrintConfig *config);
+
+    std::string test() const;
+    // XXX: style
+    void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
+private:
+    std::string host;
+    std::string apikey;
+    std::string cafile;
+
+    void set_auth(Http &http) const;
+    std::string make_url(const std::string &path) const;
+    static std::string format_error(std::string error, unsigned status);
+};
+
+
+}
+
+#endif
diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp
new file mode 100644
index 000000000..062af4e0c
--- /dev/null
+++ b/xs/xsp/Utils_OctoPrint.xsp
@@ -0,0 +1,14 @@
+%module{Slic3r::XS};
+
+%{
+#include <xsinit.h>
+#include "slic3r/Utils/OctoPrint.hpp"
+%}
+
+%name{Slic3r::OctoPrint} class OctoPrint {
+    OctoPrint(DynamicPrintConfig *config);
+    ~OctoPrint();
+
+    std::string test() const;
+    void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
+};
diff --git a/xs/xsp/my.map b/xs/xsp/my.map
index e54601632..87a8d8d86 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -236,6 +236,10 @@ Ref<PresetHints>    		O_OBJECT_SLIC3R_T
 TabIface*	   				O_OBJECT_SLIC3R
 Ref<TabIface> 				O_OBJECT_SLIC3R_T
 
+OctoPrint*                  O_OBJECT_SLIC3R
+Ref<OctoPrint>              O_OBJECT_SLIC3R_T
+Clone<OctoPrint>            O_OBJECT_SLIC3R_T
+
 Axis                  T_UV
 ExtrusionLoopRole     T_UV
 ExtrusionRole     T_UV

From fc05eb898de57bfbd845b5a32261b8702cbf0083 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Tue, 20 Feb 2018 15:37:26 +0100
Subject: [PATCH 7/9] WIP: Bonjour

---
 lib/Slic3r/GUI/MainFrame.pm       |   3 +-
 xs/CMakeLists.txt                 |   2 +
 xs/src/slic3r/Utils/Bonjour.cpp   | 602 ++++++++++++++++++++++++++++++
 xs/src/slic3r/Utils/Bonjour.hpp   |  39 ++
 xs/src/slic3r/Utils/OctoPrint.cpp | 114 +++---
 xs/src/slic3r/Utils/OctoPrint.hpp |  20 +-
 6 files changed, 711 insertions(+), 69 deletions(-)
 create mode 100644 xs/src/slic3r/Utils/Bonjour.cpp
 create mode 100644 xs/src/slic3r/Utils/Bonjour.hpp

diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 034cc2dd5..ee5ec7cb5 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -413,8 +413,7 @@ sub _init_menubar {
     my $hokusMenu = Wx::Menu->new;    # XXX: tmp
     {
         $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub {
-            # Slic3r::Http::download();
-            Slic3r::OctoPrint::send_gcode("10.0.0.46", "70E4CFD0E0D7423CB6B1CF055DBAEFA5", "/home/vojta/prog/tisk/jesterka/jesterka.gcode");
+            Slic3r::Http::pokus();
         });
     }
 
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index b73f8336d..d8ef51caa 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -203,6 +203,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/Utils/Http.hpp
     ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
     ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
+    ${LIBDIR}/slic3r/Utils/Bonjour.cpp
+    ${LIBDIR}/slic3r/Utils/Bonjour.hpp
 )
 
 add_library(admesh STATIC
diff --git a/xs/src/slic3r/Utils/Bonjour.cpp b/xs/src/slic3r/Utils/Bonjour.cpp
new file mode 100644
index 000000000..d7fb30e64
--- /dev/null
+++ b/xs/src/slic3r/Utils/Bonjour.cpp
@@ -0,0 +1,602 @@
+#include "Bonjour.hpp"
+
+#include <cstdint>
+#include <algorithm>
+#include <unordered_map>
+#include <array>
+#include <vector>
+#include <string>
+#include <random>
+#include <thread>
+#include <boost/optional.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/endian/conversion.hpp>
+#include <boost/asio.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+#include <boost/format.hpp>
+
+using boost::optional;
+using boost::system::error_code;
+namespace endian = boost::endian;
+namespace asio = boost::asio;
+using boost::asio::ip::udp;
+
+
+// TODO: Fuzzing test
+
+namespace Slic3r {
+
+
+// Miniman implementation of a MDNS client
+// This implementation is extremely simple, only the bits that are useful
+// for very basic MDNS discovery are present.
+
+struct DnsName: public std::string
+{
+	enum
+	{
+		MAX_RECURSION = 10,     // Keep this low
+	};
+
+	static optional<DnsName> decode(const std::vector<char> &buffer, ptrdiff_t &offset, unsigned depth = 0)
+	{
+		// We trust that the offset passed is bounds-checked properly,
+		// including that there is at least one byte beyond that offset.
+		// Any further arithmetic has to be bounds-checked here though.
+
+		// 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 ptrdiff_t bsize = buffer.size();
+
+		while (true) {
+			const char* ptr = buffer.data() + offset;
+			char len = *ptr;
+			if (len & 0xc0) {
+				// This is a recursive label
+				ptrdiff_t pointer = (len & 0x3f) << 8 | ptr[1];
+				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<char> &buffer) {
+		DnsHeader res;
+		const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(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<DnsQuestion> decode(const std::vector<char> &buffer, ptrdiff_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<const uint16_t*>(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<char> data;
+
+	DnsResource() :
+		type(0),
+		rclass(0),
+		ttl(0)
+	{}
+
+	static optional<DnsResource> decode(const std::vector<char> &buffer, ptrdiff_t &offset, ptrdiff_t &dataoffset)
+	{
+		auto rname = DnsName::decode(buffer, offset);
+		if (!rname) {
+			return boost::none;
+		}
+
+		const ptrdiff_t bsize = buffer.size();
+
+		if (offset + 10 >= bsize) {
+			return boost::none;
+		}
+
+		DnsResource res;
+		res.name = std::move(*rname);
+		const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(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<const uint32_t*>(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::move(std::vector<char>(buffer.begin() + offset, buffer.begin() + offset + rdlength));
+		offset += rdlength;
+
+		return std::move(res);
+	}
+};
+
+struct DnsRR_A
+{
+	enum { TAG = 0x1 };
+
+	asio::ip::address_v4 ip;
+
+	static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
+	{
+		if (rr.data.size() == 4) {
+			DnsRR_A res;
+			const uint32_t ip = endian::big_to_native(*reinterpret_cast<const uint32_t*>(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;
+
+	static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
+	{
+		if (rr.data.size() == 16) {
+			DnsRR_AAAA res;
+			std::array<unsigned char, 16> 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;
+	std::string name;
+	std::string service;
+	DnsName hostname;
+
+	static void decode(std::vector<DnsRR_SRV> &results, const std::vector<char> &buffer, const DnsResource &rr, ptrdiff_t dataoffset)
+	{
+		if (rr.data.size() < MIN_SIZE) {
+			return;
+		}
+
+		DnsRR_SRV res;
+
+		{
+			const auto dot_pos = rr.name.find_first_of('.');
+			if (dot_pos > 0 && dot_pos < rr.name.size() - 1) {
+				res.name = rr.name.substr(0, dot_pos);
+				res.service = rr.name.substr(dot_pos + 1);
+			} else {
+				return;
+			}
+		}
+
+		const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(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]);
+
+		ptrdiff_t offset = dataoffset + 6;
+		auto hostname = DnsName::decode(buffer, offset);
+
+		if (hostname) {
+			res.hostname = std::move(*hostname);
+			results.emplace_back(std::move(res));
+		}
+	}
+};
+
+struct DnsMessage
+{
+	enum
+	{
+		MAX_SIZE = 4096,
+		MAX_ANS = 30,
+	};
+
+	DnsHeader header;
+	optional<DnsQuestion> question;
+	std::vector<DnsResource> answers;
+
+	optional<DnsRR_A> rr_a;
+	optional<DnsRR_AAAA> rr_aaaa;
+	std::vector<DnsRR_SRV> rr_srv;
+
+	static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
+	{
+		const auto size = buffer.size();
+		if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
+			return boost::none;
+		}
+
+		DnsMessage res;
+		res.header = DnsHeader::decode(buffer);
+
+		if (id_wanted && *id_wanted != res.header.id) {
+			return boost::none;
+		}
+
+		if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) {
+			return boost::none;
+		}
+
+		ptrdiff_t offset = DnsHeader::SIZE;
+		if (res.header.qdcount == 1) {
+			res.question = DnsQuestion::decode(buffer, offset);
+		}
+
+		for (unsigned i = 0; i < res.header.rrcount(); i++) {
+			ptrdiff_t dataoffset = 0;
+			auto rr = DnsResource::decode(buffer, offset, dataoffset);
+			if (!rr) {
+				return boost::none;
+			} else {
+				res.parse_rr(buffer, *rr, dataoffset);
+				res.answers.push_back(std::move(*rr));
+			}
+		}
+
+		return std::move(res);
+	}
+private:
+	void parse_rr(const std::vector<char> &buffer, const DnsResource &rr, ptrdiff_t dataoffset)
+	{
+		switch (rr.type) {
+			case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
+			case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
+			case DnsRR_SRV::TAG: DnsRR_SRV::decode(this->rr_srv, buffer, rr, dataoffset); break;
+		}
+	}
+};
+
+
+struct BonjourRequest
+{
+	static const asio::ip::address_v4 MCAST_IP4;
+	static const uint16_t MCAST_PORT;
+
+	static const char rq_template[];
+
+	uint16_t id;
+	std::vector<char> data;
+
+	static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
+
+private:
+	BonjourRequest(uint16_t id, std::vector<char> &&data) :
+		id(id),
+		data(std::move(data))
+	{}
+};
+
+const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
+const uint16_t BonjourRequest::MCAST_PORT = 5353;
+
+const char BonjourRequest::rq_template[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x5f, 0x68, 0x74,
+	0x74, 0x70, 0x04, 0x5f, 0x74, 0x63, 0x70, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c,
+	0x00, 0x01,
+};
+
+optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
+{
+	if (service.size() > 15 || protocol.size() > 15) {
+		return boost::none;
+	}
+
+	std::random_device dev;
+	std::uniform_int_distribution<uint16_t> dist;
+	uint16_t id = dist(dev);
+	uint16_t id_big = endian::native_to_big(id);
+	const char *id_char = reinterpret_cast<char*>(&id_big);
+
+	std::vector<char> data;
+	data.reserve(service.size() + 18);
+
+	// Add the transaction ID
+	data.push_back(id_char[0]);
+	data.push_back(id_char[1]);
+
+	// Add metadata
+	static const char rq_meta[] = {
+		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	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 char ptr_tail[] = {
+		0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01,
+	};
+	std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
+
+	return BonjourRequest(id, std::move(data));
+}
+
+
+// API - private part
+
+struct Bonjour::priv
+{
+	const std::string service;
+	const std::string protocol;
+	const std::string service_dn;
+	unsigned timeout;
+	uint16_t rq_id;
+
+	std::vector<char> buffer;
+	std::thread io_thread;
+	Bonjour::ReplyFn replyfn;
+	Bonjour::CompleteFn completefn;
+
+	priv(std::string service, std::string protocol);
+
+	void udp_receive(const error_code &error, size_t bytes);
+	void lookup_perform();
+};
+
+Bonjour::priv::priv(std::string service, std::string protocol) :
+	service(std::move(service)),
+	protocol(std::move(protocol)),
+	service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
+	timeout(10),
+	rq_id(0)
+{
+	buffer.resize(DnsMessage::MAX_SIZE);
+}
+
+void Bonjour::priv::udp_receive(const error_code &error, size_t bytes)
+{
+	if (error || bytes == 0 || !replyfn) {
+		return;
+	}
+
+	buffer.resize(bytes);
+	const auto dns_msg = DnsMessage::decode(buffer, rq_id);
+	if (dns_msg) {
+		std::string ip;
+		if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip.to_string(); }
+		else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip.to_string(); }
+
+		for (const auto &srv : dns_msg->rr_srv) {
+			if (srv.service == service_dn) {
+				replyfn(std::move(ip), std::move(srv.hostname), std::move(srv.name));
+			}
+		}
+	}
+}
+
+void Bonjour::priv::lookup_perform()
+{
+	const auto brq = BonjourRequest::make(service, protocol);
+	if (!brq) {
+		return;
+	}
+
+	auto self = this;
+	rq_id = brq->id;
+
+	try {
+		boost::asio::io_service io_service;
+		udp::socket socket(io_service);
+		socket.open(udp::v4());
+		socket.set_option(udp::socket::reuse_address(true));
+		udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
+		socket.send_to(asio::buffer(brq->data), mcast);
+
+		bool timeout = false;
+		asio::deadline_timer timer(io_service);
+		timer.expires_from_now(boost::posix_time::seconds(10));
+		timer.async_wait([=, &timeout](const error_code &error) {
+			timeout = true;
+			if (self->completefn) {
+				self->completefn();
+			}
+		});
+
+		const auto recv_handler = [=](const error_code &error, size_t bytes) {
+			self->udp_receive(error, bytes);
+		};
+		socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler);
+
+		while (io_service.run_one()) {
+			if (timeout) {
+				socket.cancel();
+			} else {
+				buffer.resize(DnsMessage::MAX_SIZE);
+				socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler);
+			}
+		}
+	} catch (std::exception& e) {
+	}
+}
+
+
+// API - public part
+
+Bonjour::Bonjour(std::string service, std::string protocol) :
+	p(new priv(std::move(service), std::move(protocol)))
+{}
+
+Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {}
+
+Bonjour::~Bonjour()
+{
+	if (p && p->io_thread.joinable()) {
+		p->io_thread.detach();
+	}
+}
+
+Bonjour& Bonjour::set_timeout(unsigned timeout)
+{
+	if (p) { p->timeout = timeout; }
+	return *this;
+}
+
+Bonjour& Bonjour::on_reply(ReplyFn fn)
+{
+	if (p) { p->replyfn = std::move(fn); }
+	return *this;
+}
+
+Bonjour& Bonjour::on_complete(CompleteFn fn)
+{
+	if (p) { p->completefn = std::move(fn); }
+	return *this;
+}
+
+Bonjour::Ptr Bonjour::lookup()
+{
+	auto self = std::make_shared<Bonjour>(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;
+}
+
+
+void Bonjour::pokus()   // XXX
+{
+	// auto bonjour = Bonjour("http")
+	//     .set_timeout(15)
+	//     .on_reply([](std::string ip, std::string host, std::string service_name) {
+	//         std::cerr << "MDNS: " << ip << " = " << host << " : " << service_name << std::endl;
+	//     })
+	//     .on_complete([](){
+	//         std::cerr << "MDNS lookup complete" << std::endl;
+	//     })
+	//     .lookup();
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/Bonjour.hpp b/xs/src/slic3r/Utils/Bonjour.hpp
new file mode 100644
index 000000000..039db3370
--- /dev/null
+++ b/xs/src/slic3r/Utils/Bonjour.hpp
@@ -0,0 +1,39 @@
+#ifndef slic3r_Bonjour_hpp_
+#define slic3r_Bonjour_hpp_
+
+#include <memory>
+#include <string>
+#include <functional>
+
+
+namespace Slic3r {
+
+
+/// Bonjour lookup
+class Bonjour : public std::enable_shared_from_this<Bonjour> {
+private:
+	struct priv;
+public:
+	typedef std::shared_ptr<Bonjour> Ptr;
+	typedef std::function<void(std::string /* IP */, std::string /* host */, std::string /* service_name */)> ReplyFn;
+	typedef std::function<void()> CompleteFn;
+
+	Bonjour(std::string service, std::string protocol = "tcp");
+	Bonjour(Bonjour &&other);
+	~Bonjour();
+
+	Bonjour& set_timeout(unsigned timeout);
+	Bonjour& on_reply(ReplyFn fn);
+	Bonjour& on_complete(CompleteFn fn);
+
+	Ptr lookup();
+
+	static void pokus();    // XXX: remove
+private:
+	std::unique_ptr<priv> p;
+};
+
+
+}
+
+#endif
diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
index 019a4e238..58530833b 100644
--- a/xs/src/slic3r/Utils/OctoPrint.cpp
+++ b/xs/src/slic3r/Utils/OctoPrint.cpp
@@ -15,90 +15,90 @@ namespace Slic3r {
 
 
 OctoPrint::OctoPrint(DynamicPrintConfig *config) :
-    host(config->opt_string("octoprint_host")),
-    apikey(config->opt_string("octoprint_apikey")),
-    cafile(config->opt_string("octoprint_cafile"))
+	host(config->opt_string("octoprint_host")),
+	apikey(config->opt_string("octoprint_apikey")),
+	cafile(config->opt_string("octoprint_cafile"))
 {}
 
 std::string  OctoPrint::test() const
 {
-    // Since the request is performed synchronously here,
-    // it is ok to refer to `res` from within the closure
-    std::string res;
+	// Since the request is performed synchronously here,
+	// it is ok to refer to `res` from within the closure
+	std::string res;
 
-    auto http = Http::get(std::move(make_url("api/version")));
-    set_auth(http);
-    http.on_error([&](std::string, std::string error, unsigned status) {
-            res = format_error(error, status);
-        })
-        .perform_sync();
+	auto http = Http::get(std::move(make_url("api/version")));
+	set_auth(http);
+	http.on_error([&](std::string, std::string error, unsigned status) {
+			res = format_error(error, status);
+		})
+		.perform_sync();
 
-    return res;
+	return res;
 }
 
 void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const
 {
-    auto http = Http::post(std::move(make_url("api/files/local")));
-    set_auth(http);
-    http.form_add("print", print ? "true" : "false")
-        .form_add_file("file", filename)
-        .on_complete([=](std::string body, unsigned status) {
-            wxWindow *window = GUI::get_widget_by_id(windowId);
-            wxCommandEvent* evt = new wxCommandEvent(completeEvt);
-            evt->SetString("G-code file successfully uploaded to the OctoPrint server");
-            evt->SetInt(100);
-            wxQueueEvent(window, evt);
-        })
-        .on_error([=](std::string body, std::string error, unsigned status) {
-            wxWindow *window = GUI::get_widget_by_id(windowId);
+	auto http = Http::post(std::move(make_url("api/files/local")));
+	set_auth(http);
+	http.form_add("print", print ? "true" : "false")
+		.form_add_file("file", filename)
+		.on_complete([=](std::string body, unsigned status) {
+			wxWindow *window = GUI::get_widget_by_id(windowId);
+			wxCommandEvent* evt = new wxCommandEvent(completeEvt);
+			evt->SetString("G-code file successfully uploaded to the OctoPrint server");
+			evt->SetInt(100);
+			wxQueueEvent(window, evt);
+		})
+		.on_error([=](std::string body, std::string error, unsigned status) {
+			wxWindow *window = GUI::get_widget_by_id(windowId);
 
-            wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
-            evt_complete->SetInt(100);
-            wxQueueEvent(window, evt_complete);
+			wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
+			evt_complete->SetInt(100);
+			wxQueueEvent(window, evt_complete);
 
-            wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
-            evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
-            wxQueueEvent(window, evt_error);
-        })
-        .perform();
+			wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
+			evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
+			wxQueueEvent(window, evt_error);
+		})
+		.perform();
 }
 
 void OctoPrint::set_auth(Http &http) const
 {
-    http.header("X-Api-Key", apikey);
+	http.header("X-Api-Key", apikey);
 
-    if (! cafile.empty()) {
-        http.ca_file(cafile);
-    }
+	if (! cafile.empty()) {
+		http.ca_file(cafile);
+	}
 }
 
 std::string OctoPrint::make_url(const std::string &path) const
 {
-    if (host.find("http://") == 0 || host.find("https://") == 0) {
-        if (host.back() == '/') {
-            return std::move((boost::format("%1%%2%") % host % path).str());
-        } else {
-            return std::move((boost::format("%1%/%2%") % host % path).str());
-        }
-    } else {
-        return std::move((boost::format("http://%1%/%2%") % host % path).str());
-    }
+	if (host.find("http://") == 0 || host.find("https://") == 0) {
+		if (host.back() == '/') {
+			return std::move((boost::format("%1%%2%") % host % path).str());
+		} else {
+			return std::move((boost::format("%1%/%2%") % host % path).str());
+		}
+	} else {
+		return std::move((boost::format("http://%1%/%2%") % host % path).str());
+	}
 }
 
 std::string OctoPrint::format_error(std::string error, unsigned status)
 {
-    if (status != 0) {
-        std::string res{"HTTP "};
-        res.append(std::to_string(status));
+	if (status != 0) {
+		std::string res{"HTTP "};
+		res.append(std::to_string(status));
 
-        if (status == 401) {
-            res.append(": Invalid API key");
-        }
+		if (status == 401) {
+			res.append(": Invalid API key");
+		}
 
-        return std::move(res);
-    } else {
-        return std::move(error);
-    }
+		return std::move(res);
+	} else {
+		return std::move(error);
+	}
 }
 
 
diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
index c5ed70ab5..1d70a087d 100644
--- a/xs/src/slic3r/Utils/OctoPrint.hpp
+++ b/xs/src/slic3r/Utils/OctoPrint.hpp
@@ -14,19 +14,19 @@ class Http;
 class OctoPrint
 {
 public:
-    OctoPrint(DynamicPrintConfig *config);
+	OctoPrint(DynamicPrintConfig *config);
 
-    std::string test() const;
-    // XXX: style
-    void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
+	std::string test() const;
+	// XXX: style
+	void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
 private:
-    std::string host;
-    std::string apikey;
-    std::string cafile;
+	std::string host;
+	std::string apikey;
+	std::string cafile;
 
-    void set_auth(Http &http) const;
-    std::string make_url(const std::string &path) const;
-    static std::string format_error(std::string error, unsigned status);
+	void set_auth(Http &http) const;
+	std::string make_url(const std::string &path) const;
+	static std::string format_error(std::string error, unsigned status);
 };
 
 

From ca0f6131a18ea7fa089fb4eea979c9b3f87703a0 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 2 Mar 2018 15:05:17 +0100
Subject: [PATCH 8/9] WIP: Bonjour: TXT + improvements

---
 xs/src/slic3r/Utils/Bonjour.cpp   | 240 +++++++++++++++++++++---------
 xs/src/slic3r/Utils/Bonjour.hpp   |  21 ++-
 xs/src/slic3r/Utils/OctoPrint.hpp |   2 +-
 3 files changed, 191 insertions(+), 72 deletions(-)

diff --git a/xs/src/slic3r/Utils/Bonjour.cpp b/xs/src/slic3r/Utils/Bonjour.cpp
index d7fb30e64..6107e2c60 100644
--- a/xs/src/slic3r/Utils/Bonjour.cpp
+++ b/xs/src/slic3r/Utils/Bonjour.cpp
@@ -1,5 +1,6 @@
 #include "Bonjour.hpp"
 
+#include <iostream>  // XXX
 #include <cstdint>
 #include <algorithm>
 #include <unordered_map>
@@ -22,12 +23,14 @@ namespace asio = boost::asio;
 using boost::asio::ip::udp;
 
 
-// TODO: Fuzzing test
+// TODO: Fuzzing test (done without TXT)
+// FIXME: check char retype to unsigned
+
 
 namespace Slic3r {
 
 
-// Miniman implementation of a MDNS client
+// Minimal implementation of a MDNS/DNS-SD client
 // This implementation is extremely simple, only the bits that are useful
 // for very basic MDNS discovery are present.
 
@@ -38,11 +41,12 @@ struct DnsName: public std::string
 		MAX_RECURSION = 10,     // Keep this low
 	};
 
-	static optional<DnsName> decode(const std::vector<char> &buffer, ptrdiff_t &offset, unsigned depth = 0)
+	static optional<DnsName> decode(const std::vector<char> &buffer, size_t &offset, unsigned depth = 0)
 	{
-		// We trust that the offset passed is bounds-checked properly,
-		// including that there is at least one byte beyond that offset.
-		// Any further arithmetic has to be bounds-checked here though.
+		// 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:
@@ -51,14 +55,15 @@ struct DnsName: public std::string
 		}
 
 		DnsName res;
-		const ptrdiff_t bsize = buffer.size();
+		const size_t bsize = buffer.size();
 
 		while (true) {
 			const char* ptr = buffer.data() + offset;
-			char len = *ptr;
+			unsigned len = static_cast<unsigned char>(*ptr);
 			if (len & 0xc0) {
 				// This is a recursive label
-				ptrdiff_t pointer = (len & 0x3f) << 8 | ptr[1];
+				unsigned len_2 = static_cast<unsigned char>(ptr[1]);
+				size_t pointer = (len & 0x3f) << 8 | len_2;
 				const auto nested = decode(buffer, pointer, depth + 1);
 				if (!nested) {
 					return boost::none;
@@ -155,7 +160,7 @@ struct DnsQuestion
 		qclass(0)
 	{}
 
-	static optional<DnsQuestion> decode(const std::vector<char> &buffer, ptrdiff_t &offset)
+	static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset)
 	{
 		auto qname = DnsName::decode(buffer, offset);
 		if (!qname) {
@@ -187,15 +192,18 @@ struct DnsResource
 		ttl(0)
 	{}
 
-	static optional<DnsResource> decode(const std::vector<char> &buffer, ptrdiff_t &offset, ptrdiff_t &dataoffset)
+	static optional<DnsResource> decode(const std::vector<char> &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;
 		}
 
-		const ptrdiff_t bsize = buffer.size();
-
 		if (offset + 10 >= bsize) {
 			return boost::none;
 		}
@@ -267,39 +275,98 @@ struct DnsRR_SRV
 	uint16_t priority;
 	uint16_t weight;
 	uint16_t port;
-	std::string name;
-	std::string service;
 	DnsName hostname;
 
-	static void decode(std::vector<DnsRR_SRV> &results, const std::vector<char> &buffer, const DnsResource &rr, ptrdiff_t dataoffset)
+	static optional<DnsRR_SRV> decode(const std::vector<char> &buffer, const DnsResource &rr, size_t dataoffset)
 	{
 		if (rr.data.size() < MIN_SIZE) {
-			return;
+			return boost::none;
 		}
 
 		DnsRR_SRV res;
 
-		{
-			const auto dot_pos = rr.name.find_first_of('.');
-			if (dot_pos > 0 && dot_pos < rr.name.size() - 1) {
-				res.name = rr.name.substr(0, dot_pos);
-				res.service = rr.name.substr(dot_pos + 1);
-			} else {
-				return;
-			}
-		}
-
 		const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(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]);
 
-		ptrdiff_t offset = dataoffset + 6;
+		size_t offset = dataoffset + 6;
 		auto hostname = DnsName::decode(buffer, offset);
 
 		if (hostname) {
 			res.hostname = std::move(*hostname);
-			results.emplace_back(std::move(res));
+			return std::move(res);
+		} else {
+			return boost::none;
+		}
+	}
+};
+
+struct DnsRR_TXT
+{
+	enum
+	{
+		TAG = 0x10,
+	};
+
+	std::vector<std::string> values;
+
+	static optional<DnsRR_TXT> decode(const DnsResource &rr)
+	{
+		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<unsigned char>(*it);
+			if (val_size == 0 || it + val_size >= rr.data.end()) {
+				return boost::none;
+			}
+			++it;
+
+			std::string value(val_size, ' ');
+			std::copy(it, it + val_size, value.begin());
+			res.values.push_back(std::move(value));
+
+			it += val_size;
+		}
+
+		return std::move(res);
+	}
+};
+
+struct DnsSDPair
+{
+	optional<DnsRR_SRV> srv;
+	optional<DnsRR_TXT> txt;
+};
+
+struct DnsSDMap : public std::map<std::string, DnsSDPair>
+{
+	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)));
 		}
 	}
 };
@@ -314,12 +381,13 @@ struct DnsMessage
 
 	DnsHeader header;
 	optional<DnsQuestion> question;
-	std::vector<DnsResource> answers;
 
 	optional<DnsRR_A> rr_a;
 	optional<DnsRR_AAAA> rr_aaaa;
 	std::vector<DnsRR_SRV> rr_srv;
 
+	DnsSDMap sdmap;
+
 	static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
 	{
 		const auto size = buffer.size();
@@ -338,31 +406,39 @@ struct DnsMessage
 			return boost::none;
 		}
 
-		ptrdiff_t offset = DnsHeader::SIZE;
+		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++) {
-			ptrdiff_t dataoffset = 0;
+			size_t dataoffset = 0;
 			auto rr = DnsResource::decode(buffer, offset, dataoffset);
 			if (!rr) {
 				return boost::none;
 			} else {
-				res.parse_rr(buffer, *rr, dataoffset);
-				res.answers.push_back(std::move(*rr));
+				res.parse_rr(buffer, std::move(*rr), dataoffset);
 			}
 		}
 
 		return std::move(res);
 	}
 private:
-	void parse_rr(const std::vector<char> &buffer, const DnsResource &rr, ptrdiff_t dataoffset)
+	void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset)
 	{
 		switch (rr.type) {
 			case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
 			case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
-			case DnsRR_SRV::TAG: DnsRR_SRV::decode(this->rr_srv, buffer, rr, dataoffset); 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);
+				if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); }
+				break;
+			}
 		}
 	}
 };
@@ -373,8 +449,6 @@ struct BonjourRequest
 	static const asio::ip::address_v4 MCAST_IP4;
 	static const uint16_t MCAST_PORT;
 
-	static const char rq_template[];
-
 	uint16_t id;
 	std::vector<char> data;
 
@@ -390,12 +464,6 @@ private:
 const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
 const uint16_t BonjourRequest::MCAST_PORT = 5353;
 
-const char BonjourRequest::rq_template[] = {
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x5f, 0x68, 0x74,
-	0x74, 0x70, 0x04, 0x5f, 0x74, 0x63, 0x70, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c,
-	0x00, 0x01,
-};
-
 optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
 {
 	if (service.size() > 15 || protocol.size() > 15) {
@@ -416,7 +484,7 @@ optional<BonjourRequest> BonjourRequest::make(const std::string &service, const
 	data.push_back(id_char[1]);
 
 	// Add metadata
-	static const char rq_meta[] = {
+	static const unsigned char rq_meta[] = {
 		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 	};
 	std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
@@ -430,8 +498,8 @@ optional<BonjourRequest> BonjourRequest::make(const std::string &service, const
 	data.insert(data.end(), protocol.begin(), protocol.end());
 
 	// Add the rest of PTR record
-	static const char ptr_tail[] = {
-		0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01,
+	static const unsigned char ptr_tail[] = {
+		0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff,
 	};
 	std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
 
@@ -456,7 +524,7 @@ struct Bonjour::priv
 
 	priv(std::string service, std::string protocol);
 
-	void udp_receive(const error_code &error, size_t bytes);
+	void udp_receive(udp::endpoint from, size_t bytes);
 	void lookup_perform();
 };
 
@@ -470,23 +538,41 @@ Bonjour::priv::priv(std::string service, std::string protocol) :
 	buffer.resize(DnsMessage::MAX_SIZE);
 }
 
-void Bonjour::priv::udp_receive(const error_code &error, size_t bytes)
+void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
 {
-	if (error || bytes == 0 || !replyfn) {
+	if (bytes == 0 || !replyfn) {
 		return;
 	}
 
 	buffer.resize(bytes);
 	const auto dns_msg = DnsMessage::decode(buffer, rq_id);
 	if (dns_msg) {
-		std::string ip;
-		if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip.to_string(); }
-		else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip.to_string(); }
+		asio::ip::address ip = from.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 (const auto &srv : dns_msg->rr_srv) {
-			if (srv.service == service_dn) {
-				replyfn(std::move(ip), std::move(srv.hostname), std::move(srv.name));
+		for (const auto &sdpair : dns_msg->sdmap) {
+			if (! sdpair.second.srv) {
+				continue;
 			}
+
+			const auto &srv = *sdpair.second.srv;
+			BonjourReply reply(ip, sdpair.first, srv.hostname);
+
+			if (sdpair.second.txt) {
+				static const std::string tag_path = "path=";
+				static const std::string tag_version = "version=";
+
+				for (const auto &value : sdpair.second.txt->values) {
+					if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
+						reply.path = value.substr(tag_path.size());
+					} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
+						reply.version = value.substr(tag_version.size());
+					}
+				}
+			}
+
+			replyfn(std::move(reply));
 		}
 	}
 }
@@ -519,17 +605,18 @@ void Bonjour::priv::lookup_perform()
 			}
 		});
 
-		const auto recv_handler = [=](const error_code &error, size_t bytes) {
-			self->udp_receive(error, bytes);
+		udp::endpoint recv_from;
+		const auto recv_handler = [&](const error_code &error, size_t bytes) {
+			if (!error) { self->udp_receive(recv_from, bytes); }
 		};
-		socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler);
+		socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
 
 		while (io_service.run_one()) {
 			if (timeout) {
 				socket.cancel();
 			} else {
 				buffer.resize(DnsMessage::MAX_SIZE);
-				socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler);
+				socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
 			}
 		}
 	} catch (std::exception& e) {
@@ -539,6 +626,21 @@ void Bonjour::priv::lookup_perform()
 
 // API - public part
 
+BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
+	ip(std::move(ip)),
+	service_name(std::move(service_name)),
+	hostname(std::move(hostname)),
+	path("/"),
+	version("Unknown")
+{}
+
+std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
+{
+	os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", "
+		<< reply.hostname << ", " << reply.path << ", " << reply.version << ")";
+	return os;
+}
+
 Bonjour::Bonjour(std::string service, std::string protocol) :
 	p(new priv(std::move(service), std::move(protocol)))
 {}
@@ -587,15 +689,15 @@ Bonjour::Ptr Bonjour::lookup()
 
 void Bonjour::pokus()   // XXX
 {
-	// auto bonjour = Bonjour("http")
-	//     .set_timeout(15)
-	//     .on_reply([](std::string ip, std::string host, std::string service_name) {
-	//         std::cerr << "MDNS: " << ip << " = " << host << " : " << service_name << std::endl;
-	//     })
-	//     .on_complete([](){
-	//         std::cerr << "MDNS lookup complete" << std::endl;
-	//     })
-	//     .lookup();
+	auto bonjour = Bonjour("octoprint")
+		.set_timeout(15)
+		.on_reply([](BonjourReply &&reply) {
+			std::cerr << "BonjourReply: " << reply << std::endl;
+		})
+		.on_complete([](){
+			std::cerr << "MDNS lookup complete" << std::endl;
+		})
+		.lookup();
 }
 
 
diff --git a/xs/src/slic3r/Utils/Bonjour.hpp b/xs/src/slic3r/Utils/Bonjour.hpp
index 039db3370..285625c04 100644
--- a/xs/src/slic3r/Utils/Bonjour.hpp
+++ b/xs/src/slic3r/Utils/Bonjour.hpp
@@ -4,18 +4,35 @@
 #include <memory>
 #include <string>
 #include <functional>
+// #include <ostream>
+#include <boost/asio/ip/address.hpp>
 
 
 namespace Slic3r {
 
 
-/// Bonjour lookup
+// TODO: reply data structure
+struct BonjourReply
+{
+	boost::asio::ip::address ip;
+	std::string service_name;
+	std::string hostname;
+	std::string path;
+	std::string version;
+
+	BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname);
+};
+
+std::ostream& operator<<(std::ostream &, const BonjourReply &);
+
+
+/// Bonjour lookup performer
 class Bonjour : public std::enable_shared_from_this<Bonjour> {
 private:
 	struct priv;
 public:
 	typedef std::shared_ptr<Bonjour> Ptr;
-	typedef std::function<void(std::string /* IP */, std::string /* host */, std::string /* service_name */)> ReplyFn;
+	typedef std::function<void(BonjourReply &&reply)> ReplyFn;
 	typedef std::function<void()> CompleteFn;
 
 	Bonjour(std::string service, std::string protocol = "tcp");
diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
index 1d70a087d..eca3baa63 100644
--- a/xs/src/slic3r/Utils/OctoPrint.hpp
+++ b/xs/src/slic3r/Utils/OctoPrint.hpp
@@ -3,7 +3,7 @@
 
 #include <string>
 
-#include "Http.hpp"
+// #include "Http.hpp"    // XXX: ?
 
 namespace Slic3r {
 

From e26ccfc2479ad373ab86d4c54d11c8e2194d19d8 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Tue, 6 Mar 2018 11:39:24 +0100
Subject: [PATCH 9/9] Fixed compilation on Windows, removed debugging menu and
 debugging output.

---
 lib/Slic3r/GUI/MainFrame.pm | 10 ----------
 xs/CMakeLists.txt           | 23 +++++++++++++++--------
 2 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index ee5ec7cb5..442d0abc9 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -174,7 +174,6 @@ sub _init_tabpanel {
     EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub {
         my ($self, $event) = @_;
         my $msg = $event->GetString;
-        print "BUTTON_BROWSE_EVENT: ", $msg, "\n";
 
         # look for devices
         my $entries;
@@ -197,7 +196,6 @@ sub _init_tabpanel {
     EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub {
         my ($self, $event) = @_;
         my $msg = $event->GetString;
-        print "BUTTON_TEST_EVENT: ", $msg, "\n";
 
         my $ua = LWP::UserAgent->new;
         $ua->timeout(10);
@@ -410,13 +408,6 @@ sub _init_menubar {
         });
     }
 
-    my $hokusMenu = Wx::Menu->new;    # XXX: tmp
-    {
-        $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub {
-            Slic3r::Http::pokus();
-        });
-    }
-
     # menubar
     # assign menubar to frame after appending items, otherwise special items
     # will not be handled correctly
@@ -431,7 +422,6 @@ sub _init_menubar {
         # (Select application language from the list of installed languages)
         Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event});
         $menubar->Append($helpMenu, L("&Help"));
-        $menubar->Append($hokusMenu, "Hoku&s");
         # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing.
         $self->SetMenuBar($menubar);
     }
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index d8ef51caa..5f5771147 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -533,14 +533,21 @@ find_package(CURL REQUIRED)
 include_directories(${CURL_INCLUDE_DIRS})
 target_link_libraries(XS ${CURL_LIBRARIES})
 
-if (SLIC3R_STATIC AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
-    # As of now, our build system produces a statically linked libcurl,
-    # which links the OpenSSL library dynamically.
-    find_package(OpenSSL REQUIRED)
-    message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
-    message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
-    include_directories(${OPENSSL_INCLUDE_DIR})
-    target_link_libraries(XS ${OPENSSL_LIBRARIES})
+if (SLIC3R_STATIC)
+    if (NOT APPLE)
+        # libcurl is always linked dynamically to the system libcurl on OSX.
+        # On other systems, libcurl is linked statically if SLIC3R_STATIC is set.
+        add_definitions(-DCURL_STATICLIB)
+    endif()
+    if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+        # As of now, our build system produces a statically linked libcurl,
+        # which links the OpenSSL library dynamically.
+        find_package(OpenSSL REQUIRED)
+        message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
+        message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
+        include_directories(${OPENSSL_INCLUDE_DIR})
+        target_link_libraries(XS ${OPENSSL_LIBRARIES})
+    endif()
 endif()
 
 ## OPTIONAL packages