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/include/ola/acn/ACNFlags.h b/include/ola/acn/ACNFlags.h new file mode 100644 index 0000000000..a055a429b0 --- /dev/null +++ b/include/ola/acn/ACNFlags.h @@ -0,0 +1,70 @@ +/* + * 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. + * + * ACNFlags.h + * Flags used in ACN PDUs + * Copyright (C) 2020 Peter Newman + */ + +#ifndef INCLUDE_OLA_ACN_ACNFLAGS_H_ +#define INCLUDE_OLA_ACN_ACNFLAGS_H_ + +/** + * @addtogroup acn + * @{ + * @file ACNFlags.h + * @brief ACN flag values. + * @} + */ + +#include + +namespace ola { +namespace acn { + +/** + * @addtogroup acn + * @{ + */ + +// masks for the flag fields +/** + * @brief This indicates a 20 bit length field (default is 12 bits) + */ +static const uint8_t LFLAG_MASK = 0x80; + +/** + * @brief This indicates a vector is present + */ +static const uint8_t VFLAG_MASK = 0x40; + +/** + * @brief This indicates a header field is present + */ +static const uint8_t HFLAG_MASK = 0x20; + +/** + * @brief This indicates a data field is present + */ +static const uint8_t DFLAG_MASK = 0x10; + +/** + * @} + */ + +} // namespace acn +} // namespace ola + +#endif // INCLUDE_OLA_ACN_ACNFLAGS_H_ diff --git a/include/ola/acn/ACNPort.h b/include/ola/acn/ACNPort.h index bef7619c38..97f98a1ab2 100644 --- a/include/ola/acn/ACNPort.h +++ b/include/ola/acn/ACNPort.h @@ -61,6 +61,11 @@ const uint16_t ACN_PORT = 5568; */ const uint16_t E133_PORT = 5569; +/** + * @brief The port used for E1.33 LLRP communication. + */ +const uint16_t LLRP_PORT = 5569; + /** * @} */ diff --git a/include/ola/acn/ACNVectors.h b/include/ola/acn/ACNVectors.h index b45e1c0e76..72c050ea91 100644 --- a/include/ola/acn/ACNVectors.h +++ b/include/ola/acn/ACNVectors.h @@ -45,8 +45,11 @@ namespace acn { enum RootVector { VECTOR_ROOT_E131_REV2 = 3, /**< Draft E1.31, used by some old gear. */ VECTOR_ROOT_E131 = 4, /**< E1.31 (sACN) */ - VECTOR_ROOT_E133 = 5, /**< E1.33 (RDNNet) */ + VECTOR_ROOT_RPT = 5, /**< E1.33 (RPT) */ VECTOR_ROOT_NULL = 6, /**< NULL (empty) root */ + VECTOR_ROOT_BROKER = 9, /**< E1.33 (Broker) */ + VECTOR_ROOT_LLRP = 0x0A, /**< E1.33 (LLRP) */ + VECTOR_ROOT_EPT = 0x0B, /**< E1.33 (EPT) */ }; /** @@ -87,6 +90,15 @@ enum E133ControllerVector { VECTOR_CONTROLLER_EXPECT_MASTER = 5, /**< Expect master message */ }; +/** + * @brief Vectors used at the E1.33 LLRP layer. + */ +enum LLRPVector { + VECTOR_LLRP_PROBE_REQUEST = 1, /**< LLRP Probe Request */ + VECTOR_LLRP_PROBE_REPLY = 2, /**< LLRP Probe Reply */ + VECTOR_LLRP_RDM_CMD = 3, /**< LLRP RDM Command */ +}; + /** * @} */ diff --git a/include/ola/acn/CID.h b/include/ola/acn/CID.h index 91c403a5f4..295b0786c2 100644 --- a/include/ola/acn/CID.h +++ b/include/ola/acn/CID.h @@ -89,6 +89,15 @@ class CID { */ std::string ToString() const; + /** + * @brief A helper function to write a CID to an ostream. + * @param out the ostream + * @param cid the CID to write. + */ + friend std::ostream& operator<< (std::ostream &out, const CID &cid) { + return out << cid.ToString(); + } + /** * @brief Write the CID to an OutputBufferInterface */ @@ -132,6 +141,10 @@ class CID { */ static CID FromString(const std::string &cid); + static CID LLRPBroadcastCID() { + return FromString("FBAD822C-BD0C-4D4C-BDC8-7EABEBC85AFF"); + } + private: class CIDImpl *m_impl; diff --git a/include/ola/acn/Makefile.mk b/include/ola/acn/Makefile.mk index 56bc647eb5..afd628d812 100644 --- a/include/ola/acn/Makefile.mk +++ b/include/ola/acn/Makefile.mk @@ -3,6 +3,7 @@ olaacninclude_HEADERS = if INSTALL_ACN olaacninclude_HEADERS += \ + include/ola/acn/ACNFlags.h \ include/ola/acn/ACNPort.h \ include/ola/acn/ACNVectors.h \ include/ola/acn/CID.h diff --git a/libs/acn/BaseInflator.cpp b/libs/acn/BaseInflator.cpp index e3917967ad..a41dca370c 100644 --- a/libs/acn/BaseInflator.cpp +++ b/libs/acn/BaseInflator.cpp @@ -163,7 +163,7 @@ bool BaseInflator::DecodeLength(const uint8_t *data, bool BaseInflator::DecodeVector(uint8_t flags, const uint8_t *data, unsigned int length, uint32_t *vector, unsigned int *bytes_used) { - if (flags & PDU::VFLAG_MASK) { + if (flags & ola::acn::VFLAG_MASK) { if ((unsigned int) m_vector_size > length) { *vector = 0; *bytes_used = 0; @@ -223,7 +223,7 @@ bool BaseInflator::InflatePDU(HeaderSet *headers, return false; } - if (flags & PDU::HFLAG_MASK) { + if (flags & ola::acn::HFLAG_MASK) { result = DecodeHeader(headers, data + data_offset, pdu_len - data_offset, &header_bytes_used); diff --git a/libs/acn/BaseInflator.h b/libs/acn/BaseInflator.h index 384351bc28..07b7c6b88b 100644 --- a/libs/acn/BaseInflator.h +++ b/libs/acn/BaseInflator.h @@ -87,8 +87,11 @@ class BaseInflator : public InflatorInterface { unsigned int len); // masks for the flag fields - // This indicates a 20 bit length field (default is 12 bits) - static const uint8_t LFLAG_MASK = 0x80; + /** + * @brief This indicates a 20 bit length field (default is 12 bits) + * @deprecated Use ola::acn::LFLAG_MASK instead (4 Feb 2020). + */ + static const uint8_t LFLAG_MASK = ola::acn::LFLAG_MASK; // This masks the first 4 bits of the length field static const uint8_t LENGTH_MASK = 0x0F; diff --git a/libs/acn/BaseInflatorTest.cpp b/libs/acn/BaseInflatorTest.cpp index 17e86121b8..a4e0a0dd81 100644 --- a/libs/acn/BaseInflatorTest.cpp +++ b/libs/acn/BaseInflatorTest.cpp @@ -204,7 +204,7 @@ void BaseInflatorTest::testDecodeVector() { uint8_t data[] = {1, 2, 3, 4, 5, 6}; // the test data unsigned int vector = 1; unsigned int bytes_used = 0; - uint8_t flags = PDU::VFLAG_MASK; + uint8_t flags = VFLAG_MASK; OLA_ASSERT_FALSE(inflator.DecodeVector(flags, data, 0, &vector, &bytes_used)); OLA_ASSERT_EQ((unsigned int) 0, vector); @@ -235,7 +235,7 @@ void BaseInflatorTest::testDecodeVector() { } // now try with a vector size of 2 - flags = PDU::VFLAG_MASK; + flags = VFLAG_MASK; TestInflator inflator2(0, PDU::TWO_BYTES); for (unsigned int i = 0; i < 2; i++) { OLA_ASSERT_FALSE( @@ -270,7 +270,7 @@ void BaseInflatorTest::testDecodeVector() { } // now try with a vector size of 4 - flags = PDU::VFLAG_MASK; + flags = VFLAG_MASK; TestInflator inflator4(0, PDU::FOUR_BYTES); for (unsigned int i = 0; i < 4; i++) { OLA_ASSERT_FALSE( @@ -297,7 +297,7 @@ void BaseInflatorTest::testDecodeVector() { void BaseInflatorTest::testInflatePDU() { TestInflator inflator; // test with a vector size of 2 HeaderSet header_set; - uint8_t flags = PDU::VFLAG_MASK; + uint8_t flags = VFLAG_MASK; unsigned int data_size = static_cast(PDU::TWO_BYTES + sizeof(PDU_DATA)); uint8_t *data = new uint8_t[data_size]; @@ -324,7 +324,7 @@ void BaseInflatorTest::testInflatePDUBlock() { length_size + PDU::TWO_BYTES + sizeof(PDU_DATA)); uint8_t *data = new uint8_t[data_size]; // setup the vector - data[0] = PDU::VFLAG_MASK; + data[0] = VFLAG_MASK; data[1] = static_cast(data_size); data[2] = 0x01; data[3] = 0x21; @@ -337,14 +337,14 @@ void BaseInflatorTest::testInflatePDUBlock() { // inflate a multi-pdu block data = new uint8_t[2 * data_size]; - data[0] = PDU::VFLAG_MASK; + data[0] = VFLAG_MASK; data[1] = static_cast(data_size); data[2] = 0x01; data[3] = 0x21; memcpy(data + length_size + PDU::TWO_BYTES, PDU_DATA, sizeof(PDU_DATA)); - data[data_size] = PDU::VFLAG_MASK; + data[data_size] = VFLAG_MASK; data[data_size + 1] = static_cast(data_size); data[data_size + 2] = 0x01; data[data_size + 3] = 0x21; @@ -362,11 +362,11 @@ void BaseInflatorTest::testInflatePDUBlock() { unsigned int pdu_size = data_size + length_size + PDU::TWO_BYTES; data = new uint8_t[pdu_size]; - data[0] = PDU::VFLAG_MASK; + data[0] = VFLAG_MASK; data[1] = static_cast(pdu_size); data[2] = 0x01; data[3] = 0x21; - data[4] = PDU::VFLAG_MASK; + data[4] = VFLAG_MASK; data[5] = static_cast(data_size); data[6] = 0x01; data[7] = 0x21; diff --git a/libs/acn/E133Inflator.h b/libs/acn/E133Inflator.h index ee7c968f66..d2f57daf66 100644 --- a/libs/acn/E133Inflator.h +++ b/libs/acn/E133Inflator.h @@ -38,7 +38,7 @@ class E133Inflator: public BaseInflator { } ~E133Inflator() {} - uint32_t Id() const { return ola::acn::VECTOR_ROOT_E133; } + uint32_t Id() const { return ola::acn::VECTOR_ROOT_RPT; } protected: bool DecodeHeader(HeaderSet *headers, diff --git a/libs/acn/HeaderSet.h b/libs/acn/HeaderSet.h index c3e9a44f98..06a56c26aa 100644 --- a/libs/acn/HeaderSet.h +++ b/libs/acn/HeaderSet.h @@ -26,6 +26,7 @@ #include "libs/acn/DMPHeader.h" #include "libs/acn/E131Header.h" #include "libs/acn/E133Header.h" +#include "libs/acn/LLRPHeader.h" #include "libs/acn/RootHeader.h" #include "libs/acn/TransportHeader.h" @@ -56,13 +57,17 @@ class HeaderSet { const DMPHeader &GetDMPHeader() const { return m_dmp_header; } void SetDMPHeader(const DMPHeader &header) { m_dmp_header = header; } + const LLRPHeader &GetLLRPHeader() const { return m_llrp_header; } + void SetLLRPHeader(const LLRPHeader &header) { m_llrp_header = header; } + bool operator==(const HeaderSet &other) const { return ( m_transport_header == other.m_transport_header && m_root_header == other.m_root_header && m_e131_header == other.m_e131_header && m_e133_header == other.m_e133_header && - m_dmp_header == other.m_dmp_header); + m_dmp_header == other.m_dmp_header && + m_llrp_header == other.m_llrp_header); } private: @@ -71,6 +76,7 @@ class HeaderSet { E131Header m_e131_header; E133Header m_e133_header; DMPHeader m_dmp_header; + LLRPHeader m_llrp_header; }; } // namespace acn } // namespace ola diff --git a/libs/acn/LLRPHeader.h b/libs/acn/LLRPHeader.h new file mode 100644 index 0000000000..40a5098c42 --- /dev/null +++ b/libs/acn/LLRPHeader.h @@ -0,0 +1,71 @@ +/* + * 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. + * + * LLRPHeader.h + * The E1.33 LLRP Header + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPHEADER_H_ +#define LIBS_ACN_LLRPHEADER_H_ + +#include +#include + +#include + +namespace ola { +namespace acn { + +// TODO(Peter): I think technically this probably shouldn't be a header and +// instead is just data at this level! +/* + * Header for the LLRP layer + */ +class LLRPHeader { + public: + LLRPHeader() + : m_transaction_number(0) { + } + + LLRPHeader(const ola::acn::CID &destination_cid, + uint32_t transaction_number) + : m_destination_cid(destination_cid), + m_transaction_number(transaction_number) { + } + ~LLRPHeader() {} + + const ola::acn::CID DestinationCid() const { return m_destination_cid; } + uint32_t TransactionNumber() const { return m_transaction_number; } + + bool operator==(const LLRPHeader &other) const { + return m_destination_cid == other.m_destination_cid && + m_transaction_number == other.m_transaction_number; + } + + PACK( + struct llrp_pdu_header_s { + uint8_t destination_cid[CID::CID_LENGTH]; + uint32_t transaction_number; + }); + typedef struct llrp_pdu_header_s llrp_pdu_header; + + private: + ola::acn::CID m_destination_cid; + uint32_t m_transaction_number; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPHEADER_H_ diff --git a/libs/acn/LLRPInflator.cpp b/libs/acn/LLRPInflator.cpp new file mode 100644 index 0000000000..e629df8d50 --- /dev/null +++ b/libs/acn/LLRPInflator.cpp @@ -0,0 +1,70 @@ +/* + * 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. + * + * LLRPInflator.cpp + * The Inflator for E1.33 LLRP + * Copyright (C) 2020 Peter Newman + */ + +#include "ola/Logging.h" +#include "ola/network/NetworkUtils.h" +#include "libs/acn/LLRPInflator.h" + +namespace ola { +namespace acn { + +using ola::network::NetworkToHost; + +/* + * Decode the E1.33 LLRP headers. If data is null we're expected to use the + * last header we got. + * @param headers the HeaderSet to add to + * @param data a pointer to the data + * @param length length of the data + * @returns true if successful, false otherwise + */ +bool LLRPInflator::DecodeHeader(HeaderSet *headers, + const uint8_t *data, + unsigned int length, + unsigned int *bytes_used) { + if (data) { + // the header bit was set, decode it + if (length >= sizeof(LLRPHeader::llrp_pdu_header)) { + LLRPHeader::llrp_pdu_header raw_header; + memcpy(&raw_header, data, sizeof(LLRPHeader::llrp_pdu_header)); + LLRPHeader header( + ola::acn::CID::FromData(raw_header.destination_cid), + NetworkToHost(raw_header.transaction_number)); + m_last_header = header; + m_last_header_valid = true; + headers->SetLLRPHeader(header); + *bytes_used = sizeof(LLRPHeader::llrp_pdu_header); + return true; + } + *bytes_used = 0; + return false; + } + + // use the last header if it exists + *bytes_used = 0; + if (!m_last_header_valid) { + OLA_WARN << "Missing E1.33 LLRP Header data"; + return false; + } + headers->SetLLRPHeader(m_last_header); + return true; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPInflator.h b/libs/acn/LLRPInflator.h new file mode 100644 index 0000000000..34a5aa023d --- /dev/null +++ b/libs/acn/LLRPInflator.h @@ -0,0 +1,58 @@ +/* + * 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. + * + * LLRPInflator.h + * Interface for the LLRPInflator class. + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPINFLATOR_H_ +#define LIBS_ACN_LLRPINFLATOR_H_ + +#include "ola/acn/ACNVectors.h" +#include "libs/acn/BaseInflator.h" +#include "libs/acn/LLRPHeader.h" + +namespace ola { +namespace acn { + +class LLRPInflator: public BaseInflator { + friend class LLRPInflatorTest; + + public: + LLRPInflator() + : BaseInflator(), + m_last_header_valid(false) { + } + ~LLRPInflator() {} + + uint32_t Id() const { return ola::acn::VECTOR_ROOT_LLRP; } + + protected: + bool DecodeHeader(HeaderSet *headers, + const uint8_t *data, + unsigned int len, + unsigned int *bytes_used); + + void ResetHeaderField() { + m_last_header_valid = false; + } + private: + LLRPHeader m_last_header; + bool m_last_header_valid; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPINFLATOR_H_ diff --git a/libs/acn/LLRPInflatorTest.cpp b/libs/acn/LLRPInflatorTest.cpp new file mode 100644 index 0000000000..16bcd96431 --- /dev/null +++ b/libs/acn/LLRPInflatorTest.cpp @@ -0,0 +1,126 @@ +/* + * 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. + * + * LLRPInflatorTest.cpp + * Test fixture for the LLRPInflator class + * Copyright (C) 2020 Peter Newman + */ + +#include + +#include "ola/Logging.h" +#include "ola/network/NetworkUtils.h" +#include "libs/acn/HeaderSet.h" +#include "libs/acn/PDUTestCommon.h" +#include "libs/acn/LLRPInflator.h" +#include "libs/acn/LLRPPDU.h" +#include "ola/testing/TestUtils.h" + + +namespace ola { +namespace acn { + +using ola::network::HostToNetwork; + +class LLRPInflatorTest: public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LLRPInflatorTest); + CPPUNIT_TEST(testDecodeHeader); + CPPUNIT_TEST(testInflatePDU); + CPPUNIT_TEST_SUITE_END(); + + public: + void testDecodeHeader(); + void testInflatePDU(); + private: + static const uint8_t TEST_DATA[]; + static const uint8_t TEST_DATA2[]; +}; + +const uint8_t LLRPInflatorTest::TEST_DATA[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15}; +const uint8_t LLRPInflatorTest::TEST_DATA2[] = {10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, + 25}; +CPPUNIT_TEST_SUITE_REGISTRATION(LLRPInflatorTest); + + +/* + * Check that we can decode headers properly + */ +void LLRPInflatorTest::testDecodeHeader() { + LLRPHeader::llrp_pdu_header header; + memset(&header, 0, sizeof(header)); + LLRPInflator inflator; + HeaderSet header_set, header_set2; + unsigned int bytes_used; + const ola::acn::CID destination_cid = CID::FromData(TEST_DATA); + + destination_cid.Pack(header.destination_cid); + header.transaction_number = HostToNetwork(72650u); + + OLA_ASSERT(inflator.DecodeHeader(&header_set, + reinterpret_cast(&header), + sizeof(header), + &bytes_used)); + OLA_ASSERT_EQ((unsigned int) sizeof(header), bytes_used); + LLRPHeader decoded_header = header_set.GetLLRPHeader(); + OLA_ASSERT(destination_cid == decoded_header.DestinationCid()); + OLA_ASSERT_EQ((uint32_t) 72650, decoded_header.TransactionNumber()); + + // try an undersized header + OLA_ASSERT_FALSE(inflator.DecodeHeader( + &header_set, + reinterpret_cast(&header), + static_cast(sizeof(header) - 1), + &bytes_used)); + OLA_ASSERT_EQ((unsigned int) 0, bytes_used); + + // test inheriting the header from the prev call + OLA_ASSERT(inflator.DecodeHeader(&header_set2, NULL, 0, &bytes_used)); + OLA_ASSERT_EQ((unsigned int) 0, bytes_used); + decoded_header = header_set2.GetLLRPHeader(); + OLA_ASSERT(destination_cid == decoded_header.DestinationCid()); + OLA_ASSERT_EQ((uint32_t) 72650, decoded_header.TransactionNumber()); + + inflator.ResetHeaderField(); + OLA_ASSERT_FALSE(inflator.DecodeHeader(&header_set2, NULL, 0, &bytes_used)); + OLA_ASSERT_EQ((unsigned int) 0, bytes_used); +} + + +/* + * Check that we can inflate a LLRP PDU that contains other PDUs + */ +void LLRPInflatorTest::testInflatePDU() { + const ola::acn::CID destination_cid = CID::FromData(TEST_DATA2); + LLRPHeader header(destination_cid, 2370); + // TODO(Peter): pass a different type of msg here as well + LLRPPDU pdu(3, header, NULL); + OLA_ASSERT_EQ((unsigned int) 27, pdu.Size()); + + unsigned int size = pdu.Size(); + uint8_t *data = new uint8_t[size]; + unsigned int bytes_used = size; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ((unsigned int) size, bytes_used); + + LLRPInflator inflator; + HeaderSet header_set; + OLA_ASSERT(inflator.InflatePDUBlock(&header_set, data, size)); + OLA_ASSERT(header == header_set.GetLLRPHeader()); + delete[] data; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPPDU.cpp b/libs/acn/LLRPPDU.cpp new file mode 100644 index 0000000000..4fb3b3644f --- /dev/null +++ b/libs/acn/LLRPPDU.cpp @@ -0,0 +1,118 @@ +/* + * 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. + * + * LLRPPDU.cpp + * The LLRPPDU + * Copyright (C) 2020 Peter Newman + */ + + +#include "ola/Logging.h" +#include "ola/base/Array.h" +#include "ola/network/NetworkUtils.h" +#include "libs/acn/LLRPPDU.h" + +namespace ola { +namespace acn { + +using ola::io::OutputStream; +using ola::network::HostToNetwork; + +/* + * Size of the header portion. + */ +unsigned int LLRPPDU::HeaderSize() const { + return sizeof(LLRPHeader::llrp_pdu_header); +} + + +/* + * Size of the data portion + */ +unsigned int LLRPPDU::DataSize() const { + return m_pdu ? m_pdu->Size() : 0; +} + + +/* + * Pack the header portion. + */ +bool LLRPPDU::PackHeader(uint8_t *data, unsigned int *length) const { + unsigned int header_size = HeaderSize(); + + if (*length < header_size) { + OLA_WARN << "LLRPPDU::PackHeader: buffer too small, got " << *length + << " required " << header_size; + *length = 0; + return false; + } + + LLRPHeader::llrp_pdu_header header; + m_header.DestinationCid().Pack(header.destination_cid); + header.transaction_number = HostToNetwork(m_header.TransactionNumber()); + *length = sizeof(LLRPHeader::llrp_pdu_header); + memcpy(data, &header, *length); + return true; +} + + +/* + * Pack the data portion. + */ +bool LLRPPDU::PackData(uint8_t *data, unsigned int *length) const { + if (m_pdu) + return m_pdu->Pack(data, length); + *length = 0; + return true; +} + + +/* + * Pack the header into a buffer. + */ +void LLRPPDU::PackHeader(OutputStream *stream) const { + LLRPHeader::llrp_pdu_header header; + m_header.DestinationCid().Pack(header.destination_cid); + header.transaction_number = HostToNetwork(m_header.TransactionNumber()); + stream->Write(reinterpret_cast(&header), + sizeof(LLRPHeader::llrp_pdu_header)); +} + + +/* + * Pack the data into a buffer + */ +void LLRPPDU::PackData(OutputStream *stream) const { + if (m_pdu) + m_pdu->Write(stream); +} + + +void LLRPPDU::PrependPDU(ola::io::IOStack *stack, + uint32_t vector, + const ola::acn::CID &destination_cid, + uint32_t transaction_number) { + LLRPHeader::llrp_pdu_header header; + destination_cid.Pack(header.destination_cid); + header.transaction_number = HostToNetwork(transaction_number); + stack->Write(reinterpret_cast(&header), + sizeof(LLRPHeader::llrp_pdu_header)); + + vector = HostToNetwork(vector); + stack->Write(reinterpret_cast(&vector), sizeof(vector)); + PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true); +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPPDU.h b/libs/acn/LLRPPDU.h new file mode 100644 index 0000000000..669f5b5c06 --- /dev/null +++ b/libs/acn/LLRPPDU.h @@ -0,0 +1,62 @@ +/* + * 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. + * + * LLRPPDU.h + * Interface for the LLRPPDU class + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPPDU_H_ +#define LIBS_ACN_LLRPPDU_H_ + +#include +#include + +#include "libs/acn/PDU.h" +#include "libs/acn/LLRPHeader.h" + +namespace ola { +namespace acn { + +class LLRPPDU: public PDU { + public: + LLRPPDU(unsigned int vector, + const LLRPHeader &header, + const PDU *pdu): + PDU(vector, FOUR_BYTES, true), + m_header(header), + m_pdu(pdu) {} + ~LLRPPDU() {} + + unsigned int HeaderSize() const; + unsigned int DataSize() const; + bool PackHeader(uint8_t *data, unsigned int *length) const; + bool PackData(uint8_t *data, unsigned int *length) const; + + void PackHeader(ola::io::OutputStream *stream) const; + void PackData(ola::io::OutputStream *stream) const; + + static void PrependPDU(ola::io::IOStack *stack, + uint32_t vector, + const ola::acn::CID &destination_cid, + uint32_t transaction_number); + + private: + LLRPHeader m_header; + const PDU *m_pdu; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPPDU_H_ diff --git a/libs/acn/LLRPPDUTest.cpp b/libs/acn/LLRPPDUTest.cpp new file mode 100644 index 0000000000..01c30db22a --- /dev/null +++ b/libs/acn/LLRPPDUTest.cpp @@ -0,0 +1,147 @@ +/* + * 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. + * + * LLRPPDUTest.cpp + * Test fixture for the LLRPPDU class + * Copyright (C) 2020 Peter Newman + */ + +#include + +#include "ola/Logging.h" +#include "ola/io/IOQueue.h" +#include "ola/io/OutputStream.h" +#include "ola/network/NetworkUtils.h" +#include "ola/testing/TestUtils.h" +#include "libs/acn/LLRPPDU.h" +#include "libs/acn/PDUTestCommon.h" + + +namespace ola { +namespace acn { + +using ola::io::IOQueue; +using ola::io::OutputStream; +using ola::network::HostToNetwork; + +class LLRPPDUTest: public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LLRPPDUTest); + CPPUNIT_TEST(testSimpleLLRPPDU); + CPPUNIT_TEST(testSimpleLLRPPDUToOutputStream); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSimpleLLRPPDU(); + void testSimpleLLRPPDUToOutputStream(); + + void setUp() { + ola::InitLogging(ola::OLA_LOG_DEBUG, ola::OLA_LOG_STDERR); + } + + private: + static const unsigned int TEST_VECTOR; + static const uint8_t TEST_DATA[]; +}; + +const uint8_t LLRPPDUTest::TEST_DATA[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LLRPPDUTest); + +const unsigned int LLRPPDUTest::TEST_VECTOR = 39; + + +/* + * Test that packing a LLRPPDU without data works. + */ +void LLRPPDUTest::testSimpleLLRPPDU() { + const CID destination_cid = CID::FromData(TEST_DATA); + LLRPHeader header(destination_cid, 101); + LLRPPDU pdu(TEST_VECTOR, header, NULL); + + OLA_ASSERT_EQ(20u, pdu.HeaderSize()); + OLA_ASSERT_EQ(0u, pdu.DataSize()); + OLA_ASSERT_EQ(27u, pdu.Size()); + + unsigned int size = pdu.Size(); + uint8_t *data = new uint8_t[size]; + unsigned int bytes_used = size; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + + // spot check the data + OLA_ASSERT_EQ((uint8_t) 0xf0, data[0]); + // bytes_used is technically data[1] and data[2] if > 255 + OLA_ASSERT_EQ((uint8_t) bytes_used, data[2]); + unsigned int actual_value; + memcpy(&actual_value, data + 3, sizeof(actual_value)); + OLA_ASSERT_EQ(HostToNetwork(TEST_VECTOR), actual_value); + + uint8_t buffer[CID::CID_LENGTH]; + destination_cid.Pack(buffer); + OLA_ASSERT_DATA_EQUALS(&data[7], CID::CID_LENGTH, buffer, sizeof(buffer)); + // transaction number + OLA_ASSERT_EQ((uint8_t) 0, data[7 + CID::CID_LENGTH]); + OLA_ASSERT_EQ((uint8_t) 0, data[7 + CID::CID_LENGTH + 1]); + OLA_ASSERT_EQ((uint8_t) 0, data[7 + CID::CID_LENGTH + 2]); + OLA_ASSERT_EQ((uint8_t) 101, data[7 + CID::CID_LENGTH + 3]); + + // test undersized buffer + bytes_used = size - 1; + OLA_ASSERT_FALSE(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(0u, bytes_used); + + // test oversized buffer + bytes_used = size + 1; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + delete[] data; +} + + +/* + * Test that writing to an output stream works. + */ +void LLRPPDUTest::testSimpleLLRPPDUToOutputStream() { + const ola::acn::CID destination_cid = CID::FromData(TEST_DATA); + LLRPHeader header(destination_cid, 101); + LLRPPDU pdu(TEST_VECTOR, header, NULL); + + OLA_ASSERT_EQ(20u, pdu.HeaderSize()); + OLA_ASSERT_EQ(0u, pdu.DataSize()); + OLA_ASSERT_EQ(27u, pdu.Size()); + + IOQueue output; + OutputStream stream(&output); + pdu.Write(&stream); + OLA_ASSERT_EQ(27u, output.Size()); + + uint8_t *pdu_data = new uint8_t[output.Size()]; + unsigned int pdu_size = output.Peek(pdu_data, output.Size()); + OLA_ASSERT_EQ(output.Size(), pdu_size); + + uint8_t EXPECTED[] = { + 0xf0, 0x00, 0x1b, + 0, 0, 0, 39, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 0, 0, 0, 101, // transaction number + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size); + output.Pop(output.Size()); + delete[] pdu_data; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeReplyInflator.cpp b/libs/acn/LLRPProbeReplyInflator.cpp new file mode 100644 index 0000000000..6da01971cf --- /dev/null +++ b/libs/acn/LLRPProbeReplyInflator.cpp @@ -0,0 +1,107 @@ +/* + * 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. + * + * LLRPProbeReplyInflator.cpp + * The Inflator for the LLRP Probe Reply PDUs + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include "ola/Logging.h" +#include "include/ola/rdm/UID.h" +#include "include/ola/rdm/UIDSet.h" +#include "include/ola/strings/Format.h" +#include "libs/acn/LLRPProbeReplyInflator.h" +#include "libs/acn/LLRPProbeReplyPDU.h" + +namespace ola { +namespace acn { + +using ola::acn::LLRPProbeReplyPDU; +using ola::rdm::UID; +using ola::rdm::UIDSet; + +/** + * Create a new LLRP Probe Reply inflator + */ +LLRPProbeReplyInflator::LLRPProbeReplyInflator() + : BaseInflator(PDU::ONE_BYTE) { +} + +/** + * Set an LLRPProbeReplyHandler to run when receiving an LLRP Probe Reply + * message. + * @param handler the callback to invoke when there is an LLRP Probe Reply. + */ +void LLRPProbeReplyInflator::SetLLRPProbeReplyHandler( + LLRPProbeReplyHandler *handler) { + m_llrp_probe_reply_handler.reset(handler); +} + + +/* + * Decode the LLRP Probe Reply 'header', which is 0 bytes in length. + * @param headers the HeaderSet to add to + * @param data a pointer to the data + * @param length length of the data + * @returns true if successful, false otherwise + */ +bool LLRPProbeReplyInflator::DecodeHeader(HeaderSet *, + const uint8_t*, + unsigned int, + unsigned int *bytes_used) { + *bytes_used = 0; + return true; +} + + +/* + * Handle a LLRP Probe Reply PDU for E1.33. + */ +bool LLRPProbeReplyInflator::HandlePDUData(uint32_t vector, + const HeaderSet &headers, + const uint8_t *data, + unsigned int pdu_len) { + if (vector != LLRPProbeReplyPDU::VECTOR_PROBE_REPLY_DATA) { + OLA_INFO << "Not a probe reply, vector was " << vector; + return true; + } + + ola::strings::FormatData(&std::cout, data, pdu_len); + + LLRPProbeReplyPDU::llrp_probe_reply_pdu_data pdu_data; + if (pdu_len > sizeof(pdu_data)) { + OLA_WARN << "Got too much data, received " << pdu_len << " only expecting " + << sizeof(pdu_data); + return false; + } + + memcpy(reinterpret_cast(&pdu_data), data, sizeof(pdu_data)); + + OLA_DEBUG << "Probe from " << UID(pdu_data.target_uid); + + LLRPProbeReply reply(UID(pdu_data.target_uid)); + + if (m_llrp_probe_reply_handler.get()) { + m_llrp_probe_reply_handler->Run(&headers, + reply); + } else { + OLA_WARN << "No LLRP Probe Reply handler defined!"; + } + return true; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeReplyInflator.h b/libs/acn/LLRPProbeReplyInflator.h new file mode 100644 index 0000000000..3aab45f7b1 --- /dev/null +++ b/libs/acn/LLRPProbeReplyInflator.h @@ -0,0 +1,79 @@ +/* + * 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. + * + * LLRPProbeReplyInflator.h + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPPROBEREPLYINFLATOR_H_ +#define LIBS_ACN_LLRPPROBEREPLYINFLATOR_H_ + +#include "ola/Callback.h" +#include "ola/acn/ACNVectors.h" +#include "ola/rdm/UID.h" +#include "ola/rdm/UIDSet.h" +#include "libs/acn/BaseInflator.h" +#include "libs/acn/HeaderSet.h" +#include "libs/acn/LLRPProbeReplyPDU.h" + +namespace ola { +namespace acn { + +class LLRPProbeReplyInflator: public BaseInflator { + friend class LLRPProbeReplyInflatorTest; + + public: + struct LLRPProbeReply { + LLRPProbeReply(const ola::rdm::UID &_uid) + : uid(_uid) { + } + ola::rdm::UID uid; + ola::network::MACAddress hardware_address; + ola::acn::LLRPProbeReplyPDU::LLRPComponentType component_type; + }; + + + // These are pointers so the callers don't have to pull in all the headers. + typedef ola::Callback2 LLRPProbeReplyHandler; + + LLRPProbeReplyInflator(); + ~LLRPProbeReplyInflator() {} + + uint32_t Id() const { return ola::acn::VECTOR_LLRP_PROBE_REPLY; } + + void SetLLRPProbeReplyHandler(LLRPProbeReplyHandler *handler); + + protected: + bool DecodeHeader(HeaderSet *headers, + const uint8_t *data, + unsigned int len, + unsigned int *bytes_used); + + void ResetHeaderField() {} // namespace noop + + virtual bool HandlePDUData(uint32_t vector, + const HeaderSet &headers, + const uint8_t *data, + unsigned int pdu_len); + + private: + std::auto_ptr m_llrp_probe_reply_handler; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPPROBEREPLYINFLATOR_H_ diff --git a/libs/acn/LLRPProbeReplyPDU.cpp b/libs/acn/LLRPProbeReplyPDU.cpp new file mode 100644 index 0000000000..a7822e7dc9 --- /dev/null +++ b/libs/acn/LLRPProbeReplyPDU.cpp @@ -0,0 +1,70 @@ +/* + * 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. + * + * LLRPProbeReplyPDU.cpp + * The LLRPProbeReplyPDU + * Copyright (C) 2020 Peter Newman + */ + +#include "libs/acn/LLRPProbeReplyPDU.h" + +#include +#include +#include + +namespace ola { +namespace acn { + +using ola::io::OutputStream; +using ola::network::HostToNetwork; +using ola::network::MACAddress; +using ola::rdm::UID; + +bool LLRPProbeReplyPDU::PackData(uint8_t *data, unsigned int *length) const { + llrp_probe_reply_pdu_data pdu_data; + m_target_uid.Pack(pdu_data.target_uid, sizeof(pdu_data.target_uid)); + m_hardware_address.Pack(pdu_data.hardware_address, sizeof(pdu_data.hardware_address)); + pdu_data.type = HostToNetwork(static_cast(m_type)); + + *length = sizeof(llrp_probe_reply_pdu_data); + memcpy(data, &pdu_data, *length); + return true; +} + +void LLRPProbeReplyPDU::PackData(ola::io::OutputStream *stream) const { + llrp_probe_reply_pdu_data data; + m_target_uid.Pack(data.target_uid, sizeof(data.target_uid)); + m_hardware_address.Pack(data.hardware_address, sizeof(data.hardware_address)); + data.type = HostToNetwork(static_cast(m_type)); + stream->Write(reinterpret_cast(&data), + static_cast(sizeof(llrp_probe_reply_pdu_data))); +} + +void LLRPProbeReplyPDU::PrependPDU(ola::io::IOStack *stack, + const UID &target_uid, + const MACAddress &hardware_address, + const LLRPComponentType type) { + llrp_probe_reply_pdu_data data; + target_uid.Pack(data.target_uid, sizeof(data.target_uid)); + hardware_address.Pack(data.hardware_address, sizeof(data.hardware_address)); + data.type = HostToNetwork(static_cast(type)); + stack->Write(reinterpret_cast(&data), + static_cast(sizeof(llrp_probe_reply_pdu_data))); + uint8_t vector = HostToNetwork(VECTOR_PROBE_REPLY_DATA); + stack->Write(reinterpret_cast(&vector), sizeof(vector)); + PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true); +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeReplyPDU.h b/libs/acn/LLRPProbeReplyPDU.h new file mode 100644 index 0000000000..96b0644151 --- /dev/null +++ b/libs/acn/LLRPProbeReplyPDU.h @@ -0,0 +1,85 @@ +/* + * 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. + * + * LLRPProbeReplyPDU.h + * The LLRPProbeReplyPDU class + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPPROBEREPLYPDU_H_ +#define LIBS_ACN_LLRPPROBEREPLYPDU_H_ + +#include +#include +#include + +#include "libs/acn/PDU.h" + +namespace ola { +namespace acn { + +class LLRPProbeReplyPDU : public PDU { + public: + typedef enum { + LLRP_COMPONENT_TYPE_RPT_DEVICE = 0, /**< Device */ + LLRP_COMPONENT_TYPE_RPT_CONTROLLER = 1, /**< Controller */ + LLRP_COMPONENT_TYPE_BROKER = 2, /**< Broker */ + LLRP_COMPONENT_TYPE_NON_RDMNET = 0xff, /**< Non-RDMnet */ + } LLRPComponentType; + + explicit LLRPProbeReplyPDU(unsigned int vector, + const ola::rdm::UID &target_uid, + const ola::network::MACAddress &hardware_address, + const LLRPComponentType type): + PDU(vector, ONE_BYTE, true), + m_target_uid(target_uid), + m_hardware_address(hardware_address), + m_type(type) {} + + unsigned int HeaderSize() const { return 0; } + bool PackHeader(OLA_UNUSED uint8_t *data, + unsigned int *length) const { + *length = 0; + return true; + } + void PackHeader(OLA_UNUSED ola::io::OutputStream *stream) const {} + + unsigned int DataSize() const { return sizeof(llrp_probe_reply_pdu_data); } + bool PackData(uint8_t *data, unsigned int *length) const; + void PackData(ola::io::OutputStream *stream) const; + + static void PrependPDU(ola::io::IOStack *stack, + const ola::rdm::UID &target_uid, + const ola::network::MACAddress &hardware_address, + const LLRPComponentType type); + + static const uint8_t VECTOR_PROBE_REPLY_DATA = 0x01; + + PACK( + struct llrp_probe_reply_pdu_data_s { + uint8_t target_uid[ola::rdm::UID::LENGTH]; + uint8_t hardware_address[ola::network::MACAddress::LENGTH]; + uint8_t type; + }); + typedef struct llrp_probe_reply_pdu_data_s llrp_probe_reply_pdu_data; + + private: + const ola::rdm::UID m_target_uid; + const ola::network::MACAddress m_hardware_address; + const LLRPComponentType m_type; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPPROBEREPLYPDU_H_ diff --git a/libs/acn/LLRPProbeReplyPDUTest.cpp b/libs/acn/LLRPProbeReplyPDUTest.cpp new file mode 100644 index 0000000000..ae25918683 --- /dev/null +++ b/libs/acn/LLRPProbeReplyPDUTest.cpp @@ -0,0 +1,181 @@ +/* + * 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. + * + * LLRPProbeReplyPDUTest.cpp + * Test fixture for the LLRPProbeReplyPDU class + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include + +#include "ola/Logging.h" +#include "ola/io/IOQueue.h" +#include "ola/io/IOStack.h" +#include "ola/io/OutputStream.h" +#include "ola/network/NetworkUtils.h" +#include "ola/rdm/UID.h" +#include "ola/rdm/UIDSet.h" +#include "ola/testing/TestUtils.h" +#include "libs/acn/PDUTestCommon.h" +#include "libs/acn/LLRPProbeReplyPDU.h" + +namespace ola { +namespace acn { + +using ola::acn::LLRPProbeReplyPDU; +using ola::io::IOQueue; +using ola::io::IOStack; +using ola::io::OutputStream; +using ola::network::HostToNetwork; +using ola::network::MACAddress; +using ola::rdm::UID; + +class LLRPProbeReplyPDUTest: public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LLRPProbeReplyPDUTest); + CPPUNIT_TEST(testSimpleLLRPProbeReplyPDU); + CPPUNIT_TEST(testSimpleLLRPProbeReplyPDUToOutputStream); + CPPUNIT_TEST(testPrepend); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSimpleLLRPProbeReplyPDU(); + void testSimpleLLRPProbeReplyPDUToOutputStream(); + void testPrepend(); + + private: + static const unsigned int TEST_VECTOR; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LLRPProbeReplyPDUTest); + +const unsigned int LLRPProbeReplyPDUTest::TEST_VECTOR = 39; + + +/* + * Test that packing a LLRPProbeReplyPDU works. + */ +void LLRPProbeReplyPDUTest::testSimpleLLRPProbeReplyPDU() { + UID target_uid = UID(0x4321, 0x12345678); + MACAddress hardware_address; + MACAddress::FromString("01:23:45:67:89:ab", &hardware_address); + LLRPProbeReplyPDU pdu( + TEST_VECTOR, + target_uid, + hardware_address, + LLRPProbeReplyPDU::LLRP_COMPONENT_TYPE_NON_RDMNET); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(13u, pdu.DataSize()); + OLA_ASSERT_EQ(17u, pdu.Size()); + + unsigned int size = pdu.Size(); + uint8_t *data = new uint8_t[size]; + unsigned int bytes_used = size; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + + // spot check the data + OLA_ASSERT_EQ((uint8_t) 0xf0, data[0]); + // bytes_used is technically data[1] and data[2] if > 255 + OLA_ASSERT_EQ((uint8_t) bytes_used, data[2]); + OLA_ASSERT_EQ(HostToNetwork((uint8_t) TEST_VECTOR), data[3]); + + uint8_t buffer[UID::LENGTH]; + target_uid.Pack(buffer, sizeof(buffer)); + OLA_ASSERT_DATA_EQUALS(&data[4], UID::LENGTH, buffer, sizeof(buffer)); + uint8_t buffer2[MACAddress::LENGTH]; + hardware_address.Pack(buffer2, sizeof(buffer2)); + OLA_ASSERT_DATA_EQUALS(&data[10], MACAddress::LENGTH, buffer2, sizeof(buffer2)); + + // test undersized buffer + bytes_used = size - 1; + OLA_ASSERT_FALSE(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(0u, bytes_used); + + // test oversized buffer + bytes_used = size + 1; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + delete[] data; +} + + +/* + * Test that writing to an output stream works. + */ +void LLRPProbeReplyPDUTest::testSimpleLLRPProbeReplyPDUToOutputStream() { + UID target_uid = UID(0x4321, 0x12345678); + MACAddress hardware_address; + MACAddress::FromString("01:23:45:67:89:ab", &hardware_address); + LLRPProbeReplyPDU pdu( + TEST_VECTOR, + target_uid, + hardware_address, + LLRPProbeReplyPDU::LLRP_COMPONENT_TYPE_NON_RDMNET); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(13u, pdu.DataSize()); + OLA_ASSERT_EQ(17u, pdu.Size()); + + IOQueue output; + OutputStream stream(&output); + pdu.Write(&stream); + OLA_ASSERT_EQ(17u, output.Size()); + + uint8_t *pdu_data = new uint8_t[output.Size()]; + unsigned int pdu_size = output.Peek(pdu_data, output.Size()); + OLA_ASSERT_EQ(output.Size(), pdu_size); + + uint8_t EXPECTED[] = { + 0xf0, 0x00, 0x11, + 39, + 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0xff + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size); + output.Pop(output.Size()); + delete[] pdu_data; +} + + +void LLRPProbeReplyPDUTest::testPrepend() { + IOStack stack; + UID target_uid = UID(0x4321, 0x12345678); + MACAddress hardware_address; + MACAddress::FromString("01:23:45:67:89:ab", &hardware_address); + LLRPProbeReplyPDU::PrependPDU( + &stack, + target_uid, + hardware_address, + LLRPProbeReplyPDU::LLRP_COMPONENT_TYPE_NON_RDMNET); + + unsigned int length = stack.Size(); + uint8_t *buffer = new uint8_t[length]; + OLA_ASSERT(stack.Read(buffer, length)); + + const uint8_t expected_data[] = { + 0xf0, 0x00, 0x11, 1, + 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0xff + }; + OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length); + delete[] buffer; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeRequestInflator.cpp b/libs/acn/LLRPProbeRequestInflator.cpp new file mode 100644 index 0000000000..adf7d63fdd --- /dev/null +++ b/libs/acn/LLRPProbeRequestInflator.cpp @@ -0,0 +1,119 @@ +/* + * 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. + * + * LLRPProbeRequestInflator.cpp + * The Inflator for the LLRP Probe Request PDUs + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include "ola/Logging.h" +#include "include/ola/rdm/UID.h" +#include "include/ola/rdm/UIDSet.h" +#include "include/ola/strings/Format.h" +#include "libs/acn/LLRPProbeRequestInflator.h" +#include "libs/acn/LLRPProbeRequestPDU.h" + +namespace ola { +namespace acn { + +using ola::acn::LLRPProbeRequestPDU; +using ola::rdm::UID; +using ola::rdm::UIDSet; + +/** + * Create a new LLRP Probe Request inflator + */ +LLRPProbeRequestInflator::LLRPProbeRequestInflator() + : BaseInflator(PDU::ONE_BYTE) { +} + +/** + * Set a RDMHandler to run when receiving a RDM message. + * @param handler the callback to invoke when there is rdm data for this + * universe. + */ +void LLRPProbeRequestInflator::SetLLRPProbeRequestHandler( + LLRPProbeRequestHandler *handler) { + m_llrp_probe_request_handler.reset(handler); +} + + +/* + * Decode the LLRP Probe Request 'header', which is 0 bytes in length. + * @param headers the HeaderSet to add to + * @param data a pointer to the data + * @param length length of the data + * @returns true if successful, false otherwise + */ +bool LLRPProbeRequestInflator::DecodeHeader(HeaderSet *, + const uint8_t*, + unsigned int, + unsigned int *bytes_used) { + *bytes_used = 0; + return true; +} + + +/* + * Handle a LLRP Probe Request PDU for E1.33. + */ +bool LLRPProbeRequestInflator::HandlePDUData(uint32_t vector, + const HeaderSet &headers, + const uint8_t *data, + unsigned int pdu_len) { + if (vector != LLRPProbeRequestPDU::VECTOR_PROBE_REQUEST_DATA) { + OLA_INFO << "Not a probe request, vector was " << vector; + return true; + } + + ola::strings::FormatData(&std::cout, data, pdu_len); + + LLRPProbeRequestPDU::llrp_probe_request_pdu_data pdu_data; + if (pdu_len > sizeof(pdu_data)) { + OLA_WARN << "Got too much data, received " << pdu_len << " only expecting " + << sizeof(pdu_data); + return false; + } + + unsigned int known_uids_size = static_cast( + pdu_len - (sizeof(pdu_data) - + sizeof(pdu_data.known_uids))); + if (known_uids_size % UID::UID_SIZE != 0) { + OLA_WARN << "Got a partial known UID, received " << known_uids_size << " bytes"; + return false; + } + + memcpy(reinterpret_cast(&pdu_data), data, sizeof(pdu_data)); + + OLA_DEBUG << "Probe from " << UID(pdu_data.lower_uid) << " to " << UID(pdu_data.upper_uid); + + LLRPProbeRequest request(UID(pdu_data.lower_uid), UID(pdu_data.upper_uid)); + request.client_tcp_connection_inactive = (pdu_data.filter & LLRPProbeRequestPDU::FILTER_CLIENT_TCP_CONNECTION_INACTIVE); + request.brokers_only = (pdu_data.filter & LLRPProbeRequestPDU::FILTER_BROKERS_ONLY); + unsigned int known_uids_used_size = known_uids_size; + request.known_uids = UIDSet(pdu_data.known_uids, &known_uids_used_size); + + if (m_llrp_probe_request_handler.get()) { + m_llrp_probe_request_handler->Run(&headers, + request); + } else { + OLA_WARN << "No LLRP Probe Request handler defined!"; + } + return true; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeRequestInflator.h b/libs/acn/LLRPProbeRequestInflator.h new file mode 100644 index 0000000000..7cb4b49302 --- /dev/null +++ b/libs/acn/LLRPProbeRequestInflator.h @@ -0,0 +1,81 @@ +/* + * 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. + * + * LLRPProbeRequestInflator.h + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPPROBEREQUESTINFLATOR_H_ +#define LIBS_ACN_LLRPPROBEREQUESTINFLATOR_H_ + +#include "ola/Callback.h" +#include "ola/acn/ACNVectors.h" +#include "ola/rdm/UID.h" +#include "ola/rdm/UIDSet.h" +#include "libs/acn/BaseInflator.h" +#include "libs/acn/HeaderSet.h" + +namespace ola { +namespace acn { + +class LLRPProbeRequestInflator: public BaseInflator { + friend class LLRPProbeRequestInflatorTest; + + public: + struct LLRPProbeRequest { + LLRPProbeRequest(const ola::rdm::UID &_lower, const ola::rdm::UID &_upper) + : lower(_lower), + upper(_upper) { + } + ola::rdm::UID lower; + ola::rdm::UID upper; + bool client_tcp_connection_inactive; + bool brokers_only; + ola::rdm::UIDSet known_uids; + }; + + + // These are pointers so the callers don't have to pull in all the headers. + typedef ola::Callback2 LLRPProbeRequestHandler; + + LLRPProbeRequestInflator(); + ~LLRPProbeRequestInflator() {} + + uint32_t Id() const { return ola::acn::VECTOR_LLRP_PROBE_REQUEST; } + + void SetLLRPProbeRequestHandler(LLRPProbeRequestHandler *handler); + + protected: + bool DecodeHeader(HeaderSet *headers, + const uint8_t *data, + unsigned int len, + unsigned int *bytes_used); + + void ResetHeaderField() {} // namespace noop + + virtual bool HandlePDUData(uint32_t vector, + const HeaderSet &headers, + const uint8_t *data, + unsigned int pdu_len); + + private: + std::auto_ptr m_llrp_probe_request_handler; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPPROBEREQUESTINFLATOR_H_ diff --git a/libs/acn/LLRPProbeRequestPDU.cpp b/libs/acn/LLRPProbeRequestPDU.cpp new file mode 100644 index 0000000000..ee8a021ec7 --- /dev/null +++ b/libs/acn/LLRPProbeRequestPDU.cpp @@ -0,0 +1,112 @@ +/* + * 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. + * + * LLRPProbeRequestPDU.cpp + * The LLRPProbeRequestPDU + * Copyright (C) 2020 Peter Newman + */ + +#include "libs/acn/LLRPProbeRequestPDU.h" + +#include +#include +#include + +namespace ola { +namespace acn { + +using ola::io::OutputStream; +using ola::network::HostToNetwork; +using ola::rdm::UID; + +unsigned int LLRPProbeRequestPDU::DataSize() const { + llrp_probe_request_pdu_data data; + return static_cast(sizeof(llrp_probe_request_pdu_data) - + sizeof(data.known_uids) + + (m_known_uids.Size() * UID::LENGTH)); + +} + +bool LLRPProbeRequestPDU::PackData(uint8_t *data, unsigned int *length) const { + llrp_probe_request_pdu_data pdu_data; + m_lower_uid.Pack(pdu_data.lower_uid, sizeof(pdu_data.lower_uid)); + m_upper_uid.Pack(pdu_data.upper_uid, sizeof(pdu_data.upper_uid)); + uint16_t filter = 0; + if (m_client_tcp_connection_inactive) { + filter |= FILTER_CLIENT_TCP_CONNECTION_INACTIVE; + } + if (m_brokers_only) { + filter |= FILTER_BROKERS_ONLY; + } + pdu_data.filter = HostToNetwork(filter); + // TODO(Peter): We need to check we've got <= 200 UIDs here + m_known_uids.Pack(pdu_data.known_uids, sizeof(pdu_data.known_uids)); + *length = static_cast(sizeof(llrp_probe_request_pdu_data) - + sizeof(pdu_data.known_uids) + + (m_known_uids.Size() * UID::LENGTH)); + + memcpy(data, &pdu_data, *length); + return true; +} + +void LLRPProbeRequestPDU::PackData(ola::io::OutputStream *stream) const { + llrp_probe_request_pdu_data data; + m_lower_uid.Pack(data.lower_uid, sizeof(data.lower_uid)); + m_upper_uid.Pack(data.upper_uid, sizeof(data.upper_uid)); + uint16_t filter = 0; + if (m_client_tcp_connection_inactive) { + filter |= FILTER_CLIENT_TCP_CONNECTION_INACTIVE; + } + if (m_brokers_only) { + filter |= FILTER_BROKERS_ONLY; + } + data.filter = HostToNetwork(filter); + // TODO(Peter): We need to check we've got <= 200 UIDs here + m_known_uids.Pack(data.known_uids, sizeof(data.known_uids)); + stream->Write(reinterpret_cast(&data), + static_cast(sizeof(llrp_probe_request_pdu_data) - + sizeof(data.known_uids) + + (m_known_uids.Size() * UID::LENGTH))); +} + +void LLRPProbeRequestPDU::PrependPDU(ola::io::IOStack *stack, + const UID &lower_uid, + const UID &upper_uid, + bool client_tcp_connection_inactive, + bool brokers_only, + const ola::rdm::UIDSet &known_uids) { + llrp_probe_request_pdu_data data; + lower_uid.Pack(data.lower_uid, sizeof(data.lower_uid)); + upper_uid.Pack(data.upper_uid, sizeof(data.upper_uid)); + uint16_t filter = 0; + if (client_tcp_connection_inactive) { + filter |= FILTER_CLIENT_TCP_CONNECTION_INACTIVE; + } + if (brokers_only) { + 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) - + sizeof(data.known_uids) + + (known_uids.Size() * UID::LENGTH))); + uint8_t vector = HostToNetwork(VECTOR_PROBE_REQUEST_DATA); + stack->Write(reinterpret_cast(&vector), sizeof(vector)); + PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true); +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/LLRPProbeRequestPDU.h b/libs/acn/LLRPProbeRequestPDU.h new file mode 100644 index 0000000000..56095c709c --- /dev/null +++ b/libs/acn/LLRPProbeRequestPDU.h @@ -0,0 +1,93 @@ +/* + * 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. + * + * LLRPProbeRequestPDU.h + * The LLRPProbeRequestPDU class + * Copyright (C) 2020 Peter Newman + */ + +#ifndef LIBS_ACN_LLRPPROBEREQUESTPDU_H_ +#define LIBS_ACN_LLRPPROBEREQUESTPDU_H_ + +#include +#include +#include + +#include "libs/acn/PDU.h" + +namespace ola { +namespace acn { + +class LLRPProbeRequestPDU : public PDU { + public: + explicit LLRPProbeRequestPDU(unsigned int vector, + const ola::rdm::UID &lower_uid, + const ola::rdm::UID &upper_uid, + bool client_tcp_connection_inactive, + bool brokers_only, + const ola::rdm::UIDSet &known_uids): + PDU(vector, ONE_BYTE, true), + m_lower_uid(lower_uid), + m_upper_uid(upper_uid), + m_client_tcp_connection_inactive(client_tcp_connection_inactive), + m_brokers_only(brokers_only), + m_known_uids(known_uids) {} + + unsigned int HeaderSize() const { return 0; } + bool PackHeader(OLA_UNUSED uint8_t *data, + unsigned int *length) const { + *length = 0; + return true; + } + void PackHeader(OLA_UNUSED ola::io::OutputStream *stream) const {} + + unsigned int DataSize() const; + bool PackData(uint8_t *data, unsigned int *length) const; + void PackData(ola::io::OutputStream *stream) const; + + static void PrependPDU(ola::io::IOStack *stack, + const ola::rdm::UID &lower_uid, + const ola::rdm::UID &upper_uid, + bool client_tcp_connection_inactive, + bool brokers_only, + const ola::rdm::UIDSet &known_uids); + + static const uint8_t VECTOR_PROBE_REQUEST_DATA = 0x01; + + static const unsigned int LLRP_KNOWN_UID_SIZE = 200; + + // bit masks for filter + static const uint16_t FILTER_CLIENT_TCP_CONNECTION_INACTIVE = 0x0001; + static const uint16_t FILTER_BROKERS_ONLY = 0x0002; + + PACK( + struct llrp_probe_request_pdu_data_s { + uint8_t lower_uid[ola::rdm::UID::LENGTH]; + uint8_t upper_uid[ola::rdm::UID::LENGTH]; + uint16_t filter; + uint8_t known_uids[ola::rdm::UID::LENGTH * LLRP_KNOWN_UID_SIZE]; + }); + typedef struct llrp_probe_request_pdu_data_s llrp_probe_request_pdu_data; + + private: + const ola::rdm::UID m_lower_uid; + const ola::rdm::UID m_upper_uid; + bool m_client_tcp_connection_inactive; + bool m_brokers_only; + const ola::rdm::UIDSet m_known_uids; +}; +} // namespace acn +} // namespace ola +#endif // LIBS_ACN_LLRPPROBEREQUESTPDU_H_ diff --git a/libs/acn/LLRPProbeRequestPDUTest.cpp b/libs/acn/LLRPProbeRequestPDUTest.cpp new file mode 100644 index 0000000000..88f39d587c --- /dev/null +++ b/libs/acn/LLRPProbeRequestPDUTest.cpp @@ -0,0 +1,197 @@ +/* + * 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. + * + * LLRPProbeRequestPDUTest.cpp + * Test fixture for the LLRPProbeRequestPDU class + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include + +#include "ola/Logging.h" +#include "ola/io/IOQueue.h" +#include "ola/io/IOStack.h" +#include "ola/io/OutputStream.h" +#include "ola/network/NetworkUtils.h" +#include "ola/rdm/UID.h" +#include "ola/rdm/UIDSet.h" +#include "ola/testing/TestUtils.h" +#include "libs/acn/PDUTestCommon.h" +#include "libs/acn/LLRPProbeRequestPDU.h" + +namespace ola { +namespace acn { + +using ola::acn::LLRPProbeRequestPDU; +using ola::io::IOQueue; +using ola::io::IOStack; +using ola::io::OutputStream; +using ola::network::HostToNetwork; +using ola::rdm::UID; +using ola::rdm::UIDSet; + +class LLRPProbeRequestPDUTest: public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LLRPProbeRequestPDUTest); + CPPUNIT_TEST(testSimpleLLRPProbeRequestPDU); + CPPUNIT_TEST(testSimpleLLRPProbeRequestPDUToOutputStream); + CPPUNIT_TEST(testPrepend); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSimpleLLRPProbeRequestPDU(); + void testSimpleLLRPProbeRequestPDUToOutputStream(); + void testPrepend(); + + private: + static const unsigned int TEST_VECTOR; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LLRPProbeRequestPDUTest); + +const unsigned int LLRPProbeRequestPDUTest::TEST_VECTOR = 39; + + +/* + * Test that packing a LLRPProbeRequestPDU works. + */ +void LLRPProbeRequestPDUTest::testSimpleLLRPProbeRequestPDU() { + UID lower_uid = UID(0x4321, 0x12345678); + UID upper_uid = UID(0x5678, 0x00abcdef); + UIDSet known_uids; + known_uids.AddUID(UID(0x1234, 0x00000001)); + known_uids.AddUID(UID(0x5678, 0x00000002)); + known_uids.AddUID(UID(0x4321, 0x56789abc)); + LLRPProbeRequestPDU pdu( + TEST_VECTOR, + lower_uid, + upper_uid, + false, + false, + known_uids); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(32u, pdu.DataSize()); + OLA_ASSERT_EQ(36u, pdu.Size()); + + unsigned int size = pdu.Size(); + uint8_t *data = new uint8_t[size]; + unsigned int bytes_used = size; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + + // spot check the data + OLA_ASSERT_EQ((uint8_t) 0xf0, data[0]); + // bytes_used is technically data[1] and data[2] if > 255 + OLA_ASSERT_EQ((uint8_t) bytes_used, data[2]); + OLA_ASSERT_EQ(HostToNetwork((uint8_t) TEST_VECTOR), data[3]); + + uint8_t buffer[UID::LENGTH]; + lower_uid.Pack(buffer, sizeof(buffer)); + OLA_ASSERT_DATA_EQUALS(&data[4], UID::LENGTH, buffer, sizeof(buffer)); + upper_uid.Pack(buffer, sizeof(buffer)); + OLA_ASSERT_DATA_EQUALS(&data[10], UID::LENGTH, buffer, sizeof(buffer)); + + // test undersized buffer + bytes_used = size - 1; + OLA_ASSERT_FALSE(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(0u, bytes_used); + + // test oversized buffer + bytes_used = size + 1; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + delete[] data; +} + +/* + * Test that writing to an output stream works. + */ +void LLRPProbeRequestPDUTest::testSimpleLLRPProbeRequestPDUToOutputStream() { + UID lower_uid = UID(0x4321, 0x12345678); + UID upper_uid = UID(0x5678, 0x00abcdef); + UIDSet known_uids; + known_uids.AddUID(UID(0x1234, 0x00000001)); + known_uids.AddUID(UID(0x5678, 0x00000002)); + known_uids.AddUID(UID(0x4321, 0x56789abc)); + LLRPProbeRequestPDU pdu( + TEST_VECTOR, + lower_uid, + upper_uid, + false, + false, + known_uids); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(32u, pdu.DataSize()); + OLA_ASSERT_EQ(36u, pdu.Size()); + + IOQueue output; + OutputStream stream(&output); + pdu.Write(&stream); + OLA_ASSERT_EQ(36u, output.Size()); + + uint8_t *pdu_data = new uint8_t[output.Size()]; + unsigned int pdu_size = output.Peek(pdu_data, output.Size()); + OLA_ASSERT_EQ(output.Size(), pdu_size); + + uint8_t EXPECTED[] = { + 0xf0, 0x00, 0x24, + 39, + 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, + 0x56, 0x78, 0x00, 0xab, 0xcd, 0xef, + 0x00, 0x00, + 0x12, 0x34, 0, 0, 0, 1, + 0x43, 0x21, 0x56, 0x78, 0x9a, 0xbc, + 0x56, 0x78, 0, 0, 0, 2 + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size); + output.Pop(output.Size()); + delete[] pdu_data; +} + + +void LLRPProbeRequestPDUTest::testPrepend() { + IOStack stack; + UIDSet known_uids; + known_uids.AddUID(UID(0x1234, 0x00000001)); + known_uids.AddUID(UID(0x5678, 0x00000002)); + known_uids.AddUID(UID(0x4321, 0x12345678)); + LLRPProbeRequestPDU::PrependPDU(&stack, + UID(0x0000, 0x00000000), + UID(0xffff, 0xffffffff), + false, + false, + known_uids); + + unsigned int length = stack.Size(); + uint8_t *buffer = new uint8_t[length]; + OLA_ASSERT(stack.Read(buffer, length)); + + const uint8_t expected_data[] = { + 0xf0, 0x00, 0x24, 1, + 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, + 0x12, 0x34, 0, 0, 0, 1, + 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, + 0x56, 0x78, 0, 0, 0, 2, + }; + OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length); + delete[] buffer; +} +} // namespace acn +} // namespace ola diff --git a/libs/acn/Makefile.mk b/libs/acn/Makefile.mk index 57bcf5276e..a036a3f55a 100644 --- a/libs/acn/Makefile.mk +++ b/libs/acn/Makefile.mk @@ -61,6 +61,19 @@ libs_acn_libolae131core_la_SOURCES = \ libs/acn/E133StatusPDU.cpp \ libs/acn/E133StatusPDU.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 \ @@ -108,6 +121,7 @@ libs_acn_e131_loadtest_LDADD = libs/acn/libolae131core.la test_programs += \ libs/acn/E131Tester \ libs/acn/E133Tester \ + libs/acn/LLRPTester \ libs/acn/TransportTester libs_acn_E131Tester_SOURCES = \ @@ -140,6 +154,16 @@ libs_acn_E133Tester_LDADD = \ libs/acn/libolae131core.la \ $(COMMON_TESTING_LIBS) +libs_acn_LLRPTester_SOURCES = \ + libs/acn/LLRPInflatorTest.cpp \ + libs/acn/LLRPPDUTest.cpp \ + libs/acn/LLRPProbeReplyPDUTest.cpp \ + libs/acn/LLRPProbeRequestPDUTest.cpp +libs_acn_LLRPTester_CPPFLAGS = $(COMMON_TESTING_FLAGS) +libs_acn_LLRPTester_LDADD = \ + libs/acn/libolae131core.la \ + $(COMMON_TESTING_LIBS) + libs_acn_TransportTester_SOURCES = \ libs/acn/TCPTransportTest.cpp \ libs/acn/UDPTransportTest.cpp diff --git a/libs/acn/PDU.cpp b/libs/acn/PDU.cpp index c9166db6cb..0f256eca13 100644 --- a/libs/acn/PDU.cpp +++ b/libs/acn/PDU.cpp @@ -35,8 +35,9 @@ using ola::network::HostToNetwork; unsigned int PDU::Size() const { unsigned int length = m_vector_size + HeaderSize() + DataSize(); - if (length > TWOB_LENGTH_LIMIT - 2) + if ((length > TWOB_LENGTH_LIMIT - 2) || m_force_length_flag) { length += 1; + } length += 2; return length; } @@ -59,7 +60,7 @@ bool PDU::Pack(uint8_t *buffer, unsigned int *length) const { return false; } - if (size <= TWOB_LENGTH_LIMIT) { + if (size <= TWOB_LENGTH_LIMIT && !m_force_length_flag) { buffer[0] = (uint8_t) ((size & 0x0f00) >> 8); buffer[1] = (uint8_t) (size & 0xff); } else { @@ -69,6 +70,11 @@ bool PDU::Pack(uint8_t *buffer, unsigned int *length) const { offset += 1; } + if (m_force_length_flag) { + // TODO(Peter): Should this happen regardless of the force when we're + // writing 20 bits of length? + buffer[0] |= LFLAG_MASK; + } buffer[0] |= VFLAG_MASK; buffer[0] |= HFLAG_MASK; buffer[0] |= DFLAG_MASK; @@ -117,13 +123,18 @@ bool PDU::Pack(uint8_t *buffer, unsigned int *length) const { void PDU::Write(OutputStream *stream) const { unsigned int size = Size(); - if (size <= TWOB_LENGTH_LIMIT) { + if (size <= TWOB_LENGTH_LIMIT && !m_force_length_flag) { uint16_t flags_and_length = static_cast(size); flags_and_length |= (VFLAG_MASK | HFLAG_MASK | DFLAG_MASK) << 8u; *stream << HostToNetwork(flags_and_length); } else { uint8_t vhl_flags = static_cast((size & 0x0f0000) >> 16); vhl_flags |= VFLAG_MASK | HFLAG_MASK | DFLAG_MASK; + if (m_force_length_flag) { + // TODO(Peter): Should this happen regardless of the force as we're + // writing 20 bits of length? + vhl_flags |= LFLAG_MASK; + } *stream << vhl_flags; *stream << (uint8_t) ((size & 0xff00) >> 8); *stream << (uint8_t) (size & 0xff); @@ -150,8 +161,9 @@ void PDU::Write(OutputStream *stream) const { * Prepend the flags and length to an OutputBufferInterface. */ void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output, - uint8_t flags) { - PrependFlagsAndLength(output, output->Size(), flags); + uint8_t flags, + bool force_length_flag) { + PrependFlagsAndLength(output, output->Size(), flags, force_length_flag); } @@ -160,8 +172,9 @@ void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output, */ void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output, unsigned int size, - uint8_t flags) { - if (size + 2 <= TWOB_LENGTH_LIMIT) { + uint8_t flags, + bool force_length_flag) { + if (size + 2 <= TWOB_LENGTH_LIMIT && !force_length_flag) { size += 2; uint16_t flags_and_length = static_cast(size); flags_and_length |= static_cast(flags << 8u); @@ -173,6 +186,11 @@ void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output, uint8_t flags_and_length[3]; flags_and_length[0] = static_cast((size & 0x0f0000) >> 16); flags_and_length[0] |= flags; + if (force_length_flag) { + // TODO(Peter): Should this happen regardless of the force as we're + // writing 20 bits of length? + flags_and_length[0] |= LFLAG_MASK; + } flags_and_length[1] = static_cast((size & 0xff00) >> 8); flags_and_length[2] = static_cast(size & 0xff); output->Write(flags_and_length, sizeof(flags_and_length)); diff --git a/libs/acn/PDU.h b/libs/acn/PDU.h index 64c1e82adb..2be285dead 100644 --- a/libs/acn/PDU.h +++ b/libs/acn/PDU.h @@ -22,6 +22,7 @@ #define LIBS_ACN_PDU_H_ #include +#include #include #include #include @@ -41,9 +42,12 @@ class PDU { FOUR_BYTES = 4, } vector_size; - explicit PDU(unsigned int vector, vector_size size = FOUR_BYTES): + explicit PDU(unsigned int vector, + vector_size size = FOUR_BYTES, + bool force_length_flag = false): m_vector(vector), - m_vector_size(size) {} + m_vector_size(size), + m_force_length_flag(force_length_flag) {} virtual ~PDU() {} // Returns the size of this PDU @@ -72,23 +76,36 @@ class PDU { static void PrependFlagsAndLength( ola::io::OutputBufferInterface *output, - uint8_t flags = VFLAG_MASK | HFLAG_MASK | DFLAG_MASK); + uint8_t flags = VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, + bool force_length_flag = false); static void PrependFlagsAndLength( ola::io::OutputBufferInterface *output, unsigned int length, - uint8_t flags); + uint8_t flags, + bool force_length_flag = false); + + /** + * @brief This indicates a vector is present. + * @deprecated Use ola::acn::VFLAG_MASK instead (4 Feb 2020). + */ + static const uint8_t VFLAG_MASK = ola::acn::VFLAG_MASK; + /** + * @brief This indicates a header field is present. + * @deprecated Use ola::acn::HFLAG_MASK instead (4 Feb 2020). + */ + static const uint8_t HFLAG_MASK = ola::acn::HFLAG_MASK; + /** + * @brief This indicates a data field is present. + * @deprecated Use ola::acn::DFLAG_MASK instead (4 Feb 2020). + */ + static const uint8_t DFLAG_MASK = ola::acn::DFLAG_MASK; - // This indicates a vector is present - static const uint8_t VFLAG_MASK = 0x40; - // This indicates a header field is present - static const uint8_t HFLAG_MASK = 0x20; - // This indicates a data field is present - static const uint8_t DFLAG_MASK = 0x10; private: unsigned int m_vector; unsigned int m_vector_size; + bool m_force_length_flag; // The max PDU length that can be represented with the 2 byte format for // the length field. diff --git a/libs/acn/RDMInflator.cpp b/libs/acn/RDMInflator.cpp index a18f634e01..a4e32166e1 100644 --- a/libs/acn/RDMInflator.cpp +++ b/libs/acn/RDMInflator.cpp @@ -32,13 +32,14 @@ using std::string; /** * Create a new RDM inflator */ -RDMInflator::RDMInflator() - : BaseInflator(PDU::ONE_BYTE) { +RDMInflator::RDMInflator(unsigned int vector) + : BaseInflator(PDU::ONE_BYTE), + m_vector(vector) { } /** - * Set a RDMHandler to run when receiving a RDM message. - * @param handler the callback to invoke when there is rdm data for this + * Set an RDMMessageHandler to run when receiving a RDM message. + * @param handler the callback to invoke when there is RDM data for this * universe. */ void RDMInflator::SetRDMHandler(RDMMessageHandler *handler) { @@ -46,6 +47,16 @@ void RDMInflator::SetRDMHandler(RDMMessageHandler *handler) { } +/** + * Set a GenericRDMHandler to run when receiving a RDM message. + * @param handler the callback to invoke when there is RDM data for this + * universe. + */ +void RDMInflator::SetGenericRDMHandler(GenericRDMMessageHandler *handler) { + m_generic_rdm_handler.reset(handler); +} + + /* * Decode the RDM 'header', which is 0 bytes in length. * @param headers the HeaderSet to add to @@ -76,11 +87,13 @@ bool RDMInflator::HandlePDUData(uint32_t vector, string rdm_message(reinterpret_cast(&data[0]), pdu_len); - E133Header e133_header = headers.GetE133Header(); - if (m_rdm_handler.get()) { + E133Header e133_header = headers.GetE133Header(); + m_rdm_handler->Run(&headers.GetTransportHeader(), &e133_header, rdm_message); + } else if (m_generic_rdm_handler.get()) { + m_generic_rdm_handler->Run(&headers, rdm_message); } else { OLA_WARN << "No RDM handler defined!"; } diff --git a/libs/acn/RDMInflator.h b/libs/acn/RDMInflator.h index 36ccf48930..4c37cf6c0c 100644 --- a/libs/acn/RDMInflator.h +++ b/libs/acn/RDMInflator.h @@ -41,12 +41,18 @@ class RDMInflator: public BaseInflator { const std::string& // rdm data > RDMMessageHandler; - RDMInflator(); + typedef ola::Callback2 GenericRDMMessageHandler; + + RDMInflator(unsigned int vector = ola::acn::VECTOR_FRAMING_RDMNET); ~RDMInflator() {} - uint32_t Id() const { return ola::acn::VECTOR_FRAMING_RDMNET; } + uint32_t Id() const { return m_vector; } void SetRDMHandler(RDMMessageHandler *handler); + void SetGenericRDMHandler(GenericRDMMessageHandler *handler); static const unsigned int VECTOR_RDMNET_DATA = 0xcc; @@ -65,6 +71,8 @@ class RDMInflator: public BaseInflator { private: std::auto_ptr m_rdm_handler; + std::auto_ptr m_generic_rdm_handler; + unsigned int m_vector; }; } // namespace acn } // namespace ola diff --git a/libs/acn/RDMPDU.cpp b/libs/acn/RDMPDU.cpp index 7b7b61f6c8..cab7d20a5c 100644 --- a/libs/acn/RDMPDU.cpp +++ b/libs/acn/RDMPDU.cpp @@ -29,6 +29,21 @@ namespace acn { using ola::io::OutputStream; using ola::network::HostToNetwork; +unsigned int RDMPDU::DataSize() const { + return static_cast(m_command.size()); +} + +bool RDMPDU::PackData(uint8_t *data, unsigned int *length) const { + *length = static_cast(m_command.size()); + memcpy(data, reinterpret_cast(m_command.data()), *length); + return true; +} + +void RDMPDU::PackData(ola::io::OutputStream *stream) const { + stream->Write(reinterpret_cast(m_command.data()), + static_cast(m_command.size())); +} + void RDMPDU::PrependPDU(ola::io::IOStack *stack) { uint8_t vector = HostToNetwork(ola::rdm::START_CODE); stack->Write(reinterpret_cast(&vector), sizeof(vector)); diff --git a/libs/acn/RDMPDU.h b/libs/acn/RDMPDU.h index 8d36e0ced7..c51d9a140f 100644 --- a/libs/acn/RDMPDU.h +++ b/libs/acn/RDMPDU.h @@ -21,16 +21,38 @@ #ifndef LIBS_ACN_RDMPDU_H_ #define LIBS_ACN_RDMPDU_H_ +#include +#include #include +#include #include "libs/acn/PDU.h" namespace ola { namespace acn { -class RDMPDU : private PDU { +class RDMPDU : public PDU { public: + explicit RDMPDU(const ola::io::ByteString &command): + PDU(ola::rdm::START_CODE, ONE_BYTE, true), + m_command(command) {} + + unsigned int HeaderSize() const { return 0; } + bool PackHeader(OLA_UNUSED uint8_t *data, + unsigned int *length) const { + *length = 0; + return true; + } + void PackHeader(OLA_UNUSED ola::io::OutputStream *stream) const {} + + unsigned int DataSize() const; + bool PackData(uint8_t *data, unsigned int *length) const; + void PackData(ola::io::OutputStream *stream) const; + static void PrependPDU(ola::io::IOStack *stack); + + private: + const ola::io::ByteString m_command; }; } // namespace acn } // namespace ola diff --git a/libs/acn/RDMPDUTest.cpp b/libs/acn/RDMPDUTest.cpp index 4c6ce89df2..ff6489d0fb 100644 --- a/libs/acn/RDMPDUTest.cpp +++ b/libs/acn/RDMPDUTest.cpp @@ -23,6 +23,8 @@ #include #include "ola/Logging.h" +#include "ola/io/ByteString.h" +#include "ola/io/IOQueue.h" #include "ola/io/IOStack.h" #include "ola/network/NetworkUtils.h" #include "ola/testing/TestUtils.h" @@ -32,24 +34,125 @@ namespace ola { namespace acn { +using ola::io::ByteString; +using ola::io::IOQueue; using ola::io::IOStack; +using ola::io::OutputStream; +using ola::network::HostToNetwork; class RDMPDUTest: public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RDMPDUTest); + CPPUNIT_TEST(testSimpleRDMPDU); + CPPUNIT_TEST(testSimpleRDMPDUToOutputStream); CPPUNIT_TEST(testPrepend); CPPUNIT_TEST_SUITE_END(); public: + void testSimpleRDMPDU(); + void testSimpleRDMPDUToOutputStream(); void testPrepend(); private: static const unsigned int TEST_VECTOR; + static const uint8_t EXPECTED_GET_RESPONSE_BUFFER[]; }; CPPUNIT_TEST_SUITE_REGISTRATION(RDMPDUTest); const unsigned int RDMPDUTest::TEST_VECTOR = 0xcc; +const uint8_t RDMPDUTest::EXPECTED_GET_RESPONSE_BUFFER[] = { + 1, 28, // sub code & length + 0, 3, 0, 0, 0, 4, // dst uid + 0, 1, 0, 0, 0, 2, // src uid + 0, 0, 0, 0, 10, // transaction, port id, msg count & sub device + 0x21, 1, 40, 4, // command, param id, param data length + 0x5a, 0x5a, 0x5a, 0x5a, // param data + 0x02, 0xb3 // checksum +}; + +/* + * Test that packing an RDMPDU works. + */ +void RDMPDUTest::testSimpleRDMPDU() { + ByteString empty; + RDMPDU empty_pdu(empty); + + OLA_ASSERT_EQ(0u, empty_pdu.HeaderSize()); + OLA_ASSERT_EQ(0u, empty_pdu.DataSize()); + OLA_ASSERT_EQ(4u, empty_pdu.Size()); + + ByteString response(EXPECTED_GET_RESPONSE_BUFFER, + sizeof(EXPECTED_GET_RESPONSE_BUFFER)); + RDMPDU pdu(response); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(29u, pdu.DataSize()); + OLA_ASSERT_EQ(33u, pdu.Size()); + + unsigned int size = pdu.Size(); + uint8_t *data = new uint8_t[size]; + unsigned int bytes_used = size; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + + // spot check the data + OLA_ASSERT_EQ((uint8_t) 0xf0, data[0]); + // bytes_used is technically data[1] and data[2] if > 255 + OLA_ASSERT_EQ((uint8_t) bytes_used, data[2]); + OLA_ASSERT_EQ(HostToNetwork((uint8_t) TEST_VECTOR), data[3]); + + // test undersized buffer + bytes_used = size - 1; + OLA_ASSERT_FALSE(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(0u, bytes_used); + + // test oversized buffer + bytes_used = size + 1; + OLA_ASSERT(pdu.Pack(data, &bytes_used)); + OLA_ASSERT_EQ(size, bytes_used); + delete[] data; +} + + +/* + * Test that writing to an output stream works. + */ +void RDMPDUTest::testSimpleRDMPDUToOutputStream() { + ByteString response(EXPECTED_GET_RESPONSE_BUFFER, + sizeof(EXPECTED_GET_RESPONSE_BUFFER)); + RDMPDU pdu(response); + + OLA_ASSERT_EQ(0u, pdu.HeaderSize()); + OLA_ASSERT_EQ(29u, pdu.DataSize()); + OLA_ASSERT_EQ(33u, pdu.Size()); + + IOQueue output; + OutputStream stream(&output); + pdu.Write(&stream); + OLA_ASSERT_EQ(33u, output.Size()); + + uint8_t *pdu_data = new uint8_t[output.Size()]; + unsigned int pdu_size = output.Peek(pdu_data, output.Size()); + OLA_ASSERT_EQ(output.Size(), pdu_size); + + uint8_t EXPECTED[] = { + 0xf0, 0x00, 0x21, + 0xcc, + 1, 28, // sub code & length + 0, 3, 0, 0, 0, 4, // dst uid + 0, 1, 0, 0, 0, 2, // src uid + 0, 0, 0, 0, 10, // transaction, port id, msg count & sub device + 0x21, 1, 40, 4, // command, param id, param data length + 0x5a, 0x5a, 0x5a, 0x5a, // param data + 0x02, 0xb3 // checksum + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size); + output.Pop(output.Size()); + delete[] pdu_data; +} + + void RDMPDUTest::testPrepend() { IOStack stack; RDMPDU::PrependPDU(&stack); diff --git a/libs/acn/RootPDU.cpp b/libs/acn/RootPDU.cpp index 227406124f..cec65ed7bb 100644 --- a/libs/acn/RootPDU.cpp +++ b/libs/acn/RootPDU.cpp @@ -86,12 +86,12 @@ void RootPDU::SetBlock(const PDUBlock *block) { /* * Prepend a Root Layer flags, length, vector & header */ -void RootPDU::PrependPDU(IOStack *stack, uint32_t vector, const CID &cid) { +void RootPDU::PrependPDU(IOStack *stack, uint32_t vector, const CID &cid, bool force_length_flag) { cid.Write(stack); vector = HostToNetwork(vector); stack->Write(reinterpret_cast(&vector), sizeof(vector)); - PrependFlagsAndLength(stack); + PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, force_length_flag); } } // namespace acn } // namespace ola diff --git a/libs/acn/RootPDU.h b/libs/acn/RootPDU.h index cc10ff8bcc..6d66ebc178 100644 --- a/libs/acn/RootPDU.h +++ b/libs/acn/RootPDU.h @@ -33,8 +33,8 @@ namespace acn { class RootPDU: public PDU { public: - explicit RootPDU(unsigned int vector): - PDU(vector), + explicit RootPDU(unsigned int vector, bool force_length_flag = false): + PDU(vector, FOUR_BYTES, force_length_flag), m_block(NULL), m_block_size(0) {} RootPDU(unsigned int vector, @@ -60,7 +60,7 @@ class RootPDU: public PDU { void SetBlock(const PDUBlock *block); static void PrependPDU(ola::io::IOStack *stack, uint32_t vector, - const ola::acn::CID &cid); + const ola::acn::CID &cid, bool force_length_flag = false); private: ola::acn::CID m_cid; diff --git a/libs/acn/RootSender.cpp b/libs/acn/RootSender.cpp index 009bb30f7e..767c8844c1 100644 --- a/libs/acn/RootSender.cpp +++ b/libs/acn/RootSender.cpp @@ -31,8 +31,8 @@ using ola::acn::CID; * Create a new RootSender * @param cid The CID to send in the Root PDU. */ -RootSender::RootSender(const CID &cid) - : m_root_pdu(0) { +RootSender::RootSender(const CID &cid, bool force_length_flag) + : m_root_pdu(0, force_length_flag) { m_root_pdu.Cid(cid); } diff --git a/libs/acn/RootSender.h b/libs/acn/RootSender.h index 31926ef8f0..a09bbf6199 100644 --- a/libs/acn/RootSender.h +++ b/libs/acn/RootSender.h @@ -32,7 +32,7 @@ namespace acn { class RootSender { public: - explicit RootSender(const ola::acn::CID &cid); + explicit RootSender(const ola::acn::CID &cid, bool force_length_flag = false); ~RootSender() {} // Convenience method to encapsulate & send a single PDU diff --git a/plugins/usbpro/DmxTriWidget.cpp b/plugins/usbpro/DmxTriWidget.cpp index e6fdefc6a3..d8b98461df 100644 --- a/plugins/usbpro/DmxTriWidget.cpp +++ b/plugins/usbpro/DmxTriWidget.cpp @@ -950,6 +950,9 @@ bool DmxTriWidgetImpl::TriToOlaReturnCode( /** * Convert a DMX-TRI return code to Nack reason code if appropriate + * + * On the widget, the RDM NACK code is currently bitwise or-ed with 0x20 to + * generate the error code */ bool DmxTriWidgetImpl::ReturnCodeToNackReason( uint8_t return_code, diff --git a/plugins/usbpro/DmxTriWidget.h b/plugins/usbpro/DmxTriWidget.h index 6ec0addb9d..1b9f28a346 100644 --- a/plugins/usbpro/DmxTriWidget.h +++ b/plugins/usbpro/DmxTriWidget.h @@ -201,6 +201,8 @@ class DmxTriWidgetImpl: public BaseUsbProWidget, EC_INVALID_IPV6_ADDRESS = 0x32, // this is a guess EC_INVALID_PORT = 0x33 // this is a guess } dmx_tri_error_codes; + // The RDM NACK code is currently bitwise or-ed with 0x20 to generate the + // error code // TODO(Peter): try and test the guessed values static const unsigned int DATA_OFFSET = 2; // first two bytes are CI & RC diff --git a/tools/e133/Makefile.mk b/tools/e133/Makefile.mk index 6b77f499d3..76f23cdf21 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 \ @@ -104,3 +106,13 @@ tools_e133_basic_device_SOURCES = tools/e133/basic-device.cpp tools_e133_basic_device_LDADD = common/libolacommon.la \ libs/acn/libolaacn.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 \ + 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 \ + tools/e133/libolae133common.la diff --git a/tools/e133/MessageBuilder.cpp b/tools/e133/MessageBuilder.cpp index d2c6a5143a..a192b45bf0 100644 --- a/tools/e133/MessageBuilder.cpp +++ b/tools/e133/MessageBuilder.cpp @@ -109,7 +109,7 @@ void MessageBuilder::BuildTCPRootE133(IOStack *packet, uint16_t endpoint_id) { E133PDU::PrependPDU(packet, vector, m_source_name, sequence_number, endpoint_id); - RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_E133, m_cid); + RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_RPT, m_cid); PreamblePacker::AddTCPPreamble(packet); } @@ -123,7 +123,7 @@ void MessageBuilder::BuildUDPRootE133(IOStack *packet, uint16_t endpoint_id) { E133PDU::PrependPDU(packet, vector, m_source_name, sequence_number, endpoint_id); - RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_E133, m_cid); + RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_RPT, m_cid); PreamblePacker::AddUDPPreamble(packet); } } // namespace e133 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; +} diff --git a/tools/rdm/TestDefinitions.py b/tools/rdm/TestDefinitions.py index e9adf278b5..696d0d05b1 100644 --- a/tools/rdm/TestDefinitions.py +++ b/tools/rdm/TestDefinitions.py @@ -915,7 +915,7 @@ class GetSubDeviceSupportedParameters(ResponderTestFixture): REQUIRES = ['sub_device_addresses'] PROVIDES = ['sub_device_supported_parameters'] - # E1.37, 2.1 Sub devices are required to support these. + # E1.37-1, 2.1 Sub devices are required to support these. MANDATORY_PIDS = ['SUPPORTED_PARAMETERS', 'DEVICE_INFO', 'SOFTWARE_VERSION_LABEL', @@ -4611,7 +4611,7 @@ class AllSubDevicesGetPresetPlayback(TestMixins.AllSubDevicesGetMixin, PID = 'PRESET_PLAYBACK' -# E1.37 PIDS +# E1.37-1 PIDS # ============================================================================= # IDENTIFY_MODE @@ -7923,6 +7923,785 @@ class SetInterfaceHardwareAddressType1WithData( PID = 'INTERFACE_HARDWARE_ADDRESS_TYPE1' +# E1.33/E1.37-7 PIDS +# ============================================================================= + +class AllSubDevicesGetSearchDomain(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get SEARCH_DOMAIN to ALL_SUB_DEVICES.""" + PID = 'SEARCH_DOMAIN' + + +# class GetSearchDomain(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'SEARCH_DOMAIN' +# TODO(peter): Test get + + +class GetSearchDomainWithData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET SEARCH_DOMAIN with data.""" + PID = 'SEARCH_DOMAIN' + + +# class SetSearchDomain(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'SEARCH_DOMAIN' +# TODO(peter): Test set + + +class SetSearchDomainWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set SEARCH_DOMAIN command with no data.""" + PID = 'SEARCH_DOMAIN' + + +class SetSearchDomainWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET SEARCH_DOMAIN command with extra data.""" + PID = 'SEARCH_DOMAIN' + + +class AllSubDevicesGetBrokerStatus(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get BROKER_STATUS to ALL_SUB_DEVICES.""" + PID = 'BROKER_STATUS' + + +# class GetBrokerStatus(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BROKER_STATUS' +# TODO(peter): Test get + + +class GetBrokerStatusWithData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET BROKER_STATUS with data.""" + PID = 'BROKER_STATUS' + + +# class SetBrokerStatus(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BROKER_STATUS' +# TODO(peter): Test set + + +class SetBrokerStatusWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set BROKER_STATUS command with no data.""" + PID = 'BROKER_STATUS' + + +class SetBrokerStatusWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET BROKER_STATUS command with extra data.""" + PID = 'BROKER_STATUS' + + +class AllSubDevicesGetEndpointMode(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_MODE to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_MODE' + DATA = [0x0001] + + +# class GetEndpointMode(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_MODE' +# TODO(peter): Test get + + +class GetZeroEndpointMode(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_MODE for endpoint id 0.""" + PID = 'ENDPOINT_MODE' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointModeWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_MODE with no argument given.""" + PID = 'ENDPOINT_MODE' + + +class GetEndpointModeWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_MODE with more than 2 bytes of data.""" + PID = 'ENDPOINT_MODE' + + +# class SetEndpointMode(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_MODE' +# TODO(peter): Test set + + +# class SetZeroEndpointMode(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET ENDPOINT_MODE for endpoint id 0.""" +# PID = 'ENDPOINT_MODE' +# TODO(peter): Test set zero + + +class SetEndpointModeWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set ENDPOINT_MODE command with no data.""" + PID = 'ENDPOINT_MODE' + + +class SetEndpointModeWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET ENDPOINT_MODE command with extra data.""" + PID = 'ENDPOINT_MODE' + DATA = 'foobar' + + +class AllSubDevicesGetEndpointLabel(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_LABEL to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_LABEL' + DATA = [0x0001] + + +# class GetEndpointLabel(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_LABEL' +# TODO(peter): Test get + + +class GetZeroEndpointLabel(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_LABEL for endpoint id 0.""" + PID = 'ENDPOINT_LABEL' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointLabelWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_LABEL with no argument given.""" + PID = 'ENDPOINT_LABEL' + + +class GetEndpointLabelWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_LABEL with more than 2 bytes of data.""" + PID = 'ENDPOINT_LABEL' + + +# class SetEndpointLabel(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_LABEL' +# TODO(peter): Test set + + +# class SetZeroEndpointLabel(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET ENDPOINT_LABEL for endpoint id 0.""" +# PID = 'ENDPOINT_LABEL' +# TODO(peter): Test set zero + + +class SetEndpointLabelWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set ENDPOINT_LABEL command with no data.""" + PID = 'ENDPOINT_LABEL' + + +class SetEndpointLabelWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET ENDPOINT_LABEL command with extra data.""" + PID = 'ENDPOINT_LABEL' + + +class AllSubDevicesGetEndpointTiming(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_TIMING to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_TIMING' + DATA = [0x0001] + + +# class GetEndpointTiming(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_TIMING' +# TODO(peter): Test get + + +class GetZeroEndpointTiming(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING for endpoint id 0.""" + PID = 'ENDPOINT_TIMING' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointTimingWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING with no argument given.""" + PID = 'ENDPOINT_TIMING' + + +class GetEndpointTimingWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING with more than 2 bytes of data.""" + PID = 'ENDPOINT_TIMING' + + +# class SetEndpointTiming(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_TIMING' +# TODO(peter): Test set + + +# class SetZeroEndpointTiming(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET ENDPOINT_TIMING for endpoint id 0.""" +# PID = 'ENDPOINT_TIMING' +# TODO(peter): Test set zero + + +class SetEndpointTimingWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set ENDPOINT_TIMING command with no data.""" + PID = 'ENDPOINT_TIMING' + + +class SetEndpointTimingWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET ENDPOINT_TIMING command with extra data.""" + PID = 'ENDPOINT_TIMING' + DATA = 'foobar' + + +class AllSubDevicesGetEndpointTimingDescription( + TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_TIMING_DESCRIPTION to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + DATA = [0x01] + + +# class GetEndpointTimingDescription(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_TIMING_DESCRIPTION' +# TODO(peter): Test get + + +class GetZeroEndpointTimingDescription(TestMixins.GetZeroUInt8Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING_DESCRIPTION for timing setting 0.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + + +class GetEndpointTimingDescriptionWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING_DESCRIPTION with no argument given.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + + +class GetEndpointTimingDescriptionWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TIMING_DESCRIPTION with more than 1 byte of data.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + + +class SetEndpointTimingDescription(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_TIMING_DESCRIPTION.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + + +class SetEndpointTimingDescriptionWithData( + TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_TIMING_DESCRIPTION with data.""" + PID = 'ENDPOINT_TIMING_DESCRIPTION' + + +class AllSubDevicesGetEndpointResponders(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_RESPONDERS to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_RESPONDERS' + DATA = [0x0001] + + +# class GetEndpointResponders(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_RESPONDERS' +# TODO(peter): Test get + + +class GetZeroEndpointResponders(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDERS for endpoint id 0.""" + PID = 'ENDPOINT_RESPONDERS' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointRespondersWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDERS with no argument given.""" + PID = 'ENDPOINT_RESPONDERS' + + +class GetEndpointRespondersWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDERS with more than 2 bytes of data.""" + PID = 'ENDPOINT_RESPONDERS' + + +class SetEndpointResponders(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_RESPONDERS.""" + PID = 'ENDPOINT_RESPONDERS' + + +class SetEndpointRespondersWithData(TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_RESPONDERS with data.""" + PID = 'ENDPOINT_RESPONDERS' + + +class AllSubDevicesGetEndpointResponderListChange( + TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_RESPONDER_LIST_CHANGE to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + DATA = [0x0001] + + +# class GetEndpointResponderListChange(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' +# TODO(peter): Test get + + +class GetZeroEndpointResponderListChange(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDER_LIST_CHANGE for endpoint id 0.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointResponderListChangeWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDER_LIST_CHANGE with no argument given.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + + +class GetEndpointResponderListChangeWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_RESPONDER_LIST_CHANGE with more than 2 bytes of data.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + + +class SetEndpointResponderListChange(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_RESPONDER_LIST_CHANGE.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + + +class SetEndpointResponderListChangeWithData( + TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_RESPONDER_LIST_CHANGE with data.""" + PID = 'ENDPOINT_RESPONDER_LIST_CHANGE' + + +class AllSubDevicesGetEndpointList(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_LIST to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_LIST' + + +# class GetEndpointList(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_LIST' +# TODO(peter): Test get + + +class GetEndpointListWithData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_LIST with data.""" + PID = 'ENDPOINT_LIST' + + +class SetEndpointList(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_LIST.""" + PID = 'ENDPOINT_LIST' + + +class SetEndpointListWithData(TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_LIST with data.""" + PID = 'ENDPOINT_LIST' + + +class AllSubDevicesGetEndpointListChange(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_LIST_CHANGE to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_LIST_CHANGE' + + +# class GetEndpointListChange(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_LIST_CHANGE' +# TODO(peter): Test get + + +class GetEndpointListChangeWithData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_LIST_CHANGE with data.""" + PID = 'ENDPOINT_LIST_CHANGE' + + +class SetEndpointListChange(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_LIST_CHANGE.""" + PID = 'ENDPOINT_LIST_CHANGE' + + +class SetEndpointListChangeWithData(TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET ENDPOINT_LIST_CHANGE with data.""" + PID = 'ENDPOINT_LIST_CHANGE' + + +class AllSubDevicesGetEndpointToUniverse(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get ENDPOINT_TO_UNIVERSE to ALL_SUB_DEVICES.""" + PID = 'ENDPOINT_TO_UNIVERSE' + DATA = [0x0001] + + +# class GetEndpointToUniverse(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_TO_UNIVERSE' +# TODO(peter): Test get + + +class GetZeroEndpointToUniverse(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TO_UNIVERSE for endpoint id 0.""" + PID = 'ENDPOINT_TO_UNIVERSE' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetEndpointToUniverseWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TO_UNIVERSE with no argument given.""" + PID = 'ENDPOINT_TO_UNIVERSE' + + +class GetEndpointToUniverseWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET ENDPOINT_TO_UNIVERSE with more than 2 bytes of data.""" + PID = 'ENDPOINT_TO_UNIVERSE' + + +# class SetEndpointToUniverse(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'ENDPOINT_TO_UNIVERSE' +# TODO(peter): Test set + + +# class SetZeroEndpointToUniverse(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET ENDPOINT_TO_UNIVERSE for endpoint id 0.""" +# PID = 'ENDPOINT_TO_UNIVERSE' +# TODO(peter): Test set zero + + +class SetEndpointToUniverseWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set ENDPOINT_TO_UNIVERSE command with no data.""" + PID = 'ENDPOINT_TO_UNIVERSE' + + +class SetEndpointToUniverseWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET ENDPOINT_TO_UNIVERSE command with extra data.""" + PID = 'ENDPOINT_TO_UNIVERSE' + DATA = 'foobar' + + +class AllSubDevicesGetRdmTrafficEnable(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get RDM_TRAFFIC_ENABLE to ALL_SUB_DEVICES.""" + PID = 'RDM_TRAFFIC_ENABLE' + DATA = [0x0001] + + +# class GetRdmTrafficEnable(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'RDM_TRAFFIC_ENABLE' +# TODO(peter): Test get + + +class GetZeroRdmTrafficEnable(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET RDM_TRAFFIC_ENABLE for endpoint id 0.""" + PID = 'RDM_TRAFFIC_ENABLE' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetRdmTrafficEnableWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET RDM_TRAFFIC_ENABLE with no argument given.""" + PID = 'RDM_TRAFFIC_ENABLE' + + +class GetRdmTrafficEnableWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET RDM_TRAFFIC_ENABLE with more than 2 bytes of data.""" + PID = 'RDM_TRAFFIC_ENABLE' + + +# class SetRdmTrafficEnable(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'RDM_TRAFFIC_ENABLE' +# TODO(peter): Test set + + +# class SetZeroRdmTrafficEnable(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET RDM_TRAFFIC_ENABLE for endpoint id 0.""" +# PID = 'RDM_TRAFFIC_ENABLE' +# TODO(peter): Test set zero + + +class SetRdmTrafficEnableWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set RDM_TRAFFIC_ENABLE command with no data.""" + PID = 'RDM_TRAFFIC_ENABLE' + + +class SetRdmTrafficEnableWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET RDM_TRAFFIC_ENABLE command with extra data.""" + PID = 'RDM_TRAFFIC_ENABLE' + DATA = 'foobar' + + +class AllSubDevicesGetDiscoveryState(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get DISCOVERY_STATE to ALL_SUB_DEVICES.""" + PID = 'DISCOVERY_STATE' + DATA = [0x0001] + + +# class GetDiscoveryState(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'DISCOVERY_STATE' +# TODO(peter): Test get + + +class GetZeroDiscoveryState(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET DISCOVERY_STATE for endpoint id 0.""" + PID = 'DISCOVERY_STATE' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetDiscoveryStateWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET DISCOVERY_STATE with no argument given.""" + PID = 'DISCOVERY_STATE' + + +class GetDiscoveryStateWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET DISCOVERY_STATE with more than 2 bytes of data.""" + PID = 'DISCOVERY_STATE' + + +# class SetDiscoveryState(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'DISCOVERY_STATE' +# TODO(peter): Test set + + +# class SetZeroDiscoveryState(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET DISCOVERY_STATE for endpoint id 0.""" +# PID = 'DISCOVERY_STATE' +# TODO(peter): Test set zero + + +class SetDiscoveryStateWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set DISCOVERY_STATE command with no data.""" + PID = 'DISCOVERY_STATE' + + +class SetDiscoveryStateWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET DISCOVERY_STATE command with extra data.""" + PID = 'DISCOVERY_STATE' + DATA = 'foobar' + + +class AllSubDevicesGetBackgroundDiscovery(TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get BACKGROUND_DISCOVERY to ALL_SUB_DEVICES.""" + PID = 'BACKGROUND_DISCOVERY' + DATA = [0x0001] + + +# class GetBackgroundDiscovery(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BACKGROUND_DISCOVERY' +# TODO(peter): Test get + + +class GetZeroBackgroundDiscovery(TestMixins.GetZeroUInt16Mixin, + OptionalParameterTestFixture): + """GET BACKGROUND_DISCOVERY for endpoint id 0.""" + PID = 'BACKGROUND_DISCOVERY' + OVERRIDE_NACKS = [RDMNack.NR_ENDPOINT_NUMBER_INVALID] + + +class GetBackgroundDiscoveryWithNoData(TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET BACKGROUND_DISCOVERY with no argument given.""" + PID = 'BACKGROUND_DISCOVERY' + + +class GetBackgroundDiscoveryWithExtraData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET BACKGROUND_DISCOVERY with more than 2 bytes of data.""" + PID = 'BACKGROUND_DISCOVERY' + + +# class SetBackgroundDiscovery(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BACKGROUND_DISCOVERY' +# TODO(peter): Test set + + +# class SetZeroBackgroundDiscovery(TestMixins.SetZero, +# OptionalParameterTestFixture): +# """SET BACKGROUND_DISCOVERY for endpoint id 0.""" +# PID = 'BACKGROUND_DISCOVERY' +# TODO(peter): Test set zero + + +class SetBackgroundDiscoveryWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set BACKGROUND_DISCOVERY command with no data.""" + PID = 'BACKGROUND_DISCOVERY' + + +class SetBackgroundDiscoveryWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET BACKGROUND_DISCOVERY command with extra data.""" + PID = 'BACKGROUND_DISCOVERY' + DATA = 'foobar' + + +class AllSubDevicesGetBackgroundQueuedStatusPolicy( + TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get BACKGROUND_QUEUED_STATUS_POLICY to ALL_SUB_DEVICES.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY' + + +# class GetBackgroundQueuedStatusPolicy(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BACKGROUND_QUEUED_STATUS_POLICY' +# TODO(peter): Test get + + +class GetBackgroundQueuedStatusPolicyWithData(TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET BACKGROUND_QUEUED_STATUS_POLICY with data.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY' + + +# class SetBackgroundQueuedStatusPolicy(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BACKGROUND_QUEUED_STATUS_POLICY' +# TODO(peter): Test set + + +class SetBackgroundQueuedStatusPolicyWithNoData(TestMixins.SetWithNoDataMixin, + OptionalParameterTestFixture): + """Set BACKGROUND_QUEUED_STATUS_POLICY command with no data.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY' + + +class SetBackgroundQueuedStatusPolicyWithExtraData(TestMixins.SetWithDataMixin, + OptionalParameterTestFixture): + """Send a SET BACKGROUND_QUEUED_STATUS_POLICY command with extra data.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY' + + +class AllSubDevicesGetBackgroundQueuedStatusPolicyDescription( + TestMixins.AllSubDevicesGetMixin, + OptionalParameterTestFixture): + """Send a get BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION to ALL_SUB_DEVICES.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' + DATA = [0x00] + + +# class GetBackgroundQueuedStatusPolicyDescription(TestMixins., +# OptionalParameterTestFixture): +# CATEGORY = TestCategory. +# PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' +# TODO(peter): Test get + + +class GetBackgroundQueuedStatusPolicyDescriptionWithNoData( + TestMixins.GetWithNoDataMixin, + OptionalParameterTestFixture): + """GET BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION with no argument given.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' + + +class GetBackgroundQueuedStatusPolicyDescriptionWithExtraData( + TestMixins.GetWithDataMixin, + OptionalParameterTestFixture): + """GET BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION with more than 1 byte of data.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' + + +class SetBackgroundQueuedStatusPolicyDescription(TestMixins.UnsupportedSetMixin, + OptionalParameterTestFixture): + """Attempt to SET BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' + + +class SetBackgroundQueuedStatusPolicyDescriptionWithData( + TestMixins.UnsupportedSetWithDataMixin, + OptionalParameterTestFixture): + """Attempt to SET BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION with data.""" + PID = 'BACKGROUND_QUEUED_STATUS_POLICY_DESCRIPTION' + + # Cross check the control fields with various other properties # ----------------------------------------------------------------------------- class SubDeviceControlField(TestFixture): diff --git a/tools/rdm/TestMixins.py b/tools/rdm/TestMixins.py index 66e54ae2b9..e4866fe15d 100644 --- a/tools/rdm/TestMixins.py +++ b/tools/rdm/TestMixins.py @@ -1025,16 +1025,27 @@ def ResetState(self): class GetZeroMixin(ResponderTestFixture): - """Send a get to index 0, expect NR_DATA_OUT_OF_RANGE""" + """Send a get to index 0, normally expect NR_DATA_OUT_OF_RANGE + + If OVERRIDE_NACKS is non-empty, this overrides NR_DATA_OUT_OF_RANGE and adds + a custom NackGetResult to the list of allowed results for each entry. + """ CATEGORY = TestCategory.ERROR_CONDITIONS DATA = None + OVERRIDE_NACKS = [] def Test(self): if self.DATA is None: self.SetBroken('No DATA given for %s' % self.__class__.__name__) return - self.AddIfGetSupported(self.NackGetResult(RDMNack.NR_DATA_OUT_OF_RANGE)) + results = [] + if self.OVERRIDE_NACKS: + for nack in self.OVERRIDE_NACKS: + results.append(self.NackGetResult(nack)) + else: + results.append(self.NackGetResult(RDMNack.NR_DATA_OUT_OF_RANGE)) + self.AddIfGetSupported(results) self.SendRawGet(ROOT_DEVICE, self.pid, self.DATA) diff --git a/tools/rdm/list_rdm_tests.py b/tools/rdm/list_rdm_tests.py index 099594d5c7..000666fa59 100755 --- a/tools/rdm/list_rdm_tests.py +++ b/tools/rdm/list_rdm_tests.py @@ -153,11 +153,15 @@ def GetWithExtraData(names, pid, pid_test_base_name, get_size): print(' PID = \'%s\'' % (pid.name)) dummy_data = GenerateDummyData(get_size) if dummy_data is None: - print((" #DATA = b'foo' # TODO(%s): Specify extra data if this isn't " - "enough") % (getpass.getuser())) + print((" #DATA = 'foo' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (getpass.getuser(), get_size)) elif dummy_data != 'foo': - # Doesn't match default - print(" DATA = b'%s'" % (dummy_data)) + # Doesn't match default, explicitly set value + print((" DATA = '%s' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (dummy_data, getpass.getuser(), get_size)) + else: + print((" #DATA = '%s' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (dummy_data, getpass.getuser(), get_size)) print('') print('') @@ -272,11 +276,15 @@ def SetWithExtraData(names, pid, pid_test_base_name, set_size): print(' PID = \'%s\'' % (pid.name)) dummy_data = GenerateDummyData(set_size) if dummy_data is None: - print((" #DATA = b'foo' # TODO(%s): Specify extra data if this isn't " - "enough") % (getpass.getuser())) + print((" #DATA = 'foo' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (getpass.getuser(), set_size)) elif dummy_data != 'foo': - # Doesn't match default - print(" DATA = b'%s'" % (dummy_data)) + # Doesn't match default, explicitly set value + print((" DATA = '%s' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (dummy_data, getpass.getuser(), set_size)) + else: + print((" #DATA = '%s' # TODO(%s): Specify extra data if this isn't " + "enough. Ensure the first %d bytes are sane/valid.") % (dummy_data, getpass.getuser(), set_size)) print('') print('') @@ -343,8 +351,11 @@ def main(): get_size = 0 if ((pid.RequestSupported(PidStore.RDM_GET)) and (pid.GetRequest(PidStore.RDM_GET).HasAtoms())): - get_size = pid.GetRequest(PidStore.RDM_GET).GetAtoms()[0].size - # print('# Get requires %d bytes' % (get_size)) + for atom in pid.GetRequest(PidStore.RDM_GET).GetAtoms(): + get_size += atom.size + #TODO(Peter): Should we just print this total all the time? + if get_size != pid.GetRequest(PidStore.RDM_GET).GetAtoms()[0].size: + print('# Get requires %d bytes' % (get_size)) AllSubDevicesGet(names, pid, pid_test_base_name, get_size) @@ -382,7 +393,8 @@ def main(): first_atom.ValidateRawValueInRange(1))): SetZero(names, pid, pid_test_base_name, first_atom) - SetWithNoData(names, pid, pid_test_base_name) + if set_size > 0: + SetWithNoData(names, pid, pid_test_base_name) SetWithExtraData(names, pid, pid_test_base_name, set_size) else: