diff --git a/.gitignore b/.gitignore index d74286b4f2..35092aadf8 100644 --- a/.gitignore +++ b/.gitignore @@ -224,6 +224,10 @@ tools/e133/e133_monitor tools/e133/e133_monitor.exe tools/e133/e133_receiver tools/e133/e133_receiver.exe +tools/e133/llrp_manager +tools/e133/llrp_manager.exe +tools/e133/llrp_target +tools/e133/llrp_target.exe tools/e133/slp_locate tools/e133/slp_locate.exe tools/e133/slp_register diff --git a/common/protocol/Ola.proto b/common/protocol/Ola.proto index 02896effa4..a1190add51 100644 --- a/common/protocol/Ola.proto +++ b/common/protocol/Ola.proto @@ -68,6 +68,7 @@ enum PluginIds { OLA_PLUGIN_GPIO = 22; OLA_PLUGIN_SPIDMX = 23; OLA_PLUGIN_NANOLEAF = 24; + OLA_PLUGIN_E133 = 25; /* * To obtain a new plugin ID, open a ticket at diff --git a/common/rdm/UIDTest.cpp b/common/rdm/UIDTest.cpp index 3a0a3acb30..9191119a15 100644 --- a/common/rdm/UIDTest.cpp +++ b/common/rdm/UIDTest.cpp @@ -166,13 +166,13 @@ void UIDTest::testRPTUID() { static_cast(0x0001ffff)); // TODO(Peter): Handle the more complicated RPT vendorcast tests OLA_ASSERT_TRUE(rpt_all_controllers.IsBroadcast()); - // OLA_ASSERT_FALSE(rpt_all_controllers.IsVendorcast()); + OLA_ASSERT_FALSE(rpt_all_controllers.IsVendorcast()); OLA_ASSERT_TRUE(rpt_all_devices.IsBroadcast()); - // OLA_ASSERT_FALSE(rpt_all_devices.IsVendorcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsBroadcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsVendorcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsBroadcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsVendorcast()); + OLA_ASSERT_FALSE(rpt_all_devices.IsVendorcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsBroadcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsVendorcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsBroadcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsVendorcast()); } diff --git a/configure.ac b/configure.ac index 3e9e809afe..c54571a452 100644 --- a/configure.ac +++ b/configure.ac @@ -894,6 +894,7 @@ PLUGIN_SUPPORT(artnet, USE_ARTNET) PLUGIN_SUPPORT(dmx4linux, USE_DMX4LINUX, [$have_dmx4linux]) PLUGIN_SUPPORT(dummy, USE_DUMMY) PLUGIN_SUPPORT(e131, USE_E131) +PLUGIN_SUPPORT(e133, USE_E133) PLUGIN_SUPPORT(espnet, USE_ESPNET) PLUGIN_SUPPORT(ftdidmx, USE_FTDI, [$have_libftdi]) PLUGIN_SUPPORT(gpio, USE_GPIO) diff --git a/libs/acn/BrokerNullInflatorTest.cpp b/libs/acn/BrokerNullInflatorTest.cpp index bf2d98da32..8f3ada3589 100644 --- a/libs/acn/BrokerNullInflatorTest.cpp +++ b/libs/acn/BrokerNullInflatorTest.cpp @@ -36,6 +36,7 @@ using ola::network::HostToNetwork; class BrokerNullInflatorTest: public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(BrokerNullInflatorTest); +// CPPUNIT_TEST(testDecodeHeader); CPPUNIT_TEST(testInflatePDU); CPPUNIT_TEST_SUITE_END(); diff --git a/libs/acn/LLRPProbeRequestPDU.cpp b/libs/acn/LLRPProbeRequestPDU.cpp index 341bf11f60..ecb9c9b21f 100644 --- a/libs/acn/LLRPProbeRequestPDU.cpp +++ b/libs/acn/LLRPProbeRequestPDU.cpp @@ -30,7 +30,6 @@ namespace acn { using ola::io::OutputStream; using ola::network::HostToNetwork; using ola::rdm::UID; -using std::vector; unsigned int LLRPProbeRequestPDU::DataSize() const { llrp_probe_request_pdu_data data; @@ -103,6 +102,7 @@ void LLRPProbeRequestPDU::PrependPDU(ola::io::IOStack *stack, filter |= FILTER_BROKERS_ONLY; } data.filter = HostToNetwork(filter); + // TODO(Peter): We need to check we've got <= 200 UIDs here known_uids.Pack(data.known_uids, sizeof(data.known_uids)); stack->Write(reinterpret_cast(&data), static_cast(sizeof(llrp_probe_request_pdu_data) - diff --git a/libs/acn/Makefile.mk b/libs/acn/Makefile.mk index c7acf94b06..de2432be19 100644 --- a/libs/acn/Makefile.mk +++ b/libs/acn/Makefile.mk @@ -59,28 +59,11 @@ libs_acn_libolae131core_la_SOURCES = \ libs/acn/E131Sender.cpp \ libs/acn/E131Sender.h \ libs/acn/HeaderSet.h \ - libs/acn/LLRPHeader.h \ - libs/acn/LLRPInflator.cpp \ - libs/acn/LLRPInflator.h \ - libs/acn/LLRPProbeReplyInflator.cpp \ - libs/acn/LLRPProbeReplyInflator.h \ - libs/acn/LLRPProbeReplyPDU.cpp \ - libs/acn/LLRPProbeReplyPDU.h \ - libs/acn/LLRPProbeRequestInflator.cpp \ - libs/acn/LLRPProbeRequestInflator.h \ - libs/acn/LLRPProbeRequestPDU.cpp \ - libs/acn/LLRPProbeRequestPDU.h \ - libs/acn/LLRPPDU.cpp \ - libs/acn/LLRPPDU.h \ libs/acn/PDU.cpp \ libs/acn/PDU.h \ libs/acn/PDUTestCommon.h \ libs/acn/PreamblePacker.cpp \ libs/acn/PreamblePacker.h \ - libs/acn/RDMInflator.cpp \ - libs/acn/RDMInflator.h \ - libs/acn/RDMPDU.cpp \ - libs/acn/RDMPDU.h \ libs/acn/RootHeader.h \ libs/acn/RootInflator.cpp \ libs/acn/RootInflator.h \ @@ -88,15 +71,6 @@ libs_acn_libolae131core_la_SOURCES = \ libs/acn/RootPDU.h \ libs/acn/RootSender.cpp \ libs/acn/RootSender.h \ - libs/acn/RPTHeader.h \ - libs/acn/RPTInflator.cpp \ - libs/acn/RPTInflator.h \ - libs/acn/RPTNotificationInflator.h \ - libs/acn/RPTPDU.cpp \ - libs/acn/RPTPDU.h \ - libs/acn/RPTRequestInflator.h \ - libs/acn/RPTRequestPDU.cpp \ - libs/acn/RPTRequestPDU.h \ libs/acn/TCPTransport.cpp \ libs/acn/TCPTransport.h \ libs/acn/Transport.h \ @@ -113,32 +87,31 @@ libs_acn_libolae131core_la_LIBADD = $(uuid_LIBS) \ # libolae133core.la # This needs to be after libolaacn.la and libolae131core.la since it depends on # them. Otherwise it breaks the freeBSD build -# TODO(Peter): Re-add these classes -# libs/acn/BrokerConnectedClientListInflator.cpp -# libs/acn/BrokerConnectedClientListInflator.h -# libs/acn/BrokerHeader.h -# libs/acn/BrokerManager.cpp -# libs/acn/BrokerManagerImpl.cpp -# libs/acn/BrokerManagerImpl.h libs_acn_libolae133core_la_SOURCES = \ libs/acn/BrokerClientAddInflator.h \ libs/acn/BrokerClientEntryChangeInflator.h \ libs/acn/BrokerClientEntryHeader.h \ libs/acn/BrokerClientEntryPDU.cpp \ libs/acn/BrokerClientEntryPDU.h \ - libs/acn/BrokerClientEntryRPTPDU.cpp \ - libs/acn/BrokerClientEntryRPTPDU.h \ libs/acn/BrokerClientEntryRPTInflator.cpp \ libs/acn/BrokerClientEntryRPTInflator.h \ + libs/acn/BrokerClientEntryRPTPDU.cpp \ + libs/acn/BrokerClientEntryRPTPDU.h \ libs/acn/BrokerClientEntryUpdateInflator.h \ libs/acn/BrokerClientRemoveInflator.h \ + libs/acn/BrokerConnectedClientListInflator.cpp \ + libs/acn/BrokerConnectedClientListInflator.h \ libs/acn/BrokerConnectPDU.cpp \ libs/acn/BrokerConnectPDU.h \ libs/acn/BrokerConnectReplyInflator.cpp \ libs/acn/BrokerConnectReplyInflator.h \ libs/acn/BrokerFetchClientListPDU.cpp \ libs/acn/BrokerFetchClientListPDU.h \ + libs/acn/BrokerHeader.h \ libs/acn/BrokerInflator.h \ + libs/acn/BrokerManager.cpp \ + libs/acn/BrokerManagerImpl.cpp \ + libs/acn/BrokerManagerImpl.h \ libs/acn/BrokerNullInflator.h \ libs/acn/BrokerNullPDU.cpp \ libs/acn/BrokerNullPDU.h \ @@ -157,7 +130,35 @@ libs_acn_libolae133core_la_SOURCES = \ libs/acn/E133StatusInflator.h \ libs/acn/E133StatusPDU.cpp \ libs/acn/E133StatusPDU.h \ - libs/acn/MessageBuilder.cpp + libs/acn/LLRPHeader.h \ + libs/acn/LLRPInflator.cpp \ + libs/acn/LLRPInflator.h \ + libs/acn/LLRPProbeReplyInflator.cpp \ + libs/acn/LLRPProbeReplyInflator.h \ + libs/acn/LLRPProbeReplyPDU.cpp \ + libs/acn/LLRPProbeReplyPDU.h \ + libs/acn/LLRPProbeRequestInflator.cpp \ + libs/acn/LLRPProbeRequestInflator.h \ + libs/acn/LLRPProbeRequestPDU.cpp \ + libs/acn/LLRPProbeRequestPDU.h \ + libs/acn/LLRPPDU.cpp \ + libs/acn/LLRPPDU.h \ + libs/acn/MessageBuilder.cpp \ + libs/acn/RDMInflator.cpp \ + libs/acn/RDMInflator.h \ + libs/acn/RDMPDU.cpp \ + libs/acn/RDMPDU.h \ + libs/acn/RPTHeader.h \ + libs/acn/RPTInflator.cpp \ + libs/acn/RPTInflator.h \ + libs/acn/RPTNotificationInflator.h \ + libs/acn/RPTPDU.cpp \ + libs/acn/RPTPDU.h \ + libs/acn/RPTRequestInflator.h \ + libs/acn/RPTRequestPDU.cpp \ + libs/acn/RPTRequestPDU.h \ + libs/acn/RPTStatusInflator.cpp \ + libs/acn/RPTStatusInflator.h libs_acn_libolae133core_la_CXXFLAGS = \ $(COMMON_E133_CXXFLAGS) $(uuid_CFLAGS) @@ -208,6 +209,7 @@ libs_acn_E131Tester_LDADD = \ libs/acn/libolae131core.la \ $(COMMON_TESTING_LIBS) +# libs/acn/BrokerClientEntryRPTInflatorTest.cpp # libs/acn/BrokerInflatorTest.cpp libs_acn_E133Tester_SOURCES = \ libs/acn/BrokerClientEntryPDUTest.cpp \ @@ -238,6 +240,7 @@ libs_acn_LLRPTester_SOURCES = \ libs_acn_LLRPTester_CPPFLAGS = $(COMMON_TESTING_FLAGS) libs_acn_LLRPTester_LDADD = \ libs/acn/libolae131core.la \ + libs/acn/libolae133core.la \ $(COMMON_TESTING_LIBS) libs_acn_TransportTester_SOURCES = \ diff --git a/libs/acn/RDMInflator.h b/libs/acn/RDMInflator.h index aa2cb73add..4d4e5517da 100644 --- a/libs/acn/RDMInflator.h +++ b/libs/acn/RDMInflator.h @@ -46,8 +46,8 @@ class RDMInflator: public BaseInflator { const std::string& // rdm data > GenericRDMMessageHandler; - // TODO(Peter): Set a better default vector for RDM use (possibly the RPT - // one) + // TODO(Peter): Set a better default vector for RDM use (possibly the RPT + // one) explicit RDMInflator(unsigned int vector = ola::acn::VECTOR_FRAMING_RDMNET); ~RDMInflator() {} diff --git a/olad/DynamicPluginLoader.cpp b/olad/DynamicPluginLoader.cpp index d5b11e60ec..233df1eb69 100644 --- a/olad/DynamicPluginLoader.cpp +++ b/olad/DynamicPluginLoader.cpp @@ -39,6 +39,10 @@ #include "plugins/e131/E131Plugin.h" #endif // USE_E131 +#ifdef USE_E133 +#include "plugins/e133/E133Plugin.h" +#endif // USE_E133 + #ifdef USE_ESPNET #include "plugins/espnet/EspNetPlugin.h" #endif // USE_ESPNET @@ -159,6 +163,10 @@ void DynamicPluginLoader::PopulatePlugins() { m_plugins.push_back(new ola::plugin::e131::E131Plugin(m_plugin_adaptor)); #endif // USE_E131 +#ifdef USE_E133 + m_plugins.push_back(new ola::plugin::e133::E133Plugin(m_plugin_adaptor)); +#endif // USE_E133 + #ifdef USE_ESPNET m_plugins.push_back(new ola::plugin::espnet::EspNetPlugin(m_plugin_adaptor)); #endif // USE_ESPNET diff --git a/plugins/Makefile.mk b/plugins/Makefile.mk index b298128a1e..67e3e56ff9 100644 --- a/plugins/Makefile.mk +++ b/plugins/Makefile.mk @@ -23,6 +23,7 @@ if !USING_WIN32 include plugins/usbpro/Makefile.mk include plugins/dmx4linux/Makefile.mk include plugins/e131/Makefile.mk +include plugins/e133/Makefile.mk include plugins/uartdmx/Makefile.mk endif diff --git a/plugins/e133/E133Plugin.cpp b/plugins/e133/E133Plugin.cpp new file mode 100644 index 0000000000..02018a957f --- /dev/null +++ b/plugins/e133/E133Plugin.cpp @@ -0,0 +1,185 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * E133Plugin.cpp + * The E1.33 plugin for ola + * Copyright (C) 2024 Peter Newman + */ + +#include +#include + +#include "ola/Logging.h" +#include "ola/network/NetworkUtils.h" +#include "ola/network/SocketAddress.h" +#include "ola/StringUtils.h" +#include "ola/acn/CID.h" +#include "olad/PluginAdaptor.h" +#include "olad/Preferences.h" +#include "plugins/e133/E133Device.h" +#include "plugins/e133/E133Plugin.h" +#include "plugins/e133/E133PluginDescription.h" + +namespace ola { +namespace plugin { +namespace e133 { + +using ola::acn::CID; +using ola::network::IPV4SocketAddress; +using std::string; + +const char E133Plugin::CID_KEY[] = "cid"; +const unsigned int E133Plugin::DEFAULT_DSCP_VALUE = 0; +const char E133Plugin::DSCP_KEY[] = "dscp"; +const char E133Plugin::INPUT_PORT_COUNT_KEY[] = "input_ports"; +const char E133Plugin::IP_KEY[] = "ip"; +const char E133Plugin::OUTPUT_PORT_COUNT_KEY[] = "output_ports"; +const char E133Plugin::PLUGIN_NAME[] = "E1.33 (RDMNet)"; +const char E133Plugin::PLUGIN_PREFIX[] = "e133"; +const char E133Plugin::PREPEND_HOSTNAME_KEY[] = "prepend_hostname"; +const char E133Plugin::TARGET_SOCKET_KEY[] = "target_socket"; +const unsigned int E133Plugin::DEFAULT_PORT_COUNT = 5; + + +/* + * Start the plugin + */ +bool E133Plugin::StartHook() { + CID cid = CID::FromString(m_preferences->GetValue(CID_KEY)); + string ip_addr = m_preferences->GetValue(IP_KEY); + + E133Device::E133DeviceOptions options; + if (m_preferences->GetValueAsBool(PREPEND_HOSTNAME_KEY)) { + std::ostringstream str; + str << ola::network::Hostname() << "-" << m_plugin_adaptor->InstanceName(); +// options.source_name = str.str(); + } else { +// options.source_name = m_plugin_adaptor->InstanceName(); + } + + unsigned int dscp; + if (!StringToInt(m_preferences->GetValue(DSCP_KEY), &dscp)) { + OLA_WARN << "Can't convert dscp value " << + m_preferences->GetValue(DSCP_KEY) << " to int"; +// options.dscp = 0; + } else { + // shift 2 bits left +// options.dscp = dscp << 2; + } + + if (!StringToInt(m_preferences->GetValue(INPUT_PORT_COUNT_KEY), + &options.input_ports)) { + OLA_WARN << "Invalid value for input_ports"; + } + + if (!StringToInt(m_preferences->GetValue(OUTPUT_PORT_COUNT_KEY), + &options.output_ports)) { + OLA_WARN << "Invalid value for output_ports"; + } + + IPV4SocketAddress socket_address; + if (!IPV4SocketAddress::FromString(m_preferences->GetValue(TARGET_SOCKET_KEY), &socket_address)) { + OLA_WARN << "Invalid value for " << TARGET_SOCKET_KEY; + } + + m_device = new E133Device(this, cid, ip_addr, socket_address, m_plugin_adaptor, options); + + if (!m_device->Start()) { + delete m_device; + return false; + } + + m_plugin_adaptor->RegisterDevice(m_device); + return true; +} + + +/* + * Stop the plugin + * @return true on success, false on failure + */ +bool E133Plugin::StopHook() { + if (m_device) { + m_plugin_adaptor->UnregisterDevice(m_device); + bool ret = m_device->Stop(); + delete m_device; + return ret; + } + return true; +} + + +/* + * Return the description for this plugin + */ +string E133Plugin::Description() const { + return plugin_description; +} + + +/* + * Load the plugin prefs and default to sensible values + * + */ +bool E133Plugin::SetDefaultPreferences() { + if (!m_preferences) + return false; + + bool save = false; + + CID cid = CID::FromString(m_preferences->GetValue(CID_KEY)); + if (cid.IsNil()) { + cid = CID::Generate(); + m_preferences->SetValue(CID_KEY, cid.ToString()); + save = true; + } + + save |= m_preferences->SetDefaultValue( + DSCP_KEY, + UIntValidator(0, 63), + DEFAULT_DSCP_VALUE); + + save |= m_preferences->SetDefaultValue( + INPUT_PORT_COUNT_KEY, + UIntValidator(0, 512), + DEFAULT_PORT_COUNT); + + save |= m_preferences->SetDefaultValue( + OUTPUT_PORT_COUNT_KEY, + UIntValidator(0, 512), + DEFAULT_PORT_COUNT); + + save |= m_preferences->SetDefaultValue(IP_KEY, StringValidator(true), ""); + + save |= m_preferences->SetDefaultValue( + PREPEND_HOSTNAME_KEY, + BoolValidator(), + true); + + IPV4SocketAddress socket_address; + if (!IPV4SocketAddress::FromString(m_preferences->GetValue(TARGET_SOCKET_KEY), &socket_address)) { + m_preferences->SetValue(TARGET_SOCKET_KEY, ""); + save = true; + } + + if (save) { + m_preferences->Save(); + } + + return true; +} +} // namespace e133 +} // namespace plugin +} // namespace ola diff --git a/plugins/e133/E133Plugin.h b/plugins/e133/E133Plugin.h new file mode 100644 index 0000000000..68059f302d --- /dev/null +++ b/plugins/e133/E133Plugin.h @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * E133Plugin.h + * Interface for the E1.33 plugin class + * Copyright (C) 2024 Peter Newman + */ + +#ifndef PLUGINS_E133_E133PLUGIN_H_ +#define PLUGINS_E133_E133PLUGIN_H_ + +#include +#include "olad/Plugin.h" +#include "ola/plugin_id.h" + +namespace ola { +namespace plugin { +namespace e133 { + +class E133Device; + +class E133Plugin: public ola::Plugin { + public: + explicit E133Plugin(ola::PluginAdaptor *plugin_adaptor): + ola::Plugin(plugin_adaptor), + m_device(NULL) {} + ~E133Plugin() {} + + std::string Name() const { return PLUGIN_NAME; } + ola_plugin_id Id() const { return OLA_PLUGIN_E133; } + std::string Description() const; + std::string PluginPrefix() const { return PLUGIN_PREFIX; } + + private: + bool StartHook(); + bool StopHook(); + bool SetDefaultPreferences(); + + E133Device *m_device; + static const char CID_KEY[]; + static const unsigned int DEFAULT_DSCP_VALUE; + static const unsigned int DEFAULT_PORT_COUNT; + static const char DSCP_KEY[]; + static const char INPUT_PORT_COUNT_KEY[]; + static const char IP_KEY[]; + static const char OUTPUT_PORT_COUNT_KEY[]; + static const char PLUGIN_NAME[]; + static const char PLUGIN_PREFIX[]; + static const char PREPEND_HOSTNAME_KEY[]; + static const char TARGET_SOCKET_KEY[]; +}; +} // namespace e133 +} // namespace plugin +} // namespace ola +#endif // PLUGINS_E133_E133PLUGIN_H_ diff --git a/plugins/e133/Makefile.mk b/plugins/e133/Makefile.mk new file mode 100644 index 0000000000..d64378a62c --- /dev/null +++ b/plugins/e133/Makefile.mk @@ -0,0 +1,35 @@ +# LIBRARIES +################################################## + +if USE_E133 +lib_LTLIBRARIES += plugins/e133/libolae133.la + +# Plugin description is generated from README.md +built_sources += plugins/e133/E133PluginDescription.h +nodist_plugins_e133_libolae133_la_SOURCES = \ + plugins/e133/E133PluginDescription.h +plugins/e133/E133PluginDescription.h: plugins/e133/README.md plugins/e133/Makefile.mk plugins/convert_README_to_header.sh + sh $(top_srcdir)/plugins/convert_README_to_header.sh $(top_srcdir)/plugins/e133 $(top_builddir)/plugins/e133/E133PluginDescription.h + +plugins_e133_libolae133_la_SOURCES = \ + plugins/e133/E133Device.cpp \ + plugins/e133/E133Device.h \ + plugins/e133/E133Plugin.cpp \ + plugins/e133/E133Plugin.h \ + plugins/e133/E133Port.cpp \ + plugins/e133/E133Port.h \ + plugins/e133/E133PortImpl.cpp \ + plugins/e133/E133PortImpl.h +plugins_e133_libolae133_la_CXXFLAGS = $(COMMON_PROTOBUF_CXXFLAGS) $(uuid_CFLAGS) + +# plugins/e133/messages/libolae133conf.la +plugins_e133_libolae133_la_LIBADD = \ + $(uuid_LIBS) \ + common/libolacommon.la \ + olad/plugin_api/libolaserverplugininterface.la \ + libs/acn/libolaacn.la \ + libs/acn/libolae131core.la \ + libs/acn/libolae133core.la +endif + +EXTRA_DIST += plugins/e133/README.md diff --git a/plugins/e133/README.md b/plugins/e133/README.md new file mode 100644 index 0000000000..56ca954d07 --- /dev/null +++ b/plugins/e133/README.md @@ -0,0 +1,26 @@ +E1.33 (RDMNet) Plugin +===================================== + +This plugin creates a single device with a configurable number of input and +output ports. + +Each port can be assigned to a different E1.33 Universe. + + +## Config file: `ola-e133.conf` + +`cid = 00010203-0405-0607-0809-0A0B0C0D0E0F` +The CID to use for this device. + +#`dscp = [int]` +#The DSCP value to tag the packets with, range is 0 to 63. + +`input_ports = [int]` +The number of input ports to create up to an arbitrary max of 512. + +`ip = [a.b.c.d|]` +The IP address or interface name to bind to. If not specified it will use +the first non-loopback interface. + +`output_ports = [int]` +The number of output ports to create up to an arbitrary max of 512. diff --git a/tools/e133/E133Endpoint.h b/tools/e133/E133Endpoint.h index 7b8b58c160..f1d9e174e5 100644 --- a/tools/e133/E133Endpoint.h +++ b/tools/e133/E133Endpoint.h @@ -29,7 +29,7 @@ using ola::rdm::UIDSet; using std::string; - +// TODO(Peter): Can this become NULL_ENDPOINT? static const uint16_t ROOT_E133_ENDPOINT = 0; /** diff --git a/tools/e133/Makefile.mk b/tools/e133/Makefile.mk index c8f6b3d6e3..76377e1add 100644 --- a/tools/e133/Makefile.mk +++ b/tools/e133/Makefile.mk @@ -70,7 +70,9 @@ noinst_PROGRAMS += \ tools/e133/basic_device \ tools/e133/e133_controller \ tools/e133/e133_monitor \ - tools/e133/e133_receiver + tools/e133/e133_receiver \ + tools/e133/llrp_manager \ + tools/e133/llrp_target tools_e133_e133_receiver_SOURCES = tools/e133/e133-receiver.cpp tools_e133_e133_receiver_LDADD = common/libolacommon.la \ @@ -109,3 +111,15 @@ tools_e133_basic_device_LDADD = common/libolacommon.la \ libs/acn/libolaacn.la \ libs/acn/libolae133core.la \ tools/e133/libolae133common.la + +tools_e133_llrp_manager_SOURCES = tools/e133/llrp-manager.cpp +tools_e133_llrp_manager_LDADD = common/libolacommon.la \ + libs/acn/libolaacn.la \ + libs/acn/libolae133core.la \ + tools/e133/libolae133common.la + +tools_e133_llrp_target_SOURCES = tools/e133/llrp-target.cpp +tools_e133_llrp_target_LDADD = common/libolacommon.la \ + libs/acn/libolaacn.la \ + libs/acn/libolae133core.la \ + tools/e133/libolae133common.la diff --git a/tools/e133/llrp-manager.cpp b/tools/e133/llrp-manager.cpp new file mode 100644 index 0000000000..aa9f742277 --- /dev/null +++ b/tools/e133/llrp-manager.cpp @@ -0,0 +1,452 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * llrp-manager.cpp + * Run a very simple E1.33 LLRP Manager. + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libs/acn/HeaderSet.h" +#include "libs/acn/LLRPHeader.h" +#include "libs/acn/LLRPInflator.h" +#include "libs/acn/LLRPPDU.h" +#include "libs/acn/LLRPProbeReplyInflator.h" +#include "libs/acn/LLRPProbeReplyPDU.h" +#include "libs/acn/LLRPProbeRequestPDU.h" +#include "libs/acn/PreamblePacker.h" +#include "libs/acn/RDMInflator.h" +#include "libs/acn/RDMPDU.h" +#include "libs/acn/RootHeader.h" +#include "libs/acn/RootInflator.h" +#include "libs/acn/RootSender.h" +#include "libs/acn/Transport.h" +#include "libs/acn/UDPTransport.h" + +using std::string; +using std::vector; +using std::auto_ptr; + +using ola::acn::CID; +using ola::acn::IncomingUDPTransport; +using ola::acn::LLRPHeader; +using ola::acn::LLRPProbeReplyInflator; +using ola::acn::LLRPProbeReplyPDU; +using ola::acn::LLRPProbeRequestPDU; +using ola::acn::OutgoingUDPTransport; +using ola::acn::OutgoingUDPTransportImpl; +using ola::acn::RootHeader; +using ola::network::Interface; +using ola::network::IPV4Address; +using ola::network::IPV4SocketAddress; +using ola::network::MACAddress; +using ola::network::NetworkToHost; +using ola::rdm::PidStoreHelper; +using ola::rdm::RDMGetRequest; +using ola::rdm::RDMReply; +using ola::rdm::RDMRequest; +using ola::rdm::RDMResponse; +using ola::rdm::RDMSetRequest; +using ola::rdm::UID; +using ola::rdm::UIDSet; + +DEFINE_string(manager_uid, "7a70:00000002", "The UID of the manager."); +DEFINE_default_bool(set, false, "Send a set rather than a get."); +DEFINE_default_bool(allow_loopback, false, "Include the loopback interface."); +DEFINE_s_string(interface, i, "", + "The interface name (e.g. eth0) or IP address of the network " + "interface to use for LLRP messages."); + +auto_ptr picker( + ola::network::InterfacePicker::NewPicker()); +ola::network::Interface m_interface; +ola::network::UDPSocket m_socket; +uint8_t *m_recv_buffer; +std::auto_ptr manager_uid; +std::auto_ptr m_pid_helper; +ola::SequenceNumber m_llrp_transaction_number_sequence; +ola::SequenceNumber m_rdm_transaction_number_sequence; + +std::string pid_name; +vector rdm_inputs; + +ola::acn::PreamblePacker m_packer; +CID cid = CID::Generate(); +ola::acn::RootSender m_root_sender(cid, true); + +bool CheckCIDAddressedToUs(const CID destination_cid) { + return (destination_cid == CID::LLRPBroadcastCID() || + destination_cid == cid); +} + +void SendLLRPProbeRequest() { + LLRPHeader llrp_header = LLRPHeader(CID::LLRPBroadcastCID(), + m_llrp_transaction_number_sequence.Next()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.133"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + LLRPProbeRequestPDU probe_request( + LLRPProbeRequestPDU::VECTOR_PROBE_REQUEST_DATA, + *UID::FromString("0000:00000000"), + *UID::FromString("ffff:ffffffff"), + false, + false, + UIDSet()); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_PROBE_REQUEST, llrp_header, &probe_request); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +void HandleLLRPProbeReply( + const ola::acn::HeaderSet *headers, + const LLRPProbeReplyInflator::LLRPProbeReply &reply) { + OLA_DEBUG << "Potentially handling probe reply from " << reply.uid; + + const LLRPHeader llrp_header = headers->GetLLRPHeader(); + if (!CheckCIDAddressedToUs(llrp_header.DestinationCid())) { + OLA_INFO << "Ignoring probe request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + const RootHeader root_header = headers->GetRootHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader rdm_llrp_header = LLRPHeader(root_header.GetCid(), + m_llrp_transaction_number_sequence.Next()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.133"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + bool is_set = FLAGS_set; + + // get the pid descriptor + const ola::rdm::PidDescriptor *pid_descriptor = m_pid_helper->GetDescriptor( + pid_name, + reply.uid.ManufacturerId()); + + uint16_t pid_value; + if (!pid_descriptor && + (ola::PrefixedHexStringToInt(pid_name, &pid_value) || + ola::StringToInt(pid_name, &pid_value))) { + pid_descriptor = m_pid_helper->GetDescriptor( + pid_value, + reply.uid.ManufacturerId()); + } + + if (!pid_descriptor) { + std::cout << "Unknown PID: " << pid_name << std::endl; + std::cout << "Use --list-pids to list the available PIDs." << std::endl; + return; + } + + const ola::messaging::Descriptor *descriptor = NULL; + if (is_set) { + descriptor = pid_descriptor->SetRequest(); + } else { + descriptor = pid_descriptor->GetRequest(); + } + + if (!descriptor) { + std::cout << (is_set ? "SET" : "GET") << " command not supported for " + << pid_name << std::endl; + return; + } + + // attempt to build the message + auto_ptr message(m_pid_helper->BuildMessage( + descriptor, + rdm_inputs)); + + if (!message.get()) { + std::cout << m_pid_helper->SchemaAsString(descriptor); + return; + } + + unsigned int param_data_length; + const uint8_t *param_data = m_pid_helper->SerializeMessage( + message.get(), + ¶m_data_length); + + RDMRequest *request; + if (is_set) { + request = new RDMSetRequest( + *manager_uid, + reply.uid, + m_rdm_transaction_number_sequence.Next(), // transaction # + 1, // port id + 0, // sub device + pid_descriptor->Value(), // param id + param_data, // data + param_data_length); // data length + } else { + request = new RDMGetRequest( + *manager_uid, + reply.uid, + m_rdm_transaction_number_sequence.Next(), // transaction # + 1, // port id + 0, // sub device + pid_descriptor->Value(), // param id + param_data, // data + param_data_length); // data length + } + + ola::io::ByteString raw_reply; + ola::rdm::RDMCommandSerializer::Pack(*request, &raw_reply); + + ola::acn::RDMPDU rdm_reply(raw_reply); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_RDM_CMD, rdm_llrp_header, &rdm_reply); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +/** + * Handle an ACK response + */ +void HandleAckResponse(uint16_t manufacturer_id, + bool is_set, + uint16_t pid, + const uint8_t *data, + unsigned int length) { + const ola::rdm::PidDescriptor *pid_descriptor = m_pid_helper->GetDescriptor( + pid, + manufacturer_id); + + if (!pid_descriptor) { + OLA_WARN << "Unknown PID: " << pid << "."; + return; + } + + const ola::messaging::Descriptor *descriptor = NULL; + if (is_set) { + descriptor = pid_descriptor->SetResponse(); + } else { + descriptor = pid_descriptor->GetResponse(); + } + + if (!descriptor) { + OLA_WARN << "Unknown response message: " << (is_set ? "SET" : "GET") << + " " << pid_descriptor->Name(); + return; + } + + auto_ptr message( + m_pid_helper->DeserializeMessage(descriptor, data, length)); + + if (!message.get()) { + OLA_WARN << "Unable to inflate RDM response"; + return; + } + + std::cout << m_pid_helper->PrettyPrintMessage(manufacturer_id, + is_set, + pid, + message.get()); +} + +void HandleRDM( + const ola::acn::HeaderSet *headers, + const string &raw_response) { + IPV4SocketAddress target = headers->GetTransportHeader().Source(); + OLA_INFO << "Got RDM response from " << target; + + if (!CheckCIDAddressedToUs(headers->GetLLRPHeader().DestinationCid())) { + OLA_INFO << "Ignoring RDM response as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + ola::rdm::RDMStatusCode status_code; + // attempt to unpack as a request + ola::rdm::RDMResponse *response = ola::rdm::RDMResponse::InflateFromData( + reinterpret_cast(raw_response.data()), + raw_response.size(), + &status_code); + + OLA_DEBUG << "Got status code " << ola::rdm::StatusCodeToString(status_code); + + if (!response) { + OLA_WARN << "Failed to unpack LLRP RDM message, ignoring request."; + return; + } else { + OLA_DEBUG << "Got RDM response " << response->ToString(); + } + + if (!response->DestinationUID().DirectedToUID(*manager_uid)) { + OLA_WARN << "Destination UID " << response->DestinationUID() << " was not " + << "directed to us"; + return; + } + + OLA_INFO << "Got RDM response from " << response->SourceUID(); + if (response->ResponseType() == ola::rdm::RDM_ACK) { + HandleAckResponse(response->SourceUID().ManufacturerId(), + (response->CommandClass() == + ola::rdm::RDMCommand::SET_COMMAND_RESPONSE), + response->ParamId(), + response->ParamData(), + response->ParamDataSize()); + } else if (response->ResponseType() == ola::rdm::RDM_NACK_REASON) { + uint16_t nack_reason; + if (response->ParamDataSize() != sizeof(nack_reason)) { + OLA_WARN << "Invalid NACK reason size of " << response->ParamDataSize(); + } else { + memcpy(reinterpret_cast(&nack_reason), response->ParamData(), + sizeof(nack_reason)); + nack_reason = NetworkToHost(nack_reason); + std::cout << "Request NACKed: " << + ola::rdm::NackReasonToString(nack_reason) << std::endl; + } + } else { + OLA_WARN << "Unknown RDM response type " + << ola::strings::ToHex(response->ResponseType()); + } +} + +int main(int argc, char* argv[]) { + ola::AppInit(&argc, argv, "[options]", "Run a very simple E1.33 LLRP Manager."); + + if (argc >= 2) { + pid_name = argv[1]; + + // split out rdm message params from the pid name (ignore program name) + rdm_inputs.resize(argc - 2); + for(int i = 0; i < argc - 2; i++) { + rdm_inputs[i] = argv[i+2]; + } + OLA_DEBUG << "Parsed RDM"; + } else { + OLA_INFO << "No RDM to parse"; + } + + m_pid_helper.reset(new PidStoreHelper("")); + m_pid_helper->Init(); + + manager_uid.reset(UID::FromString(FLAGS_manager_uid)); + if (!manager_uid.get()) { + OLA_WARN << "Invalid UID: " << FLAGS_manager_uid; + ola::DisplayUsage(); + exit(ola::EXIT_USAGE); + } else { + OLA_INFO << "Started LLRP Manager with UID " << *manager_uid; + } + + ola::io::SelectServer ss; + + if (!m_socket.Init()) { + return false; + } + std::cout << "Init!" << std::endl; + + std::cout << "Using CID " << cid << std::endl; + + IPV4Address *addr = IPV4Address::FromString("239.255.250.134"); + + if (!m_socket.Bind(IPV4SocketAddress(*addr, + ola::acn::LLRP_PORT))) { + return false; + } + std::cout << "Bind!" << std::endl; + + const std::string m_preferred_ip = FLAGS_interface; + ola::network::InterfacePicker::Options options; + options.include_loopback = FLAGS_allow_loopback; + if (!picker->ChooseInterface(&m_interface, m_preferred_ip, options)) { + OLA_INFO << "Failed to find an interface"; + return false; + } + + std::cout << "IF " << m_interface << std::endl; + + // If we enable multicast loopback, we can test two bits of software on the + // same machine, but we get, and must ignore, all our own requests too + if (!m_socket.JoinMulticast(m_interface.ip_address, *addr, true)) { + OLA_WARN << "Failed to join multicast group " << addr; + return false; + } + + ola::acn::RootInflator root_inflator; + ola::acn::LLRPInflator llrp_inflator; + ola::acn::LLRPProbeReplyInflator llrp_probe_reply_inflator; + llrp_probe_reply_inflator.SetLLRPProbeReplyHandler( + ola::NewCallback(&HandleLLRPProbeReply)); + ola::acn::RDMInflator llrp_rdm_inflator(ola::acn::VECTOR_LLRP_RDM_CMD); + llrp_rdm_inflator.SetGenericRDMHandler( + ola::NewCallback(&HandleRDM)); + + // setup all the inflators + root_inflator.AddInflator(&llrp_inflator); + llrp_inflator.AddInflator(&llrp_probe_reply_inflator); + llrp_inflator.AddInflator(&llrp_rdm_inflator); + + IncomingUDPTransport m_incoming_udp_transport(&m_socket, &root_inflator); + m_socket.SetOnData(ola::NewCallback(&m_incoming_udp_transport, + &IncomingUDPTransport::Receive)); + ss.AddReadDescriptor(&m_socket); + + // TODO(Peter): Add the ability to filter on UID or UID+CID to avoid the probing + + // TODO(Peter): Send this three times + // TODO(Peter): Deal with known UID list etc and proper discovery + SendLLRPProbeRequest(); + ss.Run(); + + return 0; +} diff --git a/tools/e133/llrp-target.cpp b/tools/e133/llrp-target.cpp new file mode 100644 index 0000000000..b7159ec8c5 --- /dev/null +++ b/tools/e133/llrp-target.cpp @@ -0,0 +1,342 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * llrp-target.cpp + * Run a very simple E1.33 LLRP Target. + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libs/acn/HeaderSet.h" +#include "libs/acn/LLRPHeader.h" +#include "libs/acn/LLRPInflator.h" +#include "libs/acn/LLRPPDU.h" +#include "libs/acn/LLRPProbeReplyPDU.h" +#include "libs/acn/LLRPProbeRequestInflator.h" +#include "libs/acn/PreamblePacker.h" +#include "libs/acn/RDMInflator.h" +#include "libs/acn/RDMPDU.h" +#include "libs/acn/RootHeader.h" +#include "libs/acn/RootInflator.h" +#include "libs/acn/RootSender.h" +#include "libs/acn/Transport.h" +#include "libs/acn/UDPTransport.h" + +using std::string; +using std::vector; +using std::auto_ptr; + +using ola::acn::CID; +using ola::acn::IncomingUDPTransport; +using ola::acn::LLRPHeader; +using ola::acn::LLRPProbeReplyPDU; +using ola::acn::LLRPProbeRequestInflator; +using ola::acn::OutgoingUDPTransport; +using ola::acn::OutgoingUDPTransportImpl; +using ola::acn::RootHeader; +using ola::network::Interface; +using ola::network::IPV4Address; +using ola::network::IPV4SocketAddress; +using ola::network::MACAddress; +using ola::rdm::RDMReply; +using ola::rdm::RDMResponse; +using ola::rdm::UID; + +DEFINE_string(uid, "7a70:00000001", "The UID of the target."); + +auto_ptr picker( + ola::network::InterfacePicker::NewPicker()); +ola::network::Interface m_interface; +const std::string m_preferred_ip; +ola::network::UDPSocket m_socket; +uint8_t *m_recv_buffer; +std::auto_ptr target_uid; +std::auto_ptr dummy_responder; + +ola::acn::PreamblePacker m_packer; +CID cid = CID::Generate(); +ola::acn::RootSender m_root_sender(cid, true); + +bool CheckCIDAddressedToUs(const CID destination_cid) { + return (destination_cid == CID::LLRPBroadcastCID() || + destination_cid == cid); +} + +bool CompareInterfaceMACs(Interface a, Interface b) { + return a.hw_address < b.hw_address; +} + +Interface FindLowestMAC() { + // TODO(Peter): Get some clarification on whether we only care about active + // interfaces, or any installed ones? + // TODO(Peter): Work out what to do here if running on localhost only? Return + // 00:00:00:00:00:00 + vector interfaces = picker->GetInterfaces(false); + vector::iterator result = std::min_element(interfaces.begin(), + interfaces.end(), + CompareInterfaceMACs); + return *result; +} + +void HandleLLRPProbeRequest( + const ola::acn::HeaderSet *headers, + const LLRPProbeRequestInflator::LLRPProbeRequest &request) { + OLA_DEBUG << "Potentially handling probe from " << request.lower << " to " + << request.upper; + + const LLRPHeader llrp_header = headers->GetLLRPHeader(); + if (!CheckCIDAddressedToUs(llrp_header.DestinationCid())) { + OLA_INFO << "Ignoring probe request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + if ((*target_uid < request.lower) || (*target_uid > request.upper)) { + OLA_INFO << "Ignoring probe request as we are not in the target UID range"; + return; + } + + OLA_DEBUG << "Known UIDs are: " << request.known_uids; + + if (request.known_uids.Contains(*target_uid)) { + OLA_INFO << "Ignoring probe request as we are already in the known UID " + << "list"; + return; + } + + // TODO(Peter): Check the filter bits! + + const RootHeader root_header = headers->GetRootHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader reply_llrp_header = LLRPHeader(root_header.GetCid(), + llrp_header.TransactionNumber()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.134"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + LLRPProbeReplyPDU probe_reply( + LLRPProbeReplyPDU::VECTOR_PROBE_REPLY_DATA, + *target_uid, + FindLowestMAC().hw_address, + LLRPProbeReplyPDU::LLRP_COMPONENT_TYPE_NON_RDMNET); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_PROBE_REPLY, reply_llrp_header, &probe_reply); + + // TODO(Peter): Delay sending by 0 to LLRP_MAX_BACKOFF! + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +void RDMRequestComplete( + ola::acn::HeaderSet headers, + ola::rdm::RDMReply *reply) { + OLA_INFO << "Got RDM reply to send"; + OLA_DEBUG << reply->ToString(); + + const RDMResponse *response = reply->Response(); + uint8_t response_type = response->ResponseType(); + + if (response_type == ola::rdm::RDM_ACK_TIMER || + response_type == ola::rdm::ACK_OVERFLOW) { + // Technically we shouldn't have even actioned the request but we can't + // really do that in OLA, as we don't know what it might return until we've + // done it + OLA_DEBUG << "Got a disallowed ACK, mangling to NR_ACTION_NOT_SUPPORTED"; + response = NackWithReason(response, ola::rdm::NR_ACTION_NOT_SUPPORTED); + } else { + OLA_DEBUG << "Got an acceptable response type: " + << (unsigned int)response_type; + } + + const RootHeader root_header = headers.GetRootHeader(); + const LLRPHeader llrp_header = headers.GetLLRPHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader reply_llrp_header = LLRPHeader(root_header.GetCid(), + llrp_header.TransactionNumber()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.134"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + ola::io::ByteString raw_reply; + ola::rdm::RDMCommandSerializer::Pack(*response, &raw_reply); + + ola::acn::RDMPDU rdm_reply(raw_reply); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_RDM_CMD, reply_llrp_header, &rdm_reply); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent RDM PDU"; +} + +void HandleRDM( + const ola::acn::HeaderSet *headers, + const string &raw_request) { + IPV4SocketAddress target = headers->GetTransportHeader().Source(); + OLA_INFO << "Got RDM request from " << target; + + if (!CheckCIDAddressedToUs(headers->GetLLRPHeader().DestinationCid())) { + OLA_INFO << "Ignoring RDM request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + // attempt to unpack as a request + ola::rdm::RDMRequest *request = ola::rdm::RDMRequest::InflateFromData( + reinterpret_cast(raw_request.data()), + raw_request.size()); + + if (!request) { + OLA_WARN << "Failed to unpack LLRP RDM message, ignoring request."; + return; + } else { + OLA_DEBUG << "Got RDM request " << request->ToString(); + } + + if (!request->DestinationUID().DirectedToUID(*target_uid)) { + OLA_WARN << "Destination UID " << request->DestinationUID() << " was not " + << "directed to us"; + return; + } + + if (!((request->SubDevice() == ola::rdm::ROOT_RDM_DEVICE) || + (request->SubDevice() == ola::rdm::ALL_RDM_SUBDEVICES))) { + OLA_WARN << "Subdevice " << request->SubDevice() << " was not the root or " + << "broadcast subdevice, NACKing"; + // Immediately send a NACK + RDMReply reply( + ola::rdm::RDM_COMPLETED_OK, + NackWithReason(request, ola::rdm::NR_SUB_DEVICE_OUT_OF_RANGE)); + RDMRequestComplete(*headers, &reply); + } else { + // Dispatch the message to the responder + dummy_responder->SendRDMRequest( + request, + ola::NewSingleCallback(&RDMRequestComplete, + *headers)); + } +} + +int main(int argc, char* argv[]) { + ola::AppInit(&argc, argv, "[options]", "Run a very simple E1.33 LLRP Target."); + + target_uid.reset(UID::FromString(FLAGS_uid)); + if (!target_uid.get()) { + OLA_WARN << "Invalid UID: " << FLAGS_uid; + ola::DisplayUsage(); + exit(ola::EXIT_USAGE); + } else { + OLA_INFO << "Started LLRP Target with UID " << *target_uid; + } + + dummy_responder.reset(new ola::rdm::DummyResponder(*target_uid)); + + ola::io::SelectServer ss; + + if (!m_socket.Init()) { + return false; + } + std::cout << "Init!" << std::endl; + + std::cout << "Using CID " << cid << std::endl; + + if (!m_socket.Bind(IPV4SocketAddress(IPV4Address::WildCard(), + ola::acn::LLRP_PORT))) { + return false; + } + std::cout << "Bind!" << std::endl; + + IPV4Address *addr = IPV4Address::FromString("239.255.250.133"); + + ola::network::InterfacePicker::Options options; + options.include_loopback = false; + if (!picker->ChooseInterface(&m_interface, m_preferred_ip, options)) { + OLA_INFO << "Failed to find an interface"; + return false; + } + + std::cout << "IF " << m_interface << std::endl; + + // If we enable multicast loopback, we can test two bits of software on the + // same machine, but we get, and must ignore, all our own requests too + if (!m_socket.JoinMulticast(m_interface.ip_address, *addr, true)) { + OLA_WARN << "Failed to join multicast group " << addr; + return false; + } + + ola::acn::RootInflator root_inflator; + ola::acn::LLRPInflator llrp_inflator; + ola::acn::LLRPProbeRequestInflator llrp_probe_request_inflator; + llrp_probe_request_inflator.SetLLRPProbeRequestHandler( + ola::NewCallback(&HandleLLRPProbeRequest)); + ola::acn::RDMInflator llrp_rdm_inflator(ola::acn::VECTOR_LLRP_RDM_CMD); + llrp_rdm_inflator.SetGenericRDMHandler( + ola::NewCallback(&HandleRDM)); + + // setup all the inflators + root_inflator.AddInflator(&llrp_inflator); + llrp_inflator.AddInflator(&llrp_probe_request_inflator); + llrp_inflator.AddInflator(&llrp_rdm_inflator); + + IncomingUDPTransport m_incoming_udp_transport(&m_socket, &root_inflator); + m_socket.SetOnData(ola::NewCallback(&m_incoming_udp_transport, + &IncomingUDPTransport::Receive)); + ss.AddReadDescriptor(&m_socket); + + ss.Run(); + + return 0; +}