diff --git a/examples/libdatachannel/src/common/libdatachannel_websocket/rtc_websocket_wrapper.hpp b/examples/libdatachannel/src/common/libdatachannel_websocket/rtc_websocket_wrapper.hpp index e681486..138ec35 100644 --- a/examples/libdatachannel/src/common/libdatachannel_websocket/rtc_websocket_wrapper.hpp +++ b/examples/libdatachannel/src/common/libdatachannel_websocket/rtc_websocket_wrapper.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -12,21 +13,30 @@ class RtcWebsocketWrapper : public nabto::webrtc::SignalingWebsocket, public std::enable_shared_from_this { public: - static nabto::webrtc::SignalingWebsocketPtr create() { - return std::make_shared(); + static nabto::webrtc::SignalingWebsocketPtr create( + std::optional caBundle) { + return std::make_shared(caBundle); } - RtcWebsocketWrapper() {} + RtcWebsocketWrapper(std::optional& caBundle) { + if (caBundle.has_value()) { + rtc::WebSocketConfiguration conf; + conf.caCertificatePemFile = caBundle; + ws_ = std::make_shared(conf); + } else { + ws_ = std::make_shared(); + } + } - bool send(const std::string& data) { return ws_.send(data); } + bool send(const std::string& data) { return ws_->send(data); } - void close() { return ws_.close(); } + void close() { return ws_->close(); } - void onOpen(std::function callback) { ws_.onOpen(callback); } + void onOpen(std::function callback) { ws_->onOpen(callback); } void onMessage(std::function callback) { auto self = shared_from_this(); - ws_.onMessage( + ws_->onMessage( [self, callback](std::variant message) { std::string msg; if (std::holds_alternative(message)) { @@ -42,16 +52,16 @@ class RtcWebsocketWrapper }); } - void onClosed(std::function callback) { ws_.onClosed(callback); } + void onClosed(std::function callback) { ws_->onClosed(callback); } - void open(const std::string& url) { ws_.open(url); } + void open(const std::string& url) { ws_->open(url); } void onError(std::function callback) { - ws_.onError(callback); + ws_->onError(callback); } private: - rtc::WebSocket ws_; + std::shared_ptr ws_; }; } // namespace example diff --git a/examples/libdatachannel/src/webrtc_device/main.cpp b/examples/libdatachannel/src/webrtc_device/main.cpp index f4c04c5..8977364 100644 --- a/examples/libdatachannel/src/webrtc_device/main.cpp +++ b/examples/libdatachannel/src/webrtc_device/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "h264_handler.hpp" @@ -24,6 +25,7 @@ struct options { std::string sharedSecret; std::string sharedSecretId; bool centralAuthorization; + std::optional caBundle; }; bool parse_options(int argc, char** argv, struct options& opts); @@ -50,8 +52,8 @@ int main(int argc, char** argv) { nabto::webrtc::util::NabtoTokenGenerator::create( opts.productId, opts.deviceId, opts.privateKey); - auto http = nabto::webrtc::util::CurlHttpClient::create(); - auto ws = nabto::example::RtcWebsocketWrapper::create(); + auto http = nabto::webrtc::util::CurlHttpClient::create(opts.caBundle); + auto ws = nabto::example::RtcWebsocketWrapper::create(opts.caBundle); auto tf = nabto::webrtc::util::StdTimerFactory::create(); auto trackHandler = nabto::example::H264TrackHandler::create(nullptr); @@ -122,7 +124,11 @@ bool parse_options(int argc, char** argv, struct options& opts) { "Optional. Shared secret used to sign and validate signaling messages", cxxopts::value())("central-authorization", "Require central authorization")( - "h,help", "Shows this help text"); + "ca-bundle", + "Optional. Path to a CA certificate file; overrides CURL_CA_BUNDLE " + "env var if set.", + cxxopts::value())("h,help", "Shows this help text")( + "v,version", "Shows the Nabto WebRTC SDK version"); auto result = options.parse(argc, argv); if (result.count("help")) { @@ -130,6 +136,12 @@ bool parse_options(int argc, char** argv, struct options& opts) { return false; } + if (result.count("version")) { + std::cout << "Nabto WebRTC SDK C++: " + << nabto::webrtc::SignalingDevice::version() << std::endl; + return false; + } + if (result.count("deviceid") && result.count("productid")) { opts.deviceId = result["deviceid"].as(); opts.productId = result["productid"].as(); @@ -176,6 +188,23 @@ bool parse_options(int argc, char** argv, struct options& opts) { return false; } + if (result.count("ca-bundle")) { + opts.caBundle = result["ca-bundle"].as(); + } else { + const char* curlCaBundle = std::getenv("CURL_CA_BUNDLE"); + if (curlCaBundle != nullptr) { + opts.caBundle = std::string(curlCaBundle); + } + } + if (opts.caBundle.has_value()) { + std::ifstream f(opts.caBundle.value()); + if (!f.good()) { + std::cout << "CA certificate bundle file does not exist: " + << opts.caBundle.value() << std::endl; + return false; + } + } + } catch (const cxxopts::exceptions::exception& e) { std::cout << "Error parsing options: " << e.what() << std::endl; return false; diff --git a/examples/libdatachannel/src/webrtc_device_rtsp/main.cpp b/examples/libdatachannel/src/webrtc_device_rtsp/main.cpp index ce79ec0..8eb9542 100644 --- a/examples/libdatachannel/src/webrtc_device_rtsp/main.cpp +++ b/examples/libdatachannel/src/webrtc_device_rtsp/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "h264_opus_rtsp_handler.hpp" @@ -25,6 +26,7 @@ struct options { std::string sharedSecretId; bool centralAuthorization; std::string rtspUrl; + std::optional caBundle; }; bool parse_options(int argc, char** argv, struct options& opts); @@ -50,8 +52,10 @@ int main(int argc, char** argv) { nabto::webrtc::util::NabtoTokenGenerator::create( opts.productId, opts.deviceId, opts.privateKey); - auto http = nabto::webrtc::util::CurlHttpClient::create(); - auto ws = nabto::example::RtcWebsocketWrapper::create(); + nabto::webrtc::SignalingHttpClientPtr http = + nabto::webrtc::util::CurlHttpClient::create(opts.caBundle); + nabto::webrtc::SignalingWebsocketPtr ws = + nabto::example::RtcWebsocketWrapper::create(opts.caBundle); auto tf = nabto::webrtc::util::StdTimerFactory::create(); auto trackHandler = nabto::example::H264TrackHandler::create(opts.rtspUrl); @@ -125,7 +129,11 @@ bool parse_options(int argc, char** argv, struct options& opts) { cxxopts::value())("central-authorization", "Require central authorization")( "r,rtsp-url", "URL for the RTSP server", cxxopts::value())( - "h,help", "Shows this help text"); + "ca-bundle", + "Optional. Path to a CA certificate file; overrides CURL_CA_BUNDLE " + "env var if set.", + cxxopts::value())("h,help", "Shows this help text")( + "v,version", "Shows the Nabto WebRTC SDK version"); auto result = options.parse(argc, argv); if (result.count("help")) { @@ -133,6 +141,12 @@ bool parse_options(int argc, char** argv, struct options& opts) { return false; } + if (result.count("version")) { + std::cout << "Nabto WebRTC SDK C++: " + << nabto::webrtc::SignalingDevice::version() << std::endl; + return false; + } + if (result.count("deviceid") && result.count("productid")) { opts.deviceId = result["deviceid"].as(); opts.productId = result["productid"].as(); @@ -187,6 +201,23 @@ bool parse_options(int argc, char** argv, struct options& opts) { return false; } + if (result.count("ca-bundle")) { + opts.caBundle = result["ca-bundle"].as(); + } else { + const char* curlCaBundle = std::getenv("CURL_CA_BUNDLE"); + if (curlCaBundle != nullptr) { + opts.caBundle = std::string(curlCaBundle); + } + } + if (opts.caBundle.has_value()) { + std::ifstream f(opts.caBundle.value()); + if (!f.good()) { + std::cout << "CA certificate bundle file does not exist: " + << opts.caBundle.value() << std::endl; + return false; + } + } + } catch (const cxxopts::exceptions::exception& e) { std::cout << "Error parsing options: " << e.what() << std::endl; return false; diff --git a/sdk/integration_test/test_instance.hpp b/sdk/integration_test/test_instance.hpp index 251f1c9..d89fc74 100644 --- a/sdk/integration_test/test_instance.hpp +++ b/sdk/integration_test/test_instance.hpp @@ -100,7 +100,7 @@ class TestInstance : public std::enable_shared_from_this { } nabto::webrtc::SignalingDevicePtr createDevice() { - http_ = nabto::webrtc::util::CurlHttpClient::create(); + http_ = nabto::webrtc::util::CurlHttpClient::create(std::nullopt); ws_ = nabto::example::RtcWebsocketWrapper::create(); tf_ = nabto::webrtc::util::StdTimerFactory::create(); tokGen_ = TestTokenGen::create(accessToken_); diff --git a/sdk/src/signaling_util/curl_http_client/include/nabto/webrtc/util/curl_async.hpp b/sdk/src/signaling_util/curl_http_client/include/nabto/webrtc/util/curl_async.hpp index a82cacf..60acf59 100644 --- a/sdk/src/signaling_util/curl_http_client/include/nabto/webrtc/util/curl_async.hpp +++ b/sdk/src/signaling_util/curl_http_client/include/nabto/webrtc/util/curl_async.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace nabto { @@ -24,13 +25,15 @@ class CurlHttpClient : public nabto::webrtc::SignalingHttpClient, public std::enable_shared_from_this { public: /** - * Create an instance of the SignalingHttpClient. + * Create an instance of the SignalingHttpClient with a custom CA bundle. * + * @param caBundle path to the CA bundle to use * @return Smart pointer to the created SignalingHttpClient. */ - static nabto::webrtc::SignalingHttpClientPtr create(); + static nabto::webrtc::SignalingHttpClientPtr create( + std::optional caBundle); - CurlHttpClient(); + explicit CurlHttpClient(std::optional& caBundle); ~CurlHttpClient() override; CurlHttpClient(const CurlHttpClient&) = delete; CurlHttpClient& operator=(const CurlHttpClient&) = delete; @@ -46,6 +49,7 @@ class CurlHttpClient : public nabto::webrtc::SignalingHttpClient, std::string writeBuffer_; std::string authHeader_; std::string ctHeader_; + std::optional caBundle_; struct curl_slist* curlReqHeaders_ = nullptr; diff --git a/sdk/src/signaling_util/curl_http_client/src/curl_async.cpp b/sdk/src/signaling_util/curl_http_client/src/curl_async.cpp index b52e0db..2cfbfe7 100644 --- a/sdk/src/signaling_util/curl_http_client/src/curl_async.cpp +++ b/sdk/src/signaling_util/curl_http_client/src/curl_async.cpp @@ -12,17 +12,21 @@ #include #include #include +#include +#include #include namespace nabto { namespace webrtc { namespace util { -nabto::webrtc::SignalingHttpClientPtr CurlHttpClient::create() { - return std::make_shared(); +nabto::webrtc::SignalingHttpClientPtr CurlHttpClient::create( + std::optional caBundle) { + return std::make_shared(caBundle); } -CurlHttpClient::CurlHttpClient() : curl_(CurlAsync::create()) {} +CurlHttpClient::CurlHttpClient(std::optional& caBundle) + : curl_(CurlAsync::create()), caBundle_(caBundle) {} CurlHttpClient::~CurlHttpClient() { if (curlReqHeaders_ != nullptr) { @@ -63,6 +67,14 @@ bool CurlHttpClient::sendRequest( return false; } + if (caBundle_.has_value()) { + res = curl_easy_setopt(curl, CURLOPT_CAINFO, caBundle_.value().c_str()); + if (res != CURLE_OK) { + NPLOGE << "Failed to set CA bundle with: " << curl_easy_strerror(res); + return false; + } + } + res = curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunc); if (res != CURLE_OK) { diff --git a/sdk/src/signaling_util/std_timer/include/nabto/webrtc/util/std_timer.hpp b/sdk/src/signaling_util/std_timer/include/nabto/webrtc/util/std_timer.hpp index 9500a17..8c99f46 100644 --- a/sdk/src/signaling_util/std_timer/include/nabto/webrtc/util/std_timer.hpp +++ b/sdk/src/signaling_util/std_timer/include/nabto/webrtc/util/std_timer.hpp @@ -16,7 +16,12 @@ namespace util { class StdTimer : public nabto::webrtc::SignalingTimer, public std::enable_shared_from_this { public: - ~StdTimer() {} + ~StdTimer() { + if (timer_.joinable()) { + timer_.join(); + timer_ = std::thread(); + } + } void setTimeout(uint32_t timeoutMs, std::function cb) override { auto self = shared_from_this();