diff --git a/CMakeLists.txt b/CMakeLists.txt index 02dcbe0..d5f21fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ option(${REPO_OPTION_PREFIX}_ENABLE_EXAMPLE_APP "Enable ${REPO_NAME} example app option(${REPO_OPTION_PREFIX}_ENABLE_TESTS "Enable ${REPO_NAME} testing" ${PROJECT_IS_TOP_LEVEL}) option(${REPO_OPTION_PREFIX}_ENABLE_CLIENT "Enable ${REPO_NAME} client module" ${PROJECT_IS_TOP_LEVEL}) option(${REPO_OPTION_PREFIX}_ENABLE_SERVER "Enable ${REPO_NAME} server module" ${PROJECT_IS_TOP_LEVEL}) +option(${REPO_OPTION_PREFIX}_ENABLE_TLS "Enable ${REPO_NAME} TLS (daq.lts://) streaming channel" ON) opendaq_common_compile_targets_settings() opendaq_setup_compiler_flags(${REPO_OPTION_PREFIX}) @@ -40,6 +41,12 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang" AND CMAKE_CXX_COMPILER_VERS add_compile_options(-Wno-unknown-warning-option) endif() +if (${REPO_OPTION_PREFIX}_ENABLE_TLS) + message(STATUS "TLS streaming channel in ${REPO_NAME} is ENABLED") +else() + message(STATUS "TLS streaming channel in ${REPO_NAME} is DISABLED (no OpenSSL dependency)") +endif() + if (${REPO_OPTION_PREFIX}_ENABLE_TESTS) message(STATUS "Unit tests in ${REPO_NAME} are ENABLED") enable_testing() @@ -77,8 +84,9 @@ add_subdirectory(external) add_subdirectory(shared) add_subdirectory(modules) -# End-to-end integration tests need both modules present -if (${REPO_OPTION_PREFIX}_ENABLE_TESTS AND ${REPO_OPTION_PREFIX}_ENABLE_CLIENT AND ${REPO_OPTION_PREFIX}_ENABLE_SERVER) +# End-to-end integration tests need both modules present, and only cover the TLS channel +if (${REPO_OPTION_PREFIX}_ENABLE_TESTS AND ${REPO_OPTION_PREFIX}_ENABLE_CLIENT + AND ${REPO_OPTION_PREFIX}_ENABLE_SERVER AND ${REPO_OPTION_PREFIX}_ENABLE_TLS) add_subdirectory(tests) endif() diff --git a/external/ws-streaming/CMakeLists.txt b/external/ws-streaming/CMakeLists.txt index fbfc143..3160076 100644 --- a/external/ws-streaming/CMakeLists.txt +++ b/external/ws-streaming/CMakeLists.txt @@ -1,10 +1,12 @@ set(WS_STREAMING_INSTALL OFF) +set(WS_STREAMING_ENABLE_TLS ${${REPO_OPTION_PREFIX}_ENABLE_TLS}) + opendaq_dependency( NAME ws-streaming REQUIRED_VERSION 3.2.0 GIT_REPOSITORY https://github.com/openDAQ/ws-streaming - GIT_REF main + GIT_REF tls-optional GIT_SHALLOW ON EXPECT_TARGET ws-streaming::ws-streaming ) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index 617db36..6422d3e 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -50,6 +50,14 @@ class WebsocketStreamingClientModule final : public Module std::string path; }; + // Pick the plain or the secure variant according to the connection string. The daq.lts:// + // counterparts only exist in a build with the TLS channel, so these keep the preprocessor + // out of onCreateDevice() and onCreateStreaming(). + static PropertyObjectPtr createDefaultDeviceConfig(const StringPtr& connectionString); + static PropertyObjectPtr createDefaultStreamingConfig(const StringPtr& connectionString); + static DeviceTypePtr createDeviceType(const StringPtr& connectionString); + static StreamingTypePtr createStreamingType(const StringPtr& connectionString); + DAQ_WS_STREAM_CL_MODULE_API static StringPtr createUrlConnectionString(bool secureType, const StringPtr& host, const IntegerPtr& port, @@ -60,6 +68,7 @@ class WebsocketStreamingClientModule final : public Module DAQ_WS_STREAM_CL_MODULE_API static StringPtr formNewStyleConnectionString(const StringPtr& connectionString); DAQ_WS_STREAM_CL_MODULE_API static DeviceInfoPtr populateDiscoveredDevice(const discovery::MdnsDiscoveredDevice& discoveredDevice); DAQ_WS_STREAM_CL_MODULE_API static bool isSecureConnection(const std::string& connectionString); + DAQ_WS_STREAM_CL_MODULE_API static bool isSupportedServiceName(const std::string& serviceName); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index a6c3920..93e3b90 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -54,7 +54,11 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex , deviceIndex(0) , discoveryClient({CONST_SERVICE_CAPABILITY}) { +#if DAQMODULES_LT_STREAMING_ENABLE_TLS discoveryClient.initMdnsClient(List(CONST_LT_SERVICE_NAME, CONST_LTS_SERVICE_NAME, CONST_WS_SERVICE_NAME)); +#else + discoveryClient.initMdnsClient(List(CONST_LT_SERVICE_NAME, CONST_WS_SERVICE_NAME)); +#endif loggerComponent = this->context.getLogger().getOrAddComponent("StreamingLTClient"); } @@ -62,7 +66,15 @@ ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() { auto availableDevices = List(); for (const auto& device : discoveryClient.discoverMdnsDevices()) + { + if (!isSupportedServiceName(device.serviceName)) + { + LOG_D("Ignoring discovered service \"{}\": not supported by this module", device.serviceName) + continue; + } + availableDevices.pushBack(populateDiscoveredDevice(device)); + } return availableDevices; } @@ -72,11 +84,14 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableDevi const auto websocketDeviceType = WsStreamingDevice::createNewType(); const auto oldWebsocketDeviceType = WsStreamingDevice::createOldType(); - const auto secureWebsocketDeviceType = WsStreamingDevice::createNewSecureType(); result.set(websocketDeviceType.getId(), websocketDeviceType); result.set(oldWebsocketDeviceType.getId(), oldWebsocketDeviceType); + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + const auto secureWebsocketDeviceType = WsStreamingDevice::createNewSecureType(); result.set(secureWebsocketDeviceType.getId(), secureWebsocketDeviceType); +#endif return result; } @@ -86,10 +101,12 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableS auto result = Dict(); auto websocketStreamingType = WsStreaming::createType(); - auto secureWebsocketStreamingType = WsStreaming::createSecureType(); - result.set(websocketStreamingType.getId(), websocketStreamingType); + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + auto secureWebsocketStreamingType = WsStreaming::createSecureType(); result.set(secureWebsocketStreamingType.getId(), secureWebsocketStreamingType); +#endif return result; } @@ -115,20 +132,17 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec PropertyObjectPtr deviceConfig = config; if (!deviceConfig.assigned()) - deviceConfig = isSecureConnection(formedConnectionStr) ? WsStreamingDevice::createDefaultSecureConfig() - : WsStreamingDevice::createDefaultConfig(); + deviceConfig = createDefaultDeviceConfig(formedConnectionStr); std::scoped_lock lock(sync); std::string localId = fmt::format("websocket_pseudo_device{}", deviceIndex++); - auto deviceType = - isSecureConnection(formedConnectionStr) ? WsStreamingDevice::createNewSecureType() : WsStreamingDevice::createNewType(); + auto deviceType = createDeviceType(formedConnectionStr); checkErrorInfo(deviceType.asPtr()->setModuleInfo(moduleInfo)); auto device = createWithImplementation(context, parent, localId, formedConnectionStr, deviceType, deviceConfig); // Set the connection info for the device - const auto wsStreamingType = - (isSecureConnection(formedConnectionStr)) ? WsStreaming::createSecureType() : WsStreaming::createType(); + const auto wsStreamingType = createStreamingType(formedConnectionStr); ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); connectionInfo.setProtocolId(wsStreamingType.getId()); @@ -174,9 +188,7 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& PropertyObjectPtr streamingConfig = config; if (!streamingConfig.assigned()) - streamingConfig = isSecureConnection(formNewStyleConnectionString(connectionString).toStdString()) - ? WsStreaming::createDefaultSecureConfig() - : WsStreaming::createDefaultConfig(); + streamingConfig = createDefaultStreamingConfig(formNewStyleConnectionString(connectionString)); const StringPtr str = formConnectionString(connectionString, streamingConfig); return createWithImplementation(str, context, streamingConfig); @@ -185,8 +197,13 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) { const auto protoId = target.getProtocolId(); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS if (protoId != CONST_LT_STREAMING_ID && protoId != CONST_LTS_STREAMING_ID) return false; +#else + if (protoId != CONST_LT_STREAMING_ID) + return false; +#endif if (source.getConnectionType() != "TCP/IP") return false; @@ -243,6 +260,46 @@ Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapa return true; } +PropertyObjectPtr WebsocketStreamingClientModule::createDefaultDeviceConfig(const StringPtr& connectionString) +{ +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureConnection(connectionString)) + return WsStreamingDevice::createDefaultSecureConfig(); +#endif + + return WsStreamingDevice::createDefaultConfig(); +} + +PropertyObjectPtr WebsocketStreamingClientModule::createDefaultStreamingConfig(const StringPtr& connectionString) +{ +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureConnection(connectionString)) + return WsStreaming::createDefaultSecureConfig(); +#endif + + return WsStreaming::createDefaultConfig(); +} + +DeviceTypePtr WebsocketStreamingClientModule::createDeviceType(const StringPtr& connectionString) +{ +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureConnection(connectionString)) + return WsStreamingDevice::createNewSecureType(); +#endif + + return WsStreamingDevice::createNewType(); +} + +StreamingTypePtr WebsocketStreamingClientModule::createStreamingType(const StringPtr& connectionString) +{ +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureConnection(connectionString)) + return WsStreaming::createSecureType(); +#endif + + return WsStreaming::createType(); +} + StringPtr WebsocketStreamingClientModule::createUrlConnectionString(bool secureType, const StringPtr& host, const IntegerPtr& port, @@ -330,12 +387,14 @@ DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const Mdn streamingType = WsStreaming::createType(); deviceType = WsStreamingDevice::createNewType(); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS else if (discoveredDevice.serviceName == CONST_LTS_SERVICE_NAME) { isSecure = true; streamingType = WsStreaming::createSecureType(); deviceType = WsStreamingDevice::createNewSecureType(); } +#endif else { DAQ_THROW_EXCEPTION(InvalidParameterException, @@ -387,4 +446,17 @@ bool WebsocketStreamingClientModule::isSecureConnection(const std::string& conne return connectionString.find(securePrefix) != std::string::npos; } +bool WebsocketStreamingClientModule::isSupportedServiceName(const std::string& serviceName) +{ + if (serviceName == CONST_LT_SERVICE_NAME || serviceName == CONST_WS_SERVICE_NAME) + return true; + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (serviceName == CONST_LTS_SERVICE_NAME) + return true; +#endif + + return false; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_client_module/tests/CMakeLists.txt b/modules/websocket_streaming_client_module/tests/CMakeLists.txt index d56cac7..67b76ea 100644 --- a/modules/websocket_streaming_client_module/tests/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/tests/CMakeLists.txt @@ -5,6 +5,10 @@ set(TEST_SOURCES test_websocket_streaming_client_module.cpp test_app.cpp ) +if (${REPO_OPTION_PREFIX}_ENABLE_TLS) + list(APPEND TEST_SOURCES test_websocket_streaming_client_module_tls.cpp) +endif() + add_executable(${TEST_APP} ${TEST_SOURCES} ) diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 205695e..deac696 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -1,121 +1,4 @@ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace daq; -using namespace daq::websocket_streaming; -using namespace daq::modules::websocket_streaming_client_module; - -class WebsocketStreamingClientModuleTest : public testing::Test -{ -protected: - using ConnectionParameters = WebsocketStreamingClientModule::ConnectionParameters; - - static StringPtr formConnectionString(const StringPtr& connectionString, - const PropertyObjectPtr& config, - ConnectionParameters* outParams = nullptr) - { - return WebsocketStreamingClientModule::formConnectionString(connectionString, config, outParams); - } - - static StringPtr createUrlConnectionString(const StringPtr& host, const IntegerPtr& port, const StringPtr& path) - { - return WebsocketStreamingClientModule::createUrlConnectionString(false, host, port, path); - } - - static StringPtr createUrlConnectionString(bool secureType, const StringPtr& host, const IntegerPtr& port, const StringPtr& path) - { - return WebsocketStreamingClientModule::createUrlConnectionString(secureType, host, port, path); - } - - static bool isSecureConnection(const std::string& connectionString) - { - return WebsocketStreamingClientModule::isSecureConnection(connectionString); - } - - static bool acceptsConnectionParameters(const ModulePtr& module, const StringPtr& connectionString, const PropertyObjectPtr& config) - { - return reinterpret_cast(module.getObject())->acceptsConnectionParameters(connectionString, config); - } - - static bool acceptsStreamingConnectionParameters(const ModulePtr& module, const StringPtr& connectionString, const PropertyObjectPtr& config) - { - return reinterpret_cast(module.getObject())->acceptsStreamingConnectionParameters(connectionString, config); - } - - static DeviceInfoPtr populateDiscoveredDevice(const discovery::MdnsDiscoveredDevice& discoveredDevice) - { - return WebsocketStreamingClientModule::populateDiscoveredDevice(discoveredDevice); - } - - static Bool completeServerCapability(const ModulePtr& module, - const ServerCapabilityPtr& source, - const ServerCapabilityConfigPtr& target) - { - return module.completeServerCapability(source, target); - } - - static discovery::MdnsDiscoveredDevice makeDiscoveredDevice(const std::string& serviceName, - uint32_t servicePort = 7414, - const std::unordered_set& ipv4 = {"192.168.1.10"}, - const std::unordered_set& ipv6 = {}, - const std::unordered_map& properties = {}) - { - discovery::MdnsDiscoveredDevice device{}; - device.serviceName = serviceName; - device.servicePort = servicePort; - device.ipv4Addresses = ipv4; - device.ipv6Addresses = ipv6; - device.properties = properties; - return device; - } - - static ServerCapabilityPtr firstCapability(const DeviceInfoPtr& info) - { - const auto caps = info.getServerCapabilities(); - EXPECT_EQ(caps.getCount(), 1u); - return caps[0]; - } - - static ServerCapabilityConfigPtr makeSourceCapability(const std::string& address = "192.168.1.10", - const std::string& prefix = "daq.opcua") - { - auto source = ServerCapability("OpenDAQOPCUA", "OpenDAQOPCUA", ProtocolType::Configuration); - source.setConnectionType("TCP/IP"); - source.setPrefix(String(prefix)); - source.addAddress(String(address)); - source.addAddressInfo(AddressInfoBuilder() - .setAddress(String(address)) - .setReachabilityStatus(AddressReachabilityStatus::Reachable) - .setType("IPv4") - .setConnectionString(String(prefix + "://" + address + ":4840")) - .build()); - return source; - } -}; - -static ModulePtr CreateModule() -{ - ModulePtr module; - createModule(&module, NullContext()); - return module; -} +#include "test_websocket_streaming_client_module.h" TEST_F(WebsocketStreamingClientModuleTest, CreateModule) { @@ -227,13 +110,18 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); - ASSERT_EQ(deviceTypes.getCount(), 3u); ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreaming")); ASSERT_EQ(deviceTypes.get("OpenDAQLTStreaming").getId(), "OpenDAQLTStreaming"); - ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreamingSecure")); - ASSERT_EQ(deviceTypes.get("OpenDAQLTStreamingSecure").getId(), "OpenDAQLTStreamingSecure"); ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreamingOld")); ASSERT_EQ(deviceTypes.get("OpenDAQLTStreamingOld").getId(), "OpenDAQLTStreamingOld"); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + ASSERT_EQ(deviceTypes.getCount(), 3u); + ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreamingSecure")); + ASSERT_EQ(deviceTypes.get("OpenDAQLTStreamingSecure").getId(), "OpenDAQLTStreamingSecure"); +#else + ASSERT_EQ(deviceTypes.getCount(), 2u); + ASSERT_FALSE(deviceTypes.hasKey("OpenDAQLTStreamingSecure")); +#endif DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); @@ -246,12 +134,17 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableStreamingTypes) DictPtr streamingTypes; ASSERT_NO_THROW(streamingTypes = module.getAvailableStreamingTypes()); - ASSERT_EQ(streamingTypes.getCount(), 2u); ASSERT_TRUE(streamingTypes.hasKey("OpenDAQLTStreaming")); ASSERT_EQ(streamingTypes.get("OpenDAQLTStreaming").getId(), "OpenDAQLTStreaming"); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + ASSERT_EQ(streamingTypes.getCount(), 2u); ASSERT_TRUE(streamingTypes.hasKey("OpenDAQLTStreamingSecure")); ASSERT_EQ(streamingTypes.get("OpenDAQLTStreamingSecure").getId(), "OpenDAQLTStreamingSecure"); +#else + ASSERT_EQ(streamingTypes.getCount(), 1u); + ASSERT_FALSE(streamingTypes.hasKey("OpenDAQLTStreamingSecure")); +#endif } TEST_F(WebsocketStreamingClientModuleTest, CreateFunctionBlockIdNull) @@ -382,7 +275,11 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsAllPrefixes) ASSERT_TRUE(acceptsConnectionParameters(module, "daq.lt://h", nullptr)); ASSERT_TRUE(acceptsConnectionParameters(module, "daq.ws://h", nullptr)); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_TRUE(acceptsConnectionParameters(module, "daq.lts://h", nullptr)); +#else + ASSERT_FALSE(acceptsConnectionParameters(module, "daq.lts://h", nullptr)); +#endif } TEST_F(WebsocketStreamingClientModuleTest, RejectsForeignPrefixes) @@ -402,60 +299,12 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingRejectsEmpty) ASSERT_FALSE(acceptsStreamingConnectionParameters(module, "", nullptr)); ASSERT_FALSE(acceptsStreamingConnectionParameters(module, nullptr, nullptr)); + ASSERT_TRUE(acceptsStreamingConnectionParameters(module, "daq.lt://h", nullptr)); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_TRUE(acceptsStreamingConnectionParameters(module, "daq.lts://h", nullptr)); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingDefaultConfigRejectsEmptyCertKey) -{ - auto module = CreateModule(); - ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", nullptr), - InvalidParameterException, - "TLS certificate or key file path is not configured"); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingRejectsEmptyCaWithMtlsDisabled) -{ - auto module = CreateModule(); - auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); - config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, False); - - ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), - InvalidParameterException, - "TLS CA certificate file path is not configured"); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingRejectsEmptyCaWithMtlsEnabled) -{ - auto module = CreateModule(); - auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); - config.setPropertyValue(PROPERTY_WSS_CERT_FILE_PATH_CLIENT, "/tmp/cert.pem"); - config.setPropertyValue(PROPERTY_WSS_KEY_FILE_PATH_CLIENT, "/tmp/key.pem"); - - ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), - InvalidParameterException, - "TLS CA certificate file path is not configured"); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingCompletesNullConfig) -{ - // A missing configuration is completed with the secure defaults, so the constructor reaches - // the certificate check instead of dereferencing a null configuration object. - ASSERT_THROW_MSG((createWithImplementation( - String("daq.lts://127.0.0.1:1/"), NullContext(), nullptr)), - InvalidParameterException, - "TLS certificate or key file path is not configured"); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingCompletesPartialConfig) -{ - // A configuration carrying only the port is completed with the missing TLS properties. - auto module = CreateModule(); - auto config = PropertyObject(); - config.addProperty(IntProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT, DEFAULT_WSS_STREAMING_PORT)); - - ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), - InvalidParameterException, - "TLS certificate or key file path is not configured"); +#else + ASSERT_FALSE(acceptsStreamingConnectionParameters(module, "daq.lts://h", nullptr)); +#endif } TEST_F(WebsocketStreamingClientModuleTest, InsecureStreamingCompletesNullConfig) @@ -480,57 +329,6 @@ TEST_F(WebsocketStreamingClientModuleTest, DefaultInsecureStreamingConfig) ASSERT_EQ(config.getPropertyValue(PROPERTY_WS_STREAMING_PORT_CLIENT), DEFAULT_WS_STREAMING_PORT); } -TEST_F(WebsocketStreamingClientModuleTest, DefaultSecureStreamingConfig) -{ - auto module = CreateModule(); - auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); - ASSERT_TRUE(config.assigned()); - - ASSERT_EQ(config.getAllProperties().getCount(), 6u); - - ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT)); - ASSERT_TRUE(config.hasProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT)); - ASSERT_TRUE(config.hasProperty(PROPERTY_ENABLE_MTLS_CLIENT)); - ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT)); - ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT)); - ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT)); - - ASSERT_EQ(config.getProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT).getValueType(), CoreType::ctInt); - ASSERT_EQ(config.getProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT).getValueType(), CoreType::ctBool); - ASSERT_EQ(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getValueType(), CoreType::ctBool); - - ASSERT_EQ(config.getPropertyValue(PROPERTY_WSS_STREAMING_PORT_CLIENT), DEFAULT_WSS_STREAMING_PORT); - ASSERT_EQ(config.getPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT), DEFAULT_VERIFY_SERVER_CERT); - ASSERT_EQ(config.getPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT), DEFAULT_ENABLE_MTLS); - - config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, True); - ASSERT_TRUE(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getVisible()); - ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_TRUE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); - config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, False); - ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); - - // Nothing below the verification switch is meaningful once it is off - config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, True); - config.setPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT, False); - ASSERT_TRUE(config.getProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); - ASSERT_FALSE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); -} - -TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingWithoutVerificationNeedsNoCa) -{ - auto module = CreateModule(); - auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); - config.setPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT, False); - - ASSERT_THROW(module.createStreaming("daq.lts://127.0.0.1:1/", config), NotFoundException); -} TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLtService) { const auto info = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LT_SERVICE_NAME, 7425)); @@ -547,21 +345,6 @@ TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLtService) ASSERT_EQ(cap.getProtocolSecurityLevel(), CONST_LT_STREAMING_SECURITY_LVL); } -TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLtsService) -{ - const auto info = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LTS_SERVICE_NAME, 7435)); - const auto cap = firstCapability(info); - - ASSERT_EQ(cap.getProtocolId(), CONST_LTS_STREAMING_ID); - ASSERT_EQ(cap.getProtocolName(), CONST_LTS_STREAMING_ID); - ASSERT_EQ(cap.getPrefix(), CONST_LTS_STREAMING_PREFIX); - ASSERT_EQ(cap.getProtocolGroupId(), CONST_LT_PROTOCOL_GROUP_ID); - ASSERT_EQ(cap.getProtocolType(), ProtocolType::Streaming); - ASSERT_EQ(cap.getPort(), 7435); - ASSERT_EQ(info.getDeviceType().getId(), CONST_LTS_STREAMING_ID); - ASSERT_EQ(cap.getProtocolSecurityLevel(), CONST_LTS_STREAMING_SECURITY_LVL); -} - TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLegacyWsService) { const auto info = populateDiscoveredDevice(makeDiscoveredDevice(CONST_WS_SERVICE_NAME, 7425)); @@ -578,16 +361,6 @@ TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLegacyWsServi ASSERT_EQ(cap.getProtocolSecurityLevel(), CONST_LT_STREAMING_SECURITY_LVL); } -TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceSecurityLevel) -{ - const auto insecure = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LT_SERVICE_NAME)); - const auto secure = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LTS_SERVICE_NAME, 7415)); - - ASSERT_EQ(firstCapability(insecure).getProtocolSecurityLevel(), CONST_LT_STREAMING_SECURITY_LVL); - ASSERT_EQ(firstCapability(secure).getProtocolSecurityLevel(), CONST_LTS_STREAMING_SECURITY_LVL); - ASSERT_GT(firstCapability(secure).getProtocolSecurityLevel(), firstCapability(insecure).getProtocolSecurityLevel()); -} - TEST_F(WebsocketStreamingClientModuleTest, CompleteServerCapabilityDefaultPortPerScheme) { auto module = CreateModule(); @@ -597,10 +370,12 @@ TEST_F(WebsocketStreamingClientModuleTest, CompleteServerCapabilityDefaultPortPe ASSERT_TRUE(completeServerCapability(module, makeSourceCapability(), target)); ASSERT_EQ(target.getPort(), DEFAULT_WS_STREAMING_PORT); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS auto secureTarget = ServerCapability(CONST_LTS_STREAMING_ID, CONST_LTS_STREAMING_ID, ProtocolType::Streaming); secureTarget.setPrefix(CONST_LTS_STREAMING_PREFIX); ASSERT_TRUE(completeServerCapability(module, makeSourceCapability(), secureTarget)); ASSERT_EQ(secureTarget.getPort(), DEFAULT_WSS_STREAMING_PORT); +#endif } TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceUnknownServiceThrows) @@ -662,39 +437,35 @@ TEST_F(WebsocketStreamingClientModuleTest, CompleteServerCapabilityAcceptsBothId { auto module = CreateModule(); - for (const auto& protocolId : {CONST_LT_STREAMING_ID, CONST_LTS_STREAMING_ID}) { - auto target = ServerCapability(protocolId, protocolId, ProtocolType::Streaming); - ASSERT_TRUE(completeServerCapability(module, makeSourceCapability(), target)) << protocolId; + auto target = ServerCapability(CONST_LT_STREAMING_ID, CONST_LT_STREAMING_ID, ProtocolType::Streaming); + ASSERT_TRUE(completeServerCapability(module, makeSourceCapability(), target)); + } + + { + auto target = ServerCapability(CONST_LTS_STREAMING_ID, CONST_LTS_STREAMING_ID, ProtocolType::Streaming); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + ASSERT_TRUE(completeServerCapability(module, makeSourceCapability(), target)); +#else + ASSERT_FALSE(completeServerCapability(module, makeSourceCapability(), target)); +#endif } auto foreign = ServerCapability("OpenDAQOPCUA", "OpenDAQOPCUA", ProtocolType::Configuration); ASSERT_FALSE(completeServerCapability(module, makeSourceCapability(), foreign)); } -TEST_F(WebsocketStreamingClientModuleTest, CompleteServerCapabilityBuildsString) -{ - auto module = CreateModule(); - - auto target = ServerCapability(CONST_LTS_STREAMING_ID, CONST_LTS_STREAMING_ID, ProtocolType::Streaming); - target.setPrefix(CONST_LTS_STREAMING_PREFIX); - target.setPort(7435); - - ASSERT_TRUE(completeServerCapability(module, makeSourceCapability("192.168.1.10", "daq.opcua"), target)); - - ASSERT_EQ(target.getConnectionString(), "daq.lts://192.168.1.10:7435"); - ASSERT_EQ(target.getAddresses().getCount(), 1u); - ASSERT_EQ(target.getAddresses()[0], "192.168.1.10"); -} - TEST_F(WebsocketStreamingClientModuleTest, ConstantsMatchPublicIds) { ASSERT_EQ(WsStreaming::createType().getId(), CONST_LT_STREAMING_ID); ASSERT_EQ(WsStreaming::createType().getConnectionStringPrefix(), CONST_LT_STREAMING_PREFIX); - ASSERT_EQ(WsStreaming::createSecureType().getId(), CONST_LTS_STREAMING_ID); - ASSERT_EQ(WsStreaming::createSecureType().getConnectionStringPrefix(), CONST_LTS_STREAMING_PREFIX); const auto deviceTypes = CreateModule().getAvailableDeviceTypes(); ASSERT_EQ(deviceTypes.get(CONST_LT_STREAMING_ID).getConnectionStringPrefix(), CONST_LT_STREAMING_PREFIX); + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + ASSERT_EQ(WsStreaming::createSecureType().getId(), CONST_LTS_STREAMING_ID); + ASSERT_EQ(WsStreaming::createSecureType().getConnectionStringPrefix(), CONST_LTS_STREAMING_PREFIX); ASSERT_EQ(deviceTypes.get(CONST_LTS_STREAMING_ID).getConnectionStringPrefix(), CONST_LTS_STREAMING_PREFIX); +#endif } diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.h b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.h new file mode 100644 index 0000000..7bdb671 --- /dev/null +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace daq; +using namespace daq::websocket_streaming; +using namespace daq::modules::websocket_streaming_client_module; + +class WebsocketStreamingClientModuleTest : public testing::Test +{ +protected: + using ConnectionParameters = WebsocketStreamingClientModule::ConnectionParameters; + + static StringPtr formConnectionString(const StringPtr& connectionString, + const PropertyObjectPtr& config, + ConnectionParameters* outParams = nullptr) + { + return WebsocketStreamingClientModule::formConnectionString(connectionString, config, outParams); + } + + static StringPtr createUrlConnectionString(const StringPtr& host, const IntegerPtr& port, const StringPtr& path) + { + return WebsocketStreamingClientModule::createUrlConnectionString(false, host, port, path); + } + + static StringPtr createUrlConnectionString(bool secureType, const StringPtr& host, const IntegerPtr& port, const StringPtr& path) + { + return WebsocketStreamingClientModule::createUrlConnectionString(secureType, host, port, path); + } + + static bool isSecureConnection(const std::string& connectionString) + { + return WebsocketStreamingClientModule::isSecureConnection(connectionString); + } + + static bool acceptsConnectionParameters(const ModulePtr& module, const StringPtr& connectionString, const PropertyObjectPtr& config) + { + return reinterpret_cast(module.getObject())->acceptsConnectionParameters(connectionString, config); + } + + static bool acceptsStreamingConnectionParameters(const ModulePtr& module, const StringPtr& connectionString, const PropertyObjectPtr& config) + { + return reinterpret_cast(module.getObject())->acceptsStreamingConnectionParameters(connectionString, config); + } + + static DeviceInfoPtr populateDiscoveredDevice(const discovery::MdnsDiscoveredDevice& discoveredDevice) + { + return WebsocketStreamingClientModule::populateDiscoveredDevice(discoveredDevice); + } + + static Bool completeServerCapability(const ModulePtr& module, + const ServerCapabilityPtr& source, + const ServerCapabilityConfigPtr& target) + { + return module.completeServerCapability(source, target); + } + + static discovery::MdnsDiscoveredDevice makeDiscoveredDevice(const std::string& serviceName, + uint32_t servicePort = 7414, + const std::unordered_set& ipv4 = {"192.168.1.10"}, + const std::unordered_set& ipv6 = {}, + const std::unordered_map& properties = {}) + { + discovery::MdnsDiscoveredDevice device{}; + device.serviceName = serviceName; + device.servicePort = servicePort; + device.ipv4Addresses = ipv4; + device.ipv6Addresses = ipv6; + device.properties = properties; + return device; + } + + static ServerCapabilityPtr firstCapability(const DeviceInfoPtr& info) + { + const auto caps = info.getServerCapabilities(); + EXPECT_EQ(caps.getCount(), 1u); + return caps[0]; + } + + static ServerCapabilityConfigPtr makeSourceCapability(const std::string& address = "192.168.1.10", + const std::string& prefix = "daq.opcua") + { + auto source = ServerCapability("OpenDAQOPCUA", "OpenDAQOPCUA", ProtocolType::Configuration); + source.setConnectionType("TCP/IP"); + source.setPrefix(String(prefix)); + source.addAddress(String(address)); + source.addAddressInfo(AddressInfoBuilder() + .setAddress(String(address)) + .setReachabilityStatus(AddressReachabilityStatus::Reachable) + .setType("IPv4") + .setConnectionString(String(prefix + "://" + address + ":4840")) + .build()); + return source; + } +}; + +inline ModulePtr CreateModule() +{ + ModulePtr module; + createModule(&module, NullContext()); + return module; +} diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module_tls.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module_tls.cpp new file mode 100644 index 0000000..d980f4d --- /dev/null +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module_tls.cpp @@ -0,0 +1,149 @@ +// Tests for the daq.lts:// (TLS) streaming channel of the LT streaming client module. +// This file is compiled only when DAQMODULES_LT_STREAMING_ENABLE_TLS is on + +#include "test_websocket_streaming_client_module.h" + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingDefaultConfigRejectsEmptyCertKey) +{ + auto module = CreateModule(); + ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", nullptr), + InvalidParameterException, + "TLS certificate or key file path is not configured"); +} + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingRejectsEmptyCaWithMtlsDisabled) +{ + auto module = CreateModule(); + auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); + config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, False); + + ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), + InvalidParameterException, + "TLS CA certificate file path is not configured"); +} + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingRejectsEmptyCaWithMtlsEnabled) +{ + auto module = CreateModule(); + auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); + config.setPropertyValue(PROPERTY_WSS_CERT_FILE_PATH_CLIENT, "/tmp/cert.pem"); + config.setPropertyValue(PROPERTY_WSS_KEY_FILE_PATH_CLIENT, "/tmp/key.pem"); + + ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), + InvalidParameterException, + "TLS CA certificate file path is not configured"); +} + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingCompletesNullConfig) +{ + // A missing configuration is completed with the secure defaults, so the constructor reaches + // the certificate check instead of dereferencing a null configuration object. + ASSERT_THROW_MSG((createWithImplementation( + String("daq.lts://127.0.0.1:1/"), NullContext(), nullptr)), + InvalidParameterException, + "TLS certificate or key file path is not configured"); +} + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingCompletesPartialConfig) +{ + // A configuration carrying only the port is completed with the missing TLS properties. + auto module = CreateModule(); + auto config = PropertyObject(); + config.addProperty(IntProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT, DEFAULT_WSS_STREAMING_PORT)); + + ASSERT_THROW_MSG(module.createStreaming("daq.lts://127.0.0.1:1/", config), + InvalidParameterException, + "TLS certificate or key file path is not configured"); +} + +TEST_F(WebsocketStreamingClientModuleTest, DefaultSecureStreamingConfig) +{ + auto module = CreateModule(); + auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); + ASSERT_TRUE(config.assigned()); + + ASSERT_EQ(config.getAllProperties().getCount(), 6u); + + ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT)); + ASSERT_TRUE(config.hasProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT)); + ASSERT_TRUE(config.hasProperty(PROPERTY_ENABLE_MTLS_CLIENT)); + ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT)); + ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT)); + ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT)); + + ASSERT_EQ(config.getProperty(PROPERTY_WSS_STREAMING_PORT_CLIENT).getValueType(), CoreType::ctInt); + ASSERT_EQ(config.getProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT).getValueType(), CoreType::ctBool); + ASSERT_EQ(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getValueType(), CoreType::ctBool); + + ASSERT_EQ(config.getPropertyValue(PROPERTY_WSS_STREAMING_PORT_CLIENT), DEFAULT_WSS_STREAMING_PORT); + ASSERT_EQ(config.getPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT), DEFAULT_VERIFY_SERVER_CERT); + ASSERT_EQ(config.getPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT), DEFAULT_ENABLE_MTLS); + + config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, True); + ASSERT_TRUE(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getVisible()); + ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_TRUE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); + config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, False); + ASSERT_TRUE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); + + // Nothing below the verification switch is meaningful once it is off + config.setPropertyValue(PROPERTY_ENABLE_MTLS_CLIENT, True); + config.setPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT, False); + ASSERT_TRUE(config.getProperty(PROPERTY_VERIFY_SERVER_CERT_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_ENABLE_MTLS_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CERT_FILE_PATH_CLIENT).getVisible()); + ASSERT_FALSE(config.getProperty(PROPERTY_WSS_KEY_FILE_PATH_CLIENT).getVisible()); +} + +TEST_F(WebsocketStreamingClientModuleTest, SecureStreamingWithoutVerificationNeedsNoCa) +{ + auto module = CreateModule(); + auto config = module.getAvailableStreamingTypes().get("OpenDAQLTStreamingSecure").createDefaultConfig(); + config.setPropertyValue(PROPERTY_VERIFY_SERVER_CERT_CLIENT, False); + + ASSERT_THROW(module.createStreaming("daq.lts://127.0.0.1:1/", config), NotFoundException); +} + +TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceLtsService) +{ + const auto info = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LTS_SERVICE_NAME, 7435)); + const auto cap = firstCapability(info); + + ASSERT_EQ(cap.getProtocolId(), CONST_LTS_STREAMING_ID); + ASSERT_EQ(cap.getProtocolName(), CONST_LTS_STREAMING_ID); + ASSERT_EQ(cap.getPrefix(), CONST_LTS_STREAMING_PREFIX); + ASSERT_EQ(cap.getProtocolGroupId(), CONST_LT_PROTOCOL_GROUP_ID); + ASSERT_EQ(cap.getProtocolType(), ProtocolType::Streaming); + ASSERT_EQ(cap.getPort(), 7435); + ASSERT_EQ(info.getDeviceType().getId(), CONST_LTS_STREAMING_ID); + ASSERT_EQ(cap.getProtocolSecurityLevel(), CONST_LTS_STREAMING_SECURITY_LVL); +} + +TEST_F(WebsocketStreamingClientModuleTest, PopulateDiscoveredDeviceSecurityLevel) +{ + const auto insecure = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LT_SERVICE_NAME)); + const auto secure = populateDiscoveredDevice(makeDiscoveredDevice(CONST_LTS_SERVICE_NAME, 7415)); + + ASSERT_EQ(firstCapability(insecure).getProtocolSecurityLevel(), CONST_LT_STREAMING_SECURITY_LVL); + ASSERT_EQ(firstCapability(secure).getProtocolSecurityLevel(), CONST_LTS_STREAMING_SECURITY_LVL); + ASSERT_GT(firstCapability(secure).getProtocolSecurityLevel(), firstCapability(insecure).getProtocolSecurityLevel()); +} + +TEST_F(WebsocketStreamingClientModuleTest, CompleteServerCapabilityBuildsString) +{ + auto module = CreateModule(); + + auto target = ServerCapability(CONST_LTS_STREAMING_ID, CONST_LTS_STREAMING_ID, ProtocolType::Streaming); + target.setPrefix(CONST_LTS_STREAMING_PREFIX); + target.setPort(7435); + + ASSERT_TRUE(completeServerCapability(module, makeSourceCapability("192.168.1.10", "daq.opcua"), target)); + + ASSERT_EQ(target.getConnectionString(), "daq.lts://192.168.1.10:7435"); + ASSERT_EQ(target.getAddresses().getCount(), 1u); + ASSERT_EQ(target.getAddresses()[0], "192.168.1.10"); +} diff --git a/modules/websocket_streaming_server_module/tests/CMakeLists.txt b/modules/websocket_streaming_server_module/tests/CMakeLists.txt index 5c73a00..a1d007d 100644 --- a/modules/websocket_streaming_server_module/tests/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/tests/CMakeLists.txt @@ -5,6 +5,10 @@ set(TEST_SOURCES test_websocket_streaming_server_module.cpp test_app.cpp ) +if (${REPO_OPTION_PREFIX}_ENABLE_TLS) + list(APPEND TEST_SOURCES test_websocket_streaming_server_module_tls.cpp) +endif() + add_executable(${TEST_APP} ${TEST_SOURCES} ) diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index 969467b..71ba788 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -1,62 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace daq; -using namespace daq::websocket_streaming; - -class WsStreamingServerModuleTest : public testing::Test -{ -public: - void TearDown() override - { - } - - static ContextPtr CreateContextWithModuleOptions(const DictPtr& moduleOptions) - { - auto options = Dict(); - options.set("Modules", Dict({{"StreamingLtServer", moduleOptions}})); - - return NullContext(Logger(), TypeManager(), options); - } - - static PropertyObjectPtr CreateWsOnlyConfig(const ModulePtr& module, Int wsPort) - { - auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); - config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, True); - config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_WS_STREAMING_PORT_SERVER, wsPort); - return config; - } -}; - -static ModulePtr CreateModule(ContextPtr context = NullContext()) -{ - ModulePtr module; - createModule(&module, context); - return module; -} - -static PropertyObjectPtr CreateServerConfig() -{ - auto module = CreateModule(); - auto serverTypes = module.getAvailableServerTypes(); - return serverTypes.get("OpenDAQLTStreaming").createDefaultConfig(); -} +#include "test_websocket_streaming_server_module.h" TEST_F(WsStreamingServerModuleTest, CreateModule) { @@ -114,7 +56,11 @@ TEST_F(WsStreamingServerModuleTest, ServerConfig) auto config = CreateServerConfig(); ASSERT_TRUE(config.assigned()); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_EQ(config.getAllProperties().getCount(), 11u); +#else + ASSERT_EQ(config.getAllProperties().getCount(), 5u); +#endif ASSERT_TRUE(config.hasProperty(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER)); ASSERT_EQ(config.getProperty(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER).getValueType(), CoreType::ctBool); @@ -132,6 +78,8 @@ TEST_F(WsStreamingServerModuleTest, ServerConfig) ASSERT_EQ(config.getProperty(PROPERTY_WS_CONTROL_PORT_SERVER).getValueType(), CoreType::ctInt); ASSERT_EQ(config.getPropertyValue(PROPERTY_WS_CONTROL_PORT_SERVER), DEFAULT_WS_CONTROL_PORT); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + ASSERT_TRUE(config.hasProperty(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER)); ASSERT_EQ(config.getProperty(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER).getValueType(), CoreType::ctBool); ASSERT_EQ(config.getPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER), DEFAULT_ENABLE_WSS_STREAMING_PORT); @@ -153,6 +101,17 @@ TEST_F(WsStreamingServerModuleTest, ServerConfig) ASSERT_TRUE(config.hasProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_SERVER)); ASSERT_EQ(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_SERVER).getValueType(), CoreType::ctString); +#else + + ASSERT_FALSE(config.hasProperty(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER)); + ASSERT_FALSE(config.hasProperty(PROPERTY_ENABLE_MTLS_SERVER)); + ASSERT_FALSE(config.hasProperty(PROPERTY_WSS_STREAMING_PORT_SERVER)); + ASSERT_FALSE(config.hasProperty(PROPERTY_WSS_CERT_FILE_PATH_SERVER)); + ASSERT_FALSE(config.hasProperty(PROPERTY_WSS_KEY_FILE_PATH_SERVER)); + ASSERT_FALSE(config.hasProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_SERVER)); + +#endif + ASSERT_TRUE(config.hasProperty(PROPERTY_PATH_SERVER)); ASSERT_EQ(config.getPropertyValue(PROPERTY_PATH_SERVER), "/"); } @@ -171,6 +130,8 @@ TEST_F(WsStreamingServerModuleTest, ServerConfigVisibility) config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); ASSERT_FALSE(config.getProperty(PROPERTY_WS_CONTROL_PORT_SERVER).getVisible()); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, False); ASSERT_FALSE(config.getProperty(PROPERTY_ENABLE_MTLS_SERVER).getVisible()); ASSERT_FALSE(config.getProperty(PROPERTY_WSS_STREAMING_PORT_SERVER).getVisible()); @@ -193,55 +154,8 @@ TEST_F(WsStreamingServerModuleTest, ServerConfigVisibility) config.setPropertyValue(PROPERTY_ENABLE_MTLS_SERVER, True); config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, False); ASSERT_FALSE(config.getProperty(PROPERTY_WSS_CA_CERT_FILE_PATH_SERVER).getVisible()); -} - -TEST_F(WsStreamingServerModuleTest, CreateServerRejectsMtlsWithoutCa) -{ - const auto instance = Instance(); - auto module = CreateModule(instance.getContext()); - - auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); - config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); - ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), - InvalidParameterException, - "Mutual TLS is enabled but no CA certificate file path is configured"); -} - -TEST_F(WsStreamingServerModuleTest, CreateServerRejectsEmptyCertificatePath) -{ - const auto instance = Instance(); - auto module = CreateModule(instance.getContext()); - - auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); - config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); - config.setPropertyValue(PROPERTY_ENABLE_MTLS_SERVER, False); - config.setPropertyValue(PROPERTY_WSS_KEY_FILE_PATH_SERVER, "some-key.pem"); - - ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), - InvalidParameterException, - "TLS certificate or key file path is not configured"); -} - -TEST_F(WsStreamingServerModuleTest, CreateServerRejectsEmptyKeyPath) -{ - const auto instance = Instance(); - auto module = CreateModule(instance.getContext()); - - auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); - config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); - config.setPropertyValue(PROPERTY_ENABLE_MTLS_SERVER, False); - config.setPropertyValue(PROPERTY_WSS_CERT_FILE_PATH_SERVER, "some-cert.pem"); - - ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), - InvalidParameterException, - "TLS certificate or key file path is not configured"); +#endif } TEST_F(WsStreamingServerModuleTest, AddCapabilityWsOnly) @@ -321,7 +235,11 @@ TEST_F(WsStreamingServerModuleTest, PartialConfigIsCompleted) ASSERT_NO_THROW((server = createWithImplementation( instance.getRootDevice(), config, instance.getContext()))); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_EQ(config.getAllProperties().getCount(), 11u); +#else + ASSERT_EQ(config.getAllProperties().getCount(), 5u); +#endif ASSERT_TRUE(config.hasProperty(PROPERTY_PATH_SERVER)); ASSERT_EQ(config.getPropertyValue(PROPERTY_PATH_SERVER), "/"); ASSERT_EQ(config.getPropertyValue(PROPERTY_WS_STREAMING_PORT_SERVER), DEFAULT_WS_STREAMING_PORT); @@ -344,7 +262,9 @@ TEST_F(WsStreamingServerModuleTest, PartialConfigPreservesUserValues) instance.getRootDevice(), config, instance.getContext()); ASSERT_EQ(config.getPropertyValue(PROPERTY_WS_STREAMING_PORT_SERVER), 7655); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_EQ(config.getPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER), DEFAULT_ENABLE_WSS_STREAMING_PORT); +#endif ASSERT_EQ(instance.getRootDevice().getInfo().getServerCapability(CONST_LT_STREAMING_ID).getPort(), 7655); server.stop(); @@ -364,7 +284,11 @@ TEST_F(WsStreamingServerModuleTest, PartialConfigKeepsForeignProperties) ASSERT_NO_THROW((server = createWithImplementation( instance.getRootDevice(), config, instance.getContext()))); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_EQ(config.getAllProperties().getCount(), 12u); +#else + ASSERT_EQ(config.getAllProperties().getCount(), 6u); +#endif ASSERT_EQ(config.getPropertyValue("SomeForeignProperty"), "keep me"); server.stop(); @@ -378,26 +302,17 @@ TEST_F(WsStreamingServerModuleTest, CreateServerRejectsAllChannelsDisabled) auto config = CreateWsOnlyConfig(module, 7657); config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), InvalidParameterException, "Neither the websocket streaming port nor the TLS streaming port is enabled"); - - ASSERT_FALSE(instance.getRootDevice().getInfo().hasServerCapability(CONST_LT_STREAMING_ID)); -} - -TEST_F(WsStreamingServerModuleTest, CreateServerRejectsControlPortWithoutStreamingPort) -{ - const auto instance = Instance(); - auto module = CreateModule(instance.getContext()); - - auto config = CreateWsOnlyConfig(module, 7658); - config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); - config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, True); - config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); - +#else ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), InvalidParameterException, - "The control port cannot be enabled without the websocket streaming port"); + "The websocket streaming port is not enabled"); +#endif + + ASSERT_FALSE(instance.getRootDevice().getInfo().hasServerCapability(CONST_LT_STREAMING_ID)); } TEST_F(WsStreamingServerModuleTest, ProviderOptionsOverrideDefaults) @@ -433,7 +348,11 @@ TEST_F(WsStreamingServerModuleTest, ProviderOptionsIgnoreUnknownKeys) PropertyObjectPtr config; ASSERT_NO_THROW(config = WsStreamingServer::createDefaultConfig(context)); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS ASSERT_EQ(config.getAllProperties().getCount(), 11u); +#else + ASSERT_EQ(config.getAllProperties().getCount(), 5u); +#endif ASSERT_FALSE(config.hasProperty("NoSuchProperty")); ASSERT_EQ(config.getPropertyValue(PROPERTY_WS_STREAMING_PORT_SERVER), 7661); } diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.h b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.h new file mode 100644 index 0000000..bc53de7 --- /dev/null +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace daq; +using namespace daq::websocket_streaming; + +class WsStreamingServerModuleTest : public testing::Test +{ +public: + void TearDown() override + { + } + + static ContextPtr CreateContextWithModuleOptions(const DictPtr& moduleOptions) + { + auto options = Dict(); + options.set("Modules", Dict({{"StreamingLtServer", moduleOptions}})); + + return NullContext(Logger(), TypeManager(), options); + } + + static PropertyObjectPtr CreateWsOnlyConfig(const ModulePtr& module, Int wsPort) + { + auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); + config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, True); + config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, False); +#endif + config.setPropertyValue(PROPERTY_WS_STREAMING_PORT_SERVER, wsPort); + return config; + } +}; + +inline ModulePtr CreateModule(ContextPtr context = NullContext()) +{ + ModulePtr module; + createModule(&module, context); + return module; +} + +inline PropertyObjectPtr CreateServerConfig() +{ + auto module = CreateModule(); + auto serverTypes = module.getAvailableServerTypes(); + return serverTypes.get("OpenDAQLTStreaming").createDefaultConfig(); +} diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module_tls.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module_tls.cpp new file mode 100644 index 0000000..0d627ac --- /dev/null +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module_tls.cpp @@ -0,0 +1,68 @@ +// Tests for the TLS configuration of the LT streaming server module. +// This file is compiled only when DAQMODULES_LT_STREAMING_ENABLE_TLS is on + +#include "test_websocket_streaming_server_module.h" + +TEST_F(WsStreamingServerModuleTest, CreateServerRejectsMtlsWithoutCa) +{ + const auto instance = Instance(); + auto module = CreateModule(instance.getContext()); + + auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); + config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); + + ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), + InvalidParameterException, + "Mutual TLS is enabled but no CA certificate file path is configured"); +} + +TEST_F(WsStreamingServerModuleTest, CreateServerRejectsEmptyCertificatePath) +{ + const auto instance = Instance(); + auto module = CreateModule(instance.getContext()); + + auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); + config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); + config.setPropertyValue(PROPERTY_ENABLE_MTLS_SERVER, False); + config.setPropertyValue(PROPERTY_WSS_KEY_FILE_PATH_SERVER, "some-key.pem"); + + ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), + InvalidParameterException, + "TLS certificate or key file path is not configured"); +} + +TEST_F(WsStreamingServerModuleTest, CreateServerRejectsEmptyKeyPath) +{ + const auto instance = Instance(); + auto module = CreateModule(instance.getContext()); + + auto config = module.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); + config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); + config.setPropertyValue(PROPERTY_ENABLE_MTLS_SERVER, False); + config.setPropertyValue(PROPERTY_WSS_CERT_FILE_PATH_SERVER, "some-cert.pem"); + + ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), + InvalidParameterException, + "TLS certificate or key file path is not configured"); +} + +TEST_F(WsStreamingServerModuleTest, CreateServerRejectsControlPortWithoutStreamingPort) +{ + const auto instance = Instance(); + auto module = CreateModule(instance.getContext()); + + auto config = CreateWsOnlyConfig(module, 7658); + config.setPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER, False); + config.setPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER, True); + config.setPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, True); + + ASSERT_THROW_MSG(module.createServer("OpenDAQLTStreaming", instance.getRootDevice(), config), + InvalidParameterException, + "The control port cannot be enabled without the websocket streaming port"); +} diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/common.h b/shared/libraries/websocket_streaming/include/websocket_streaming/common.h index f5e10ba..cc6e438 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/common.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/common.h @@ -16,5 +16,11 @@ #pragma once +#if DAQMODULES_LT_STREAMING_ENABLE_TLS && !WS_STREAMING_ENABLE_TLS +#error "DAQMODULES_LT_STREAMING_ENABLE_TLS is on, but ws-streaming was built without TLS support. \ +Rebuild ws-streaming with WS_STREAMING_ENABLE_TLS=ON, or configure this project with \ +DAQMODULES_LT_STREAMING_ENABLE_TLS=OFF." +#endif + #define BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING namespace daq::websocket_streaming { #define END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING } diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming.h index becc226..14f5572 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming.h @@ -79,17 +79,28 @@ class WsStreaming : public Streaming */ static StreamingTypePtr createType(); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + /*! * @brief Creates an openDAQ streaming type object using the `daq.lts://` prefix * with secure TLS channel. * + * Only available in a build with the TLS channel enabled. + * * @return An openDAQ streaming type object using the `daq.lts://` prefix. */ static StreamingTypePtr createSecureType(); +#endif + static PropertyObjectPtr createDefaultConfig(); + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + static PropertyObjectPtr createDefaultSecureConfig(); +#endif + public: /*! diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_device.h b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_device.h index fb92bd4..a0bde83 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_device.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_device.h @@ -59,17 +59,28 @@ class WsStreamingDevice : public Device */ static DeviceTypePtr createNewType(); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + /*! * @brief Creates an openDAQ device type for the new-style `daq.lts://` connection string * with secure TLS channel. * + * Only available in a build with the TLS channel enabled. + * * @return An openDAQ device type for the new-style `daq.lts://` connection string. */ static DeviceTypePtr createNewSecureType(); +#endif + static PropertyObjectPtr createDefaultConfig(); + +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + static PropertyObjectPtr createDefaultSecureConfig(); +#endif + /*! * @brief Opens a new WebSocket streaming connection. * diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h index 3ef3efd..7314576 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h @@ -140,6 +140,8 @@ class WsStreamingServer : public Server Int _wss_port{0}; std::string _path; bool _ws_channel_enabled{false}; + // Stays false in a build without the TLS channel: nothing ever sets it, so every + // `if (_wss_channel_enabled)` below is dead code the compiler drops. bool _wss_channel_enabled{false}; bool _capability_added{false}; diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index 4c73434..b8b43d9 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -51,6 +51,10 @@ target_link_libraries(${LIB_NAME} ws-streaming::ws-streaming ) +if (${REPO_OPTION_PREFIX}_ENABLE_TLS) + target_compile_definitions(${LIB_NAME} PUBLIC ${REPO_OPTION_PREFIX}_ENABLE_TLS) +endif() + if (MSVC) target_compile_options(${LIB_NAME} PRIVATE /bigobj) endif() diff --git a/shared/libraries/websocket_streaming/src/ws_streaming.cpp b/shared/libraries/websocket_streaming/src/ws_streaming.cpp index 5c83460..5f41b20 100644 --- a/shared/libraries/websocket_streaming/src/ws_streaming.cpp +++ b/shared/libraries/websocket_streaming/src/ws_streaming.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -38,10 +37,16 @@ #include #include +#if DAQMODULES_LT_STREAMING_ENABLE_TLS +#include +#endif + using namespace std::placeholders; BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + namespace { bool isTlsRejection(bool isSecureChannel, const boost::system::error_code& ec) @@ -58,6 +63,8 @@ bool isTlsRejection(bool isSecureChannel, const boost::system::error_code& ec) } +#endif + StreamingTypePtr WsStreaming::createType() { return StreamingTypeBuilder() @@ -69,6 +76,8 @@ StreamingTypePtr WsStreaming::createType() .build(); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + StreamingTypePtr WsStreaming::createSecureType() { return StreamingTypeBuilder() @@ -80,6 +89,8 @@ StreamingTypePtr WsStreaming::createSecureType() .build(); } +#endif + WsStreaming::WsStreaming( const StringPtr& connectionString, const ContextPtr& context, @@ -101,8 +112,20 @@ WsStreaming::WsStreaming( boost::replace_all(wsConnectionString, "daq.wss://", "wss://"); bool isSecureChannel = wsConnectionString.find("wss://") != std::string::npos; +#if !DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureChannel) + { + DAQ_THROW_EXCEPTION(InvalidParameterException, + "Cannot connect to {}: this module was built without TLS support", + connectionString.toStdString()); + } + +#endif + const auto effectiveConfig = populateConfigFromDefault(config, isSecureChannel); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (isSecureChannel) { LOG_I("Secure channel requested, enabling TLS"); @@ -159,6 +182,8 @@ WsStreaming::WsStreaming( } } +#endif + // Start the ws-streaming connection attempt. LOG_I("Connecting to {}", wsConnectionString); wsClient.async_connect(wsConnectionString, @@ -177,6 +202,8 @@ WsStreaming::WsStreaming( ioContext.stop(); thread.join(); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + // A failure raised by the TLS layer means the peer was reached but not trusted. // That is an authentication problem. if (ec.category() == boost::asio::error::get_ssl_category()) @@ -195,6 +222,8 @@ WsStreaming::WsStreaming( connectionString.toStdString(), ec.message()); } +#endif + DAQ_THROW_EXCEPTION(NotFoundException, "Failed to connect to {}: {}", connectionString.toStdString(), ec.message()); } @@ -232,7 +261,12 @@ WsStreaming::~WsStreaming() PropertyObjectPtr WsStreaming::populateConfigFromDefault(const PropertyObjectPtr& config, bool secure) { +#if DAQMODULES_LT_STREAMING_ENABLE_TLS const auto defaultConfig = secure ? createDefaultSecureConfig() : createDefaultConfig(); +#else + (void) secure; + const auto defaultConfig = createDefaultConfig(); +#endif if (!config.assigned()) return defaultConfig; @@ -256,6 +290,8 @@ PropertyObjectPtr WsStreaming::createDefaultConfig() return obj; } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + PropertyObjectPtr WsStreaming::createDefaultSecureConfig() { constexpr Int minPortValue = 0; @@ -306,6 +342,8 @@ PropertyObjectPtr WsStreaming::createDefaultSecureConfig() return defaultConfig; } +#endif + void WsStreaming::onSetActive(bool active) { } diff --git a/shared/libraries/websocket_streaming/src/ws_streaming_device.cpp b/shared/libraries/websocket_streaming/src/ws_streaming_device.cpp index 923074a..9439029 100644 --- a/shared/libraries/websocket_streaming/src/ws_streaming_device.cpp +++ b/shared/libraries/websocket_streaming/src/ws_streaming_device.cpp @@ -51,6 +51,8 @@ DeviceTypePtr WsStreamingDevice::createNewType() .build(); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + DeviceTypePtr WsStreamingDevice::createNewSecureType() { return DeviceTypeBuilder() @@ -62,6 +64,8 @@ DeviceTypePtr WsStreamingDevice::createNewSecureType() .build(); } +#endif + WsStreamingDevice::WsStreamingDevice( const ContextPtr& context, const ComponentPtr& parent, @@ -91,11 +95,15 @@ PropertyObjectPtr WsStreamingDevice::createDefaultConfig() return WsStreaming::createDefaultConfig(); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + PropertyObjectPtr WsStreamingDevice::createDefaultSecureConfig() { return WsStreaming::createDefaultSecureConfig(); } +#endif + void WsStreamingDevice::removed() { streamingEvents.clear(); diff --git a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp index 38cc842..03a4f16 100644 --- a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp @@ -107,6 +107,8 @@ PropertyObjectPtr WsStreamingServer::createDefaultConfig() defaultConfig.addProperty(builder.build()); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + { auto builder = BoolPropertyBuilder(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER, DEFAULT_ENABLE_WSS_STREAMING_PORT); defaultConfig.addProperty(builder.build()); @@ -145,6 +147,8 @@ PropertyObjectPtr WsStreamingServer::createDefaultConfig() defaultConfig.addProperty(builder.build()); } +#endif + defaultConfig.addProperty(StringProperty(PROPERTY_PATH_SERVER, "/")); return defaultConfig; } @@ -187,14 +191,21 @@ WsStreamingServer::WsStreamingServer( addDefaultConfig(this->config); _ws_channel_enabled = (this->config.getPropertyValue(PROPERTY_ENABLE_WS_STREAMING_PORT_SERVER).asPtr().getValue(False) == True); +#if DAQMODULES_LT_STREAMING_ENABLE_TLS _wss_channel_enabled = (this->config.getPropertyValue(PROPERTY_ENABLE_WSS_STREAMING_PORT_SERVER).asPtr().getValue(False) == True); +#endif const bool control_channel_enabled = (this->config.getPropertyValue(PROPERTY_ENABLE_WS_CONTROL_PORT_SERVER).asPtr().getValue(False) == True); if (!_ws_channel_enabled && !_wss_channel_enabled) { +#if DAQMODULES_LT_STREAMING_ENABLE_TLS DAQ_THROW_EXCEPTION(InvalidParameterException, "Neither the websocket streaming port nor the TLS streaming port is enabled"); +#else + DAQ_THROW_EXCEPTION(InvalidParameterException, + "The websocket streaming port is not enabled"); +#endif } if (control_channel_enabled && !_ws_channel_enabled) @@ -213,6 +224,8 @@ WsStreamingServer::WsStreamingServer( _server.add_listener(config.getPropertyValue(PROPERTY_WS_CONTROL_PORT_SERVER), true); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (_wss_channel_enabled) { std::string ca_cert; @@ -243,6 +256,9 @@ WsStreamingServer::WsStreamingServer( DAQ_THROW_EXCEPTION(InvalidParameterException, "Cannot load the TLS secrets: {}", e.what()); } } + +#endif + _path = config.getPropertyValue(PROPERTY_PATH_SERVER).asPtr().toStdString(); _onClientConnected = _server.on_client_connected.connect( @@ -301,6 +317,8 @@ ListPtr WsStreamingServer::getDiscoveryConfigs() discoveryConfigs.pushBack(std::move(discoveryConfig)); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (_wss_channel_enabled) { auto discoveryConfig = PropertyObject(); @@ -312,6 +330,8 @@ ListPtr WsStreamingServer::getDiscoveryConfigs() discoveryConfigs.pushBack(std::move(discoveryConfig)); } + +#endif return discoveryConfigs; } @@ -380,6 +400,8 @@ void WsStreamingServer::addCapability() info.asPtr(true).addServerCapability(cap); } +#if DAQMODULES_LT_STREAMING_ENABLE_TLS + if (_wss_channel_enabled) { auto cap = ServerCapability(CONST_LTS_STREAMING_ID, CONST_LTS_STREAMING_ID, ProtocolType::Streaming); @@ -391,6 +413,8 @@ void WsStreamingServer::addCapability() info.asPtr(true).addServerCapability(cap); } +#endif + _capability_added = _ws_channel_enabled || _wss_channel_enabled; }