From 8d1c56393232af9d94142a6b84c766292cb5deb0 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Mon, 3 Jul 2023 18:17:55 +0100
Subject: [PATCH 01/17] Add the ability to build BrokerNullTCP packets

(cherry picked from commit bdb40db292dda2f280db66c738c120d6a7fa5c45)
---
 include/ola/e133/MessageBuilder.h |  4 ++++
 tools/e133/MessageBuilder.cpp     | 12 ++++++++++++
 2 files changed, 16 insertions(+)

diff --git a/include/ola/e133/MessageBuilder.h b/include/ola/e133/MessageBuilder.h
index 272a56339a..ebff6ffe58 100644
--- a/include/ola/e133/MessageBuilder.h
+++ b/include/ola/e133/MessageBuilder.h
@@ -48,6 +48,10 @@ class MessageBuilder {
 
     void BuildNullTCPPacket(IOStack *packet);
 
+    void BuildBrokerFetchClientListTCPPacket(IOStack *packet);
+
+    void BuildBrokerNullTCPPacket(IOStack *packet);
+
     void BuildTCPE133StatusPDU(IOStack *packet,
                                uint32_t sequence_number, uint16_t endpoint_id,
                                ola::e133::E133StatusCode status_code,
diff --git a/tools/e133/MessageBuilder.cpp b/tools/e133/MessageBuilder.cpp
index a192b45bf0..eed3b85961 100644
--- a/tools/e133/MessageBuilder.cpp
+++ b/tools/e133/MessageBuilder.cpp
@@ -25,6 +25,7 @@
 #include "ola/e133/MessageBuilder.h"
 #include "ola/io/IOStack.h"
 
+#include "libs/acn/BrokerPDU.h"
 #include "libs/acn/E133PDU.h"
 #include "libs/acn/RDMPDU.h"
 #include "libs/acn/RootPDU.h"
@@ -36,6 +37,7 @@ namespace e133 {
 
 using ola::acn::CID;
 using ola::io::IOStack;
+using ola::acn::BrokerPDU;
 using ola::acn::E133PDU;
 using ola::acn::PreamblePacker;
 using ola::acn::RootPDU;
@@ -67,6 +69,16 @@ void MessageBuilder::BuildNullTCPPacket(IOStack *packet) {
 }
 
 
+/**
+ * Build a Broker NULL TCP packet. These packets can be used for broker heartbeats.
+ */
+void MessageBuilder::BuildBrokerNullTCPPacket(IOStack *packet) {
+  BrokerPDU::PrependPDU(packet, ola::acn::VECTOR_BROKER_NULL);
+  RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_BROKER, m_cid, true);
+  PreamblePacker::AddTCPPreamble(packet);
+}
+
+
 /**
  * Build a TCP E1.33 Status PDU response. This should really only be used with
  * SC_E133_ACK.

From f56a071c0999aaf0816505b76bb818edf19bf06b Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Mon, 3 Jul 2023 18:19:10 +0100
Subject: [PATCH 02/17] Correctly commit only a partial change for now

(cherry picked from commit 847734f7527568c8ad380d60f327e96243f8e10b)
---
 include/ola/e133/MessageBuilder.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/include/ola/e133/MessageBuilder.h b/include/ola/e133/MessageBuilder.h
index ebff6ffe58..a94d52200c 100644
--- a/include/ola/e133/MessageBuilder.h
+++ b/include/ola/e133/MessageBuilder.h
@@ -48,8 +48,6 @@ class MessageBuilder {
 
     void BuildNullTCPPacket(IOStack *packet);
 
-    void BuildBrokerFetchClientListTCPPacket(IOStack *packet);
-
     void BuildBrokerNullTCPPacket(IOStack *packet);
 
     void BuildTCPE133StatusPDU(IOStack *packet,

From d242ccb15e95cc9757cdea12ce19dace0da94a3c Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Mon, 3 Jul 2023 18:29:12 +0100
Subject: [PATCH 03/17] Update E133HealthCheckedConnection to work with the new
 standardised heartbeat behaviour and timing

(cherry picked from commit 4d7049caf766586177248d1fbba0b31b7185633d)
---
 tools/e133/E133HealthCheckedConnection.cpp |  8 +++++---
 tools/e133/E133HealthCheckedConnection.h   | 17 ++++++++++++-----
 2 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/tools/e133/E133HealthCheckedConnection.cpp b/tools/e133/E133HealthCheckedConnection.cpp
index f03e7e5dc7..c6dd17f663 100644
--- a/tools/e133/E133HealthCheckedConnection.cpp
+++ b/tools/e133/E133HealthCheckedConnection.cpp
@@ -39,8 +39,9 @@ E133HealthCheckedConnection::E133HealthCheckedConnection(
   ola::io::NonBlockingSender *message_queue,
   ola::SingleUseCallback0<void> *on_timeout,
   ola::thread::SchedulingExecutorInterface *scheduler,
-  const ola::TimeInterval heartbeat_interval)
-    : HealthCheckedConnection(scheduler, heartbeat_interval),
+  const ola::TimeInterval heartbeat_interval,
+  const ola::TimeInterval timeout_interval)
+    : HealthCheckedConnection(scheduler, heartbeat_interval, timeout_interval),
       m_message_builder(message_builder),
       m_message_queue(message_queue),
       m_on_timeout(on_timeout),
@@ -52,8 +53,9 @@ E133HealthCheckedConnection::E133HealthCheckedConnection(
  * Send a E1.33 heartbeat
  */
 void E133HealthCheckedConnection::SendHeartbeat() {
+  OLA_DEBUG << "Sending heartbeat";
   IOStack packet(m_message_builder->pool());
-  m_message_builder->BuildNullTCPPacket(&packet);
+  m_message_builder->BuildBrokerNullTCPPacket(&packet);
   m_message_queue->SendMessage(&packet);
 }
 
diff --git a/tools/e133/E133HealthCheckedConnection.h b/tools/e133/E133HealthCheckedConnection.h
index 5e7b61453a..3e6712cb30 100644
--- a/tools/e133/E133HealthCheckedConnection.h
+++ b/tools/e133/E133HealthCheckedConnection.h
@@ -21,9 +21,12 @@
  * same health checking logic (and agree on heartbeat intervals) for this to
  * work correctly.
  *
- * Even though this is called a E1.33 Health Checked Connection, it doesn't
- * actually rely on E1.33 at all. You can use it with any ACN based protocol
- * since it just sends PDUs with a ROOT_VECTOR_NULL as heartbeat messages.
+ * This is a E1.33 Health Checked Connection as it sends E1.33 Broker NULL PDUs
+ * using VECTOR_BROKER_NULL, but it also accepts any ACN root layer PDUs as a
+ * positive indication the connection is healthy.
+ *
+ * You could use it with any ACN based protocol by subclassing it and sending
+ * heartbeat messages of ROOT_VECTOR_NULL via SendHeartbeat instead.
  */
 
 #ifndef TOOLS_E133_E133HEALTHCHECKEDCONNECTION_H_
@@ -52,7 +55,9 @@ class E133HealthCheckedConnection
         ola::SingleUseCallback0<void> *on_timeout,
         ola::thread::SchedulingExecutorInterface *scheduler,
         const ola::TimeInterval heartbeat_interval =
-          ola::TimeInterval(E133_TCP_HEARTBEAT_INTERVAL, 0));
+          ola::TimeInterval(E133_TCP_HEARTBEAT_INTERVAL, 0),
+        const ola::TimeInterval timeout_interval =
+          ola::TimeInterval(E133_HEARTBEAT_TIMEOUT, 0));
 
     void SendHeartbeat();
     void HeartbeatTimeout();
@@ -64,6 +69,8 @@ class E133HealthCheckedConnection
     ola::thread::SchedulingExecutorInterface *m_executor;
 
     // The default interval in seconds for sending heartbeat messages.
-    static const unsigned int E133_TCP_HEARTBEAT_INTERVAL = 5;
+    static const unsigned int E133_TCP_HEARTBEAT_INTERVAL = 15;
+    // The default interval in seconds before timing out.
+    static const unsigned int E133_HEARTBEAT_TIMEOUT = 45;
 };
 #endif  // TOOLS_E133_E133HEALTHCHECKEDCONNECTION_H_

From b378b89ef13224204c995161d532b72e4e50ac6c Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 8 Jul 2023 23:07:14 +0100
Subject: [PATCH 04/17] Update the RDM Inflator to the standardised E1.33

(cherry picked from commit d1eb7b8ff4f3f8fce4149844d03cbb53a5a42ac7)
---
 libs/acn/RDMInflator.cpp | 4 ++--
 libs/acn/RDMInflator.h   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libs/acn/RDMInflator.cpp b/libs/acn/RDMInflator.cpp
index a4e32166e1..9dba40ba45 100644
--- a/libs/acn/RDMInflator.cpp
+++ b/libs/acn/RDMInflator.cpp
@@ -74,13 +74,13 @@ bool RDMInflator::DecodeHeader(HeaderSet *,
 
 
 /*
- * Handle a DMP PDU for E1.33.
+ * Handle an RDM PDU for E1.33.
  */
 bool RDMInflator::HandlePDUData(uint32_t vector,
                                 const HeaderSet &headers,
                                 const uint8_t *data,
                                 unsigned int pdu_len) {
-  if (vector != VECTOR_RDMNET_DATA) {
+  if (vector != VECTOR_RDM_CMD_RDM_DATA) {
     OLA_INFO << "Not a RDM message, vector was " << vector;
     return true;
   }
diff --git a/libs/acn/RDMInflator.h b/libs/acn/RDMInflator.h
index 41f29ae676..aa2cb73add 100644
--- a/libs/acn/RDMInflator.h
+++ b/libs/acn/RDMInflator.h
@@ -46,6 +46,8 @@ class RDMInflator: public BaseInflator {
                          const std::string&  // rdm data
                         > GenericRDMMessageHandler;
 
+    // TODO(Peter): Set a better default vector for RDM use (possibly the RPT
+    // one)
   explicit RDMInflator(unsigned int vector = ola::acn::VECTOR_FRAMING_RDMNET);
   ~RDMInflator() {}
 
@@ -54,8 +56,6 @@ class RDMInflator: public BaseInflator {
   void SetRDMHandler(RDMMessageHandler *handler);
   void SetGenericRDMHandler(GenericRDMMessageHandler *handler);
 
-  static const unsigned int VECTOR_RDMNET_DATA = 0xcc;
-
  protected:
   bool DecodeHeader(HeaderSet *headers,
                     const uint8_t *data,

From 7f68a429df7d072564e4ae9931d1bcbd1b23d88d Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 8 Jul 2023 23:11:55 +0100
Subject: [PATCH 05/17] Add support inflator for a lot of E1.33 RPT PDUs

(cherry picked from commit 669e2e5fd5c15252223a6a22e5e1840d8d40be6d)
---
 libs/acn/HeaderSet.h               |  8 ++-
 libs/acn/Makefile.mk               |  5 ++
 libs/acn/RPTHeader.h               | 94 ++++++++++++++++++++++++++++++
 libs/acn/RPTInflator.cpp           | 73 +++++++++++++++++++++++
 libs/acn/RPTInflator.h             | 58 ++++++++++++++++++
 libs/acn/RPTNotificationInflator.h | 55 +++++++++++++++++
 libs/acn/RPTRequestInflator.h      | 55 +++++++++++++++++
 7 files changed, 347 insertions(+), 1 deletion(-)
 create mode 100644 libs/acn/RPTHeader.h
 create mode 100644 libs/acn/RPTInflator.cpp
 create mode 100644 libs/acn/RPTInflator.h
 create mode 100644 libs/acn/RPTNotificationInflator.h
 create mode 100644 libs/acn/RPTRequestInflator.h

diff --git a/libs/acn/HeaderSet.h b/libs/acn/HeaderSet.h
index 06a56c26aa..ae4925e1b4 100644
--- a/libs/acn/HeaderSet.h
+++ b/libs/acn/HeaderSet.h
@@ -28,6 +28,7 @@
 #include "libs/acn/E133Header.h"
 #include "libs/acn/LLRPHeader.h"
 #include "libs/acn/RootHeader.h"
+#include "libs/acn/RPTHeader.h"
 #include "libs/acn/TransportHeader.h"
 
 namespace ola {
@@ -60,6 +61,9 @@ class HeaderSet {
     const LLRPHeader &GetLLRPHeader() const { return m_llrp_header; }
     void SetLLRPHeader(const LLRPHeader &header) { m_llrp_header = header; }
 
+    const RPTHeader &GetRPTHeader() const { return m_rpt_header; }
+    void SetRPTHeader(const RPTHeader &header) { m_rpt_header = header; }
+
     bool operator==(const HeaderSet &other) const {
       return (
           m_transport_header == other.m_transport_header &&
@@ -67,7 +71,8 @@ class HeaderSet {
           m_e131_header == other.m_e131_header &&
           m_e133_header == other.m_e133_header &&
           m_dmp_header == other.m_dmp_header &&
-          m_llrp_header == other.m_llrp_header);
+          m_llrp_header == other.m_llrp_header &&
+          m_rpt_header == other.m_rpt_header);
     }
 
  private:
@@ -77,6 +82,7 @@ class HeaderSet {
     E133Header m_e133_header;
     DMPHeader m_dmp_header;
     LLRPHeader m_llrp_header;
+    RPTHeader m_rpt_header;
 };
 }  // namespace acn
 }  // namespace ola
diff --git a/libs/acn/Makefile.mk b/libs/acn/Makefile.mk
index 7c02570ed9..45c84e1619 100644
--- a/libs/acn/Makefile.mk
+++ b/libs/acn/Makefile.mk
@@ -104,6 +104,11 @@ libs_acn_libolae131core_la_SOURCES = \
     libs/acn/RootPDU.h \
     libs/acn/RootSender.cpp \
     libs/acn/RootSender.h \
+    libs/acn/RPTHeader.h \
+    libs/acn/RPTInflator.cpp \
+    libs/acn/RPTInflator.h \
+    libs/acn/RPTNotificationInflator.h \
+    libs/acn/RPTRequestInflator.h \
     libs/acn/TCPTransport.cpp \
     libs/acn/TCPTransport.h \
     libs/acn/Transport.h \
diff --git a/libs/acn/RPTHeader.h b/libs/acn/RPTHeader.h
new file mode 100644
index 0000000000..80bb34cb5f
--- /dev/null
+++ b/libs/acn/RPTHeader.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ * RPTHeader.h
+ * The E1.33 RPT Header
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTHEADER_H_
+#define LIBS_ACN_RPTHEADER_H_
+
+#include <ola/base/Macro.h>
+#include <ola/rdm/UID.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace ola {
+namespace acn {
+
+/*
+ * Header for the E133 RPT layer
+ */
+class RPTHeader {
+ public:
+  RPTHeader()
+      : m_source_uid(0, 0),
+        m_source_endpoint(0),
+        m_destination_uid(0, 0),
+        m_destination_endpoint(0),
+        m_sequence(0) {
+  }
+
+  RPTHeader(ola::rdm::UID source_uid,
+            uint16_t source_endpoint,
+            ola::rdm::UID destination_uid,
+            uint16_t destination_endpoint,
+            uint32_t sequence)
+      : m_source_uid(source_uid),
+        m_source_endpoint(source_endpoint),
+        m_destination_uid(destination_uid),
+        m_destination_endpoint(destination_endpoint),
+        m_sequence(sequence) {
+  }
+  ~RPTHeader() {}
+
+  ola::rdm::UID SourceUID() const { return m_source_uid; }
+  uint16_t SourceEndpoint() const { return m_source_endpoint; }
+  ola::rdm::UID DestinationUID() const { return m_destination_uid; }
+  uint16_t DestinationEndpoint() const { return m_destination_endpoint; }
+  // TODO(Peter): Should the sequence number really be part of the header?
+  uint32_t Sequence() const { return m_sequence; }
+
+  bool operator==(const RPTHeader &other) const {
+    return m_source_uid == other.m_source_uid &&
+      m_source_endpoint == other.m_source_endpoint &&
+      m_destination_uid == other.m_destination_uid &&
+      m_destination_endpoint == other.m_destination_endpoint &&
+      m_sequence == other.m_sequence;
+  }
+
+  PACK(
+  struct rpt_pdu_header_s {
+    uint8_t source_uid[ola::rdm::UID::LENGTH];
+    uint16_t source_endpoint;
+    uint8_t destination_uid[ola::rdm::UID::LENGTH];
+    uint16_t destination_endpoint;
+    uint32_t sequence;
+    uint8_t reserved;
+  });
+  typedef struct rpt_pdu_header_s rpt_pdu_header;
+
+ private:
+  ola::rdm::UID m_source_uid;
+  uint16_t m_source_endpoint;
+  ola::rdm::UID m_destination_uid;
+  uint16_t m_destination_endpoint;
+  uint32_t m_sequence;
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTHEADER_H_
diff --git a/libs/acn/RPTInflator.cpp b/libs/acn/RPTInflator.cpp
new file mode 100644
index 0000000000..5d9cde6f50
--- /dev/null
+++ b/libs/acn/RPTInflator.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ *
+ * RPTInflator.cpp
+ * The Inflator for E1.33 RPT
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#include "ola/Logging.h"
+#include "ola/network/NetworkUtils.h"
+#include "libs/acn/RPTInflator.h"
+
+namespace ola {
+namespace acn {
+
+using ola::network::NetworkToHost;
+
+/*
+ * Decode the E1.33 RPT 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 RPTInflator::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(RPTHeader::rpt_pdu_header)) {
+      RPTHeader::rpt_pdu_header raw_header;
+      memcpy(&raw_header, data, sizeof(RPTHeader::rpt_pdu_header));
+      RPTHeader header(
+          ola::rdm::UID(raw_header.source_uid),
+          NetworkToHost(raw_header.source_endpoint),
+          ola::rdm::UID(raw_header.destination_uid),
+          NetworkToHost(raw_header.destination_endpoint),
+          NetworkToHost(raw_header.sequence));
+      m_last_header = header;
+      m_last_header_valid = true;
+      headers->SetRPTHeader(header);
+      *bytes_used = sizeof(RPTHeader::rpt_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 RPT Header data";
+    return false;
+  }
+  headers->SetRPTHeader(m_last_header);
+  return true;
+}
+}  // namespace acn
+}  // namespace ola
diff --git a/libs/acn/RPTInflator.h b/libs/acn/RPTInflator.h
new file mode 100644
index 0000000000..1fd4eedaaf
--- /dev/null
+++ b/libs/acn/RPTInflator.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.
+ *
+ * RPTInflator.h
+ * Interface for the RPTInflator class.
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTINFLATOR_H_
+#define LIBS_ACN_RPTINFLATOR_H_
+
+#include "ola/acn/ACNVectors.h"
+#include "libs/acn/BaseInflator.h"
+#include "libs/acn/RPTHeader.h"
+
+namespace ola {
+namespace acn {
+
+class RPTInflator: public BaseInflator {
+  friend class RPTInflatorTest;
+
+ public:
+    RPTInflator()
+        : BaseInflator(),
+          m_last_header_valid(false) {
+    }
+    ~RPTInflator() {}
+
+    uint32_t Id() const { return ola::acn::VECTOR_ROOT_RPT; }
+
+ protected:
+    bool DecodeHeader(HeaderSet *headers,
+                      const uint8_t *data,
+                      unsigned int len,
+                      unsigned int *bytes_used);
+
+    void ResetHeaderField() {
+      m_last_header_valid = false;
+    }
+ private:
+    RPTHeader m_last_header;
+    bool m_last_header_valid;
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTINFLATOR_H_
diff --git a/libs/acn/RPTNotificationInflator.h b/libs/acn/RPTNotificationInflator.h
new file mode 100644
index 0000000000..532fb45a54
--- /dev/null
+++ b/libs/acn/RPTNotificationInflator.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ *
+ * RPTNotificationInflator.h
+ * Interface for the RPTNotificationInflator class.
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTNOTIFICATIONINFLATOR_H_
+#define LIBS_ACN_RPTNOTIFICATIONINFLATOR_H_
+
+#include "ola/acn/ACNVectors.h"
+#include "libs/acn/BaseInflator.h"
+
+namespace ola {
+namespace acn {
+
+class RPTNotificationInflator: public BaseInflator {
+  friend class RPTNotificationInflatorTest;
+
+ public:
+  RPTNotificationInflator()
+    : BaseInflator() {
+  }
+  ~RPTNotificationInflator() {}
+
+  uint32_t Id() const { return ola::acn::VECTOR_RPT_NOTIFICATION; }
+
+ protected:
+  // The 'header' is 0 bytes in length.
+  bool DecodeHeader(HeaderSet*,
+                    const uint8_t*,
+                    unsigned int,
+                    unsigned int *bytes_used) {
+    *bytes_used = 0;
+    return true;
+  }
+
+  void ResetHeaderField() {}  // namespace noop
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTNOTIFICATIONINFLATOR_H_
diff --git a/libs/acn/RPTRequestInflator.h b/libs/acn/RPTRequestInflator.h
new file mode 100644
index 0000000000..ec2cf38de3
--- /dev/null
+++ b/libs/acn/RPTRequestInflator.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ *
+ * RPTRequestInflator.h
+ * Interface for the RPTRequestInflator class.
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTREQUESTINFLATOR_H_
+#define LIBS_ACN_RPTREQUESTINFLATOR_H_
+
+#include "ola/acn/ACNVectors.h"
+#include "libs/acn/BaseInflator.h"
+
+namespace ola {
+namespace acn {
+
+class RPTRequestInflator: public BaseInflator {
+  friend class RPTRequestInflatorTest;
+
+ public:
+  RPTRequestInflator()
+    : BaseInflator() {
+  }
+  ~RPTRequestInflator() {}
+
+  uint32_t Id() const { return ola::acn::VECTOR_RPT_REQUEST; }
+
+ protected:
+  // The 'header' is 0 bytes in length.
+  bool DecodeHeader(HeaderSet*,
+                    const uint8_t*,
+                    unsigned int,
+                    unsigned int *bytes_used) {
+    *bytes_used = 0;
+    return true;
+  }
+
+  void ResetHeaderField() {}  // namespace noop
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTREQUESTINFLATOR_H_

From 65d58c9ad03d4ada3cd8ae50ba5258b772588b37 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 8 Jul 2023 23:38:51 +0100
Subject: [PATCH 06/17] Add tests for the LLRP and RPT headers in the HeaderSet

(cherry picked from commit ec58616c7d5631f479c7c82e1e821c4f4145bd02)
---
 libs/acn/HeaderSetTest.cpp | 76 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/libs/acn/HeaderSetTest.cpp b/libs/acn/HeaderSetTest.cpp
index 18fa7141f5..26623b0c1f 100644
--- a/libs/acn/HeaderSetTest.cpp
+++ b/libs/acn/HeaderSetTest.cpp
@@ -25,6 +25,7 @@
 #include "ola/acn/CID.h"
 #include "ola/network/SocketAddress.h"
 #include "ola/network/NetworkUtils.h"
+#include "ola/rdm/UID.h"
 #include "libs/acn/HeaderSet.h"
 #include "ola/testing/TestUtils.h"
 
@@ -36,11 +37,14 @@ using ola::acn::E131Rev2Header;
 using ola::acn::E133Header;
 using ola::acn::FOUR_BYTES;
 using ola::acn::HeaderSet;
+using ola::acn::LLRPHeader;
 using ola::acn::NON_RANGE;
 using ola::acn::ONE_BYTES;
 using ola::acn::RANGE_EQUAL;
 using ola::acn::RootHeader;
+using ola::acn::RPTHeader;
 using ola::acn::TransportHeader;
+using ola::rdm::UID;
 
 class HeaderSetTest: public CppUnit::TestFixture {
   CPPUNIT_TEST_SUITE(HeaderSetTest);
@@ -49,6 +53,8 @@ class HeaderSetTest: public CppUnit::TestFixture {
   CPPUNIT_TEST(testE131Header);
   CPPUNIT_TEST(testE133Header);
   CPPUNIT_TEST(testDMPHeader);
+  CPPUNIT_TEST(testLLRPHeader);
+  CPPUNIT_TEST(testRPTHeader);
   CPPUNIT_TEST(testHeaderSet);
   CPPUNIT_TEST_SUITE_END();
 
@@ -58,6 +64,8 @@ class HeaderSetTest: public CppUnit::TestFixture {
     void testE131Header();
     void testE133Header();
     void testDMPHeader();
+    void testLLRPHeader();
+    void testRPTHeader();
     void testHeaderSet();
 };
 
@@ -213,6 +221,59 @@ void HeaderSetTest::testDMPHeader() {
 }
 
 
+/*
+ * test the E1.33 LLRP Header
+ */
+void HeaderSetTest::testLLRPHeader() {
+  CID cid = CID::Generate();
+
+  LLRPHeader header(cid, 9840);
+  OLA_ASSERT(cid == header.DestinationCid());
+  OLA_ASSERT_EQ((uint32_t) 9840, header.TransactionNumber());
+
+  // test copy and assign
+  LLRPHeader header2 = header;
+  OLA_ASSERT(header.DestinationCid() == header2.DestinationCid());
+  OLA_ASSERT_EQ(header.TransactionNumber(), header2.TransactionNumber());
+
+  LLRPHeader header3(header);
+  OLA_ASSERT(header.DestinationCid() == header3.DestinationCid());
+  OLA_ASSERT_EQ(header.TransactionNumber(), header3.TransactionNumber());
+  OLA_ASSERT(header == header3);
+}
+
+
+/*
+ * test the RPT Header
+ */
+void HeaderSetTest::testRPTHeader() {
+  UID src(1, 2);
+  UID dest(4, 10);
+  RPTHeader header(src, 3, dest, 5, 9840);
+  OLA_ASSERT(src == header.SourceUID());
+  OLA_ASSERT_EQ((uint16_t) 3, header.SourceEndpoint());
+  OLA_ASSERT(dest == header.DestinationUID());
+  OLA_ASSERT_EQ((uint16_t) 5, header.DestinationEndpoint());
+  OLA_ASSERT_EQ((uint32_t) 9840, header.Sequence());
+
+  // test copy and assign
+  RPTHeader header2 = header;
+  OLA_ASSERT(header.SourceUID() == header2.SourceUID());
+  OLA_ASSERT_EQ(header.SourceEndpoint(), header2.SourceEndpoint());
+  OLA_ASSERT(header.DestinationUID() == header2.DestinationUID());
+  OLA_ASSERT_EQ(header.DestinationEndpoint(), header2.DestinationEndpoint());
+  OLA_ASSERT_EQ(header.Sequence(), header2.Sequence());
+
+  RPTHeader header3(header);
+  OLA_ASSERT(header.SourceUID() == header3.SourceUID());
+  OLA_ASSERT_EQ(header.SourceEndpoint(), header3.SourceEndpoint());
+  OLA_ASSERT(header.DestinationUID() == header3.DestinationUID());
+  OLA_ASSERT_EQ(header.DestinationEndpoint(), header3.DestinationEndpoint());
+  OLA_ASSERT_EQ(header.Sequence(), header3.Sequence());
+  OLA_ASSERT(header == header3);
+}
+
+
 /*
  * Check that the header set works
  */
@@ -222,6 +283,9 @@ void HeaderSetTest::testHeaderSet() {
   E131Header e131_header("e131", 1, 2, 6001);
   E133Header e133_header("foo", 1, 2050);
   DMPHeader dmp_header(false, false, NON_RANGE, ONE_BYTES);
+  CID destination_cid = CID::Generate();
+  LLRPHeader llrp_header(destination_cid, 9840);
+  RPTHeader rpt_header(UID(1, 2), 3, UID(4, 10), 5, 9840);
 
   // test the root header component
   CID cid = CID::Generate();
@@ -241,12 +305,22 @@ void HeaderSetTest::testHeaderSet() {
   headers.SetDMPHeader(dmp_header);
   OLA_ASSERT(dmp_header == headers.GetDMPHeader());
 
+  // test the LLRP headers component
+  headers.SetLLRPHeader(llrp_header);
+  OLA_ASSERT(llrp_header == headers.GetLLRPHeader());
+
+  // test the RPT headers component
+  headers.SetRPTHeader(rpt_header);
+  OLA_ASSERT(rpt_header == headers.GetRPTHeader());
+
   // test assign
   HeaderSet headers2 = headers;
   OLA_ASSERT(root_header == headers2.GetRootHeader());
   OLA_ASSERT(e131_header == headers2.GetE131Header());
   OLA_ASSERT(e133_header == headers2.GetE133Header());
   OLA_ASSERT(dmp_header == headers2.GetDMPHeader());
+  OLA_ASSERT(llrp_header == headers2.GetLLRPHeader());
+  OLA_ASSERT(rpt_header == headers2.GetRPTHeader());
   OLA_ASSERT(headers2 == headers);
 
   // test copy
@@ -255,5 +329,7 @@ void HeaderSetTest::testHeaderSet() {
   OLA_ASSERT(e131_header == headers3.GetE131Header());
   OLA_ASSERT(e133_header == headers3.GetE133Header());
   OLA_ASSERT(dmp_header == headers3.GetDMPHeader());
+  OLA_ASSERT(llrp_header == headers3.GetLLRPHeader());
+  OLA_ASSERT(rpt_header == headers3.GetRPTHeader());
   OLA_ASSERT(headers3 == headers);
 }

From a0df70a004cfa0701be4f12edb3640db3587ee85 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 24 Feb 2024 21:54:18 +0000
Subject: [PATCH 07/17] Update the RDM PDU to the released standard version

(cherry picked from commit 40ff84c7a504bcec7eb472d15fab4b2cbdb713f3)
---
 libs/acn/RDMPDU.cpp     | 2 +-
 libs/acn/RDMPDUTest.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libs/acn/RDMPDU.cpp b/libs/acn/RDMPDU.cpp
index f99779ad16..a816592680 100644
--- a/libs/acn/RDMPDU.cpp
+++ b/libs/acn/RDMPDU.cpp
@@ -47,7 +47,7 @@ void RDMPDU::PackData(ola::io::OutputStream *stream) const {
 void RDMPDU::PrependPDU(ola::io::IOStack *stack) {
   uint8_t vector = HostToNetwork(ola::rdm::START_CODE);
   stack->Write(reinterpret_cast<uint8_t*>(&vector), sizeof(vector));
-  PrependFlagsAndLength(stack);
+  PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true);
 }
 }  // namespace acn
 }  // namespace ola
diff --git a/libs/acn/RDMPDUTest.cpp b/libs/acn/RDMPDUTest.cpp
index ff6489d0fb..f11160f6ba 100644
--- a/libs/acn/RDMPDUTest.cpp
+++ b/libs/acn/RDMPDUTest.cpp
@@ -161,7 +161,7 @@ void RDMPDUTest::testPrepend() {
   uint8_t *buffer = new uint8_t[length];
   OLA_ASSERT(stack.Read(buffer, length));
 
-  const uint8_t expected_data[] = {0x70, 3, TEST_VECTOR};
+  const uint8_t expected_data[] = {0xf0, 0, 4, TEST_VECTOR};
   OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length);
   delete[] buffer;
 }

From 262dc37b8c8ddd41d9b8ca02dd882afa4c8aea35 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 24 Feb 2024 23:12:08 +0000
Subject: [PATCH 08/17] Add the RPT and RPT Request PDU classes

(cherry picked from commit b3942cf1c8dac01026497ef80b920048b7efd21c)
---
 libs/acn/Makefile.mk           |   9 +-
 libs/acn/RPTHeader.h           |   2 +
 libs/acn/RPTInflatorTest.cpp   | 129 ++++++++++++++++++++++++++++
 libs/acn/RPTPDU.cpp            | 132 ++++++++++++++++++++++++++++
 libs/acn/RPTPDU.h              |  65 ++++++++++++++
 libs/acn/RPTPDUTest.cpp        | 152 +++++++++++++++++++++++++++++++++
 libs/acn/RPTRequestPDU.cpp     |  39 +++++++++
 libs/acn/RPTRequestPDU.h       |  56 ++++++++++++
 libs/acn/RPTRequestPDUTest.cpp | 145 +++++++++++++++++++++++++++++++
 9 files changed, 728 insertions(+), 1 deletion(-)
 create mode 100644 libs/acn/RPTInflatorTest.cpp
 create mode 100644 libs/acn/RPTPDU.cpp
 create mode 100644 libs/acn/RPTPDU.h
 create mode 100644 libs/acn/RPTPDUTest.cpp
 create mode 100644 libs/acn/RPTRequestPDU.cpp
 create mode 100644 libs/acn/RPTRequestPDU.h
 create mode 100644 libs/acn/RPTRequestPDUTest.cpp

diff --git a/libs/acn/Makefile.mk b/libs/acn/Makefile.mk
index 45c84e1619..d66b402a72 100644
--- a/libs/acn/Makefile.mk
+++ b/libs/acn/Makefile.mk
@@ -108,7 +108,11 @@ libs_acn_libolae131core_la_SOURCES = \
     libs/acn/RPTInflator.cpp \
     libs/acn/RPTInflator.h \
     libs/acn/RPTNotificationInflator.h \
+    libs/acn/RPTPDU.cpp \
+    libs/acn/RPTPDU.h \
     libs/acn/RPTRequestInflator.h \
+    libs/acn/RPTRequestPDU.cpp \
+    libs/acn/RPTRequestPDU.h \
     libs/acn/TCPTransport.cpp \
     libs/acn/TCPTransport.h \
     libs/acn/Transport.h \
@@ -171,7 +175,10 @@ libs_acn_E133Tester_SOURCES = \
     libs/acn/BrokerPDUTest.cpp \
     libs/acn/E133InflatorTest.cpp \
     libs/acn/E133PDUTest.cpp \
-    libs/acn/RDMPDUTest.cpp
+    libs/acn/RDMPDUTest.cpp \
+    libs/acn/RPTInflatorTest.cpp \
+    libs/acn/RPTPDUTest.cpp \
+    libs/acn/RPTRequestPDUTest.cpp
 libs_acn_E133Tester_CPPFLAGS = $(COMMON_TESTING_FLAGS)
 libs_acn_E133Tester_LDADD = \
     libs/acn/libolae131core.la \
diff --git a/libs/acn/RPTHeader.h b/libs/acn/RPTHeader.h
index 80bb34cb5f..00a26bc349 100644
--- a/libs/acn/RPTHeader.h
+++ b/libs/acn/RPTHeader.h
@@ -60,6 +60,7 @@ class RPTHeader {
   uint16_t SourceEndpoint() const { return m_source_endpoint; }
   ola::rdm::UID DestinationUID() const { return m_destination_uid; }
   uint16_t DestinationEndpoint() const { return m_destination_endpoint; }
+  // TODO(Peter): Should this be SequenceNumber?
   // TODO(Peter): Should the sequence number really be part of the header?
   uint32_t Sequence() const { return m_sequence; }
 
@@ -73,6 +74,7 @@ class RPTHeader {
 
   PACK(
   struct rpt_pdu_header_s {
+    rpt_pdu_header_s() : reserved(0) {}
     uint8_t source_uid[ola::rdm::UID::LENGTH];
     uint16_t source_endpoint;
     uint8_t destination_uid[ola::rdm::UID::LENGTH];
diff --git a/libs/acn/RPTInflatorTest.cpp b/libs/acn/RPTInflatorTest.cpp
new file mode 100644
index 0000000000..931c78b062
--- /dev/null
+++ b/libs/acn/RPTInflatorTest.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ *
+ * RPTInflatorTest.cpp
+ * Test fixture for the RPTInflator class
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "ola/Logging.h"
+#include "ola/network/NetworkUtils.h"
+#include "ola/rdm/UID.h"
+#include "libs/acn/HeaderSet.h"
+#include "libs/acn/PDUTestCommon.h"
+#include "libs/acn/RPTInflator.h"
+#include "libs/acn/RPTPDU.h"
+#include "ola/testing/TestUtils.h"
+
+
+namespace ola {
+namespace acn {
+
+using ola::network::HostToNetwork;
+using ola::rdm::UID;
+
+class RPTInflatorTest: public CppUnit::TestFixture {
+  CPPUNIT_TEST_SUITE(RPTInflatorTest);
+  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 RPTInflatorTest::TEST_DATA[] = {0, 1, 2, 3, 4, 5};
+const uint8_t RPTInflatorTest::TEST_DATA2[] = {10, 11, 12, 13, 14, 15};
+CPPUNIT_TEST_SUITE_REGISTRATION(RPTInflatorTest);
+
+
+/*
+ * Check that we can decode headers properly
+ */
+void RPTInflatorTest::testDecodeHeader() {
+  RPTHeader::rpt_pdu_header header;
+  memset(&header, 0, sizeof(header));
+  RPTInflator inflator;
+  HeaderSet header_set, header_set2;
+  unsigned int bytes_used;
+  const ola::rdm::UID source_uid = UID(TEST_DATA);
+  const ola::rdm::UID destination_uid = UID(TEST_DATA2);
+
+  source_uid.Pack(header.source_uid, sizeof(header.source_uid));
+  header.source_endpoint = HostToNetwork((uint16_t) 1234u);
+  destination_uid.Pack(header.destination_uid, sizeof(header.destination_uid));
+  header.destination_endpoint = HostToNetwork((uint16_t) 5678u);
+  header.sequence = HostToNetwork(72650u);
+
+  OLA_ASSERT(inflator.DecodeHeader(&header_set,
+                                   reinterpret_cast<uint8_t*>(&header),
+                                   sizeof(header),
+                                   &bytes_used));
+  OLA_ASSERT_EQ((unsigned int) sizeof(header), bytes_used);
+  RPTHeader decoded_header = header_set.GetRPTHeader();
+
+  // try an undersized header
+  OLA_ASSERT_FALSE(inflator.DecodeHeader(
+      &header_set,
+      reinterpret_cast<uint8_t*>(&header),
+      static_cast<unsigned int>(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.GetRPTHeader();
+  OLA_ASSERT(source_uid == decoded_header.SourceUID());
+  OLA_ASSERT_EQ((uint16_t) 1234, decoded_header.SourceEndpoint());
+  OLA_ASSERT(destination_uid == decoded_header.DestinationUID());
+  OLA_ASSERT_EQ((uint16_t) 5678, decoded_header.DestinationEndpoint());
+  OLA_ASSERT_EQ((uint32_t) 72650, decoded_header.Sequence());
+
+  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 RPT PDU that contains other PDUs
+ */
+void RPTInflatorTest::testInflatePDU() {
+  RPTHeader header(UID(1, 2), 42, UID(10, 20), 99, 2370);
+  // TODO(Peter): pass a different type of msg here as well
+  RPTPDU pdu(3, header, NULL);
+  OLA_ASSERT_EQ((unsigned int) 28, 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);
+
+  RPTInflator inflator;
+  HeaderSet header_set;
+  OLA_ASSERT(inflator.InflatePDUBlock(&header_set, data, size));
+  OLA_ASSERT(header == header_set.GetRPTHeader());
+  delete[] data;
+}
+}  // namespace acn
+}  // namespace ola
diff --git a/libs/acn/RPTPDU.cpp b/libs/acn/RPTPDU.cpp
new file mode 100644
index 0000000000..f7eaf0b422
--- /dev/null
+++ b/libs/acn/RPTPDU.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ *
+ * RPTPDU.cpp
+ * The E1.33 RPT PDU
+ * Copyright (C) 2024 Peter Newman
+ */
+
+
+#include "ola/Logging.h"
+#include "ola/base/Array.h"
+#include "ola/network/NetworkUtils.h"
+#include "libs/acn/RPTPDU.h"
+
+namespace ola {
+namespace acn {
+
+using ola::io::OutputStream;
+using ola::network::HostToNetwork;
+
+/*
+ * Size of the header portion.
+ */
+unsigned int RPTPDU::HeaderSize() const {
+  return sizeof(RPTHeader::rpt_pdu_header);
+}
+
+
+/*
+ * Size of the data portion
+ */
+unsigned int RPTPDU::DataSize() const {
+  return m_pdu ? m_pdu->Size() : 0;
+}
+
+
+/*
+ * Pack the header portion.
+ */
+bool RPTPDU::PackHeader(uint8_t *data, unsigned int *length) const {
+  unsigned int header_size = HeaderSize();
+
+  if (*length < header_size) {
+    OLA_WARN << "RPTPDU::PackHeader: buffer too small, got " << *length
+             << " required " << header_size;
+    *length = 0;
+    return false;
+  }
+
+  RPTHeader::rpt_pdu_header header;
+  m_header.SourceUID().Pack(header.source_uid, sizeof(header.source_uid));
+  header.source_endpoint = HostToNetwork(m_header.SourceEndpoint());
+  m_header.DestinationUID().Pack(header.destination_uid,
+                                 sizeof(header.destination_uid));
+  header.destination_endpoint = HostToNetwork(m_header.DestinationEndpoint());
+  header.sequence = HostToNetwork(m_header.Sequence());
+  *length = sizeof(RPTHeader::rpt_pdu_header);
+  memcpy(data, &header, *length);
+  return true;
+}
+
+
+/*
+ * Pack the data portion.
+ */
+bool RPTPDU::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 RPTPDU::PackHeader(OutputStream *stream) const {
+  RPTHeader::rpt_pdu_header header;
+  m_header.SourceUID().Pack(header.source_uid, sizeof(header.source_uid));
+  header.source_endpoint = HostToNetwork(m_header.SourceEndpoint());
+  m_header.DestinationUID().Pack(header.destination_uid,
+                                 sizeof(header.destination_uid));
+  header.destination_endpoint = HostToNetwork(m_header.DestinationEndpoint());
+  header.sequence = HostToNetwork(m_header.Sequence());
+  stream->Write(reinterpret_cast<uint8_t*>(&header),
+                sizeof(RPTHeader::rpt_pdu_header));
+}
+
+
+/*
+ * Pack the data into a buffer
+ */
+void RPTPDU::PackData(OutputStream *stream) const {
+  if (m_pdu)
+    m_pdu->Write(stream);
+}
+
+
+void RPTPDU::PrependPDU(ola::io::IOStack *stack,
+                        uint32_t vector,
+                        const ola::rdm::UID &source_uid,
+                        uint16_t source_endpoint,
+                        const ola::rdm::UID &destination_uid,
+                        uint16_t destination_endpoint,
+                        uint32_t sequence_number) {
+  RPTHeader::rpt_pdu_header header;
+  source_uid.Pack(header.source_uid, sizeof(header.source_uid));
+  header.source_endpoint = HostToNetwork(source_endpoint);
+  destination_uid.Pack(header.destination_uid, sizeof(header.destination_uid));
+  header.destination_endpoint = HostToNetwork(destination_endpoint);
+  header.sequence = HostToNetwork(sequence_number);
+  stack->Write(reinterpret_cast<uint8_t*>(&header),
+               sizeof(RPTHeader::rpt_pdu_header));
+
+  vector = HostToNetwork(vector);
+  stack->Write(reinterpret_cast<uint8_t*>(&vector), sizeof(vector));
+  PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true);
+}
+}  // namespace acn
+}  // namespace ola
diff --git a/libs/acn/RPTPDU.h b/libs/acn/RPTPDU.h
new file mode 100644
index 0000000000..eb04809915
--- /dev/null
+++ b/libs/acn/RPTPDU.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ *
+ * RPTPDU.h
+ * Interface for the E1.33 RPTPDU class
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTPDU_H_
+#define LIBS_ACN_RPTPDU_H_
+
+#include <ola/io/IOStack.h>
+#include <ola/rdm/UID.h>
+
+#include "libs/acn/PDU.h"
+#include "libs/acn/RPTHeader.h"
+
+namespace ola {
+namespace acn {
+
+class RPTPDU: public PDU {
+ public:
+    RPTPDU(unsigned int vector,
+           const RPTHeader &header,
+           const PDU *pdu):
+      PDU(vector, FOUR_BYTES, true),
+      m_header(header),
+      m_pdu(pdu) {}
+    ~RPTPDU() {}
+
+    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::rdm::UID &source_uid,
+                           uint16_t source_endpoint,
+                           const ola::rdm::UID &destination_uid,
+                           uint16_t destination_endpoint,
+                           uint32_t sequence_number);
+
+ private:
+    RPTHeader m_header;
+    const PDU *m_pdu;
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTPDU_H_
diff --git a/libs/acn/RPTPDUTest.cpp b/libs/acn/RPTPDUTest.cpp
new file mode 100644
index 0000000000..075b2b52e3
--- /dev/null
+++ b/libs/acn/RPTPDUTest.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ *
+ * RPTPDUTest.cpp
+ * Test fixture for the E1.33 RPTPDU class
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "ola/Logging.h"
+#include "ola/io/IOQueue.h"
+#include "ola/io/OutputStream.h"
+#include "ola/network/NetworkUtils.h"
+#include "ola/rdm/UID.h"
+#include "ola/testing/TestUtils.h"
+#include "libs/acn/RPTPDU.h"
+#include "libs/acn/PDUTestCommon.h"
+
+
+namespace ola {
+namespace acn {
+
+using ola::io::IOQueue;
+using ola::io::OutputStream;
+using ola::network::HostToNetwork;
+using ola::rdm::UID;
+
+class RPTPDUTest: public CppUnit::TestFixture {
+  CPPUNIT_TEST_SUITE(RPTPDUTest);
+  CPPUNIT_TEST(testSimpleRPTPDU);
+  CPPUNIT_TEST(testSimpleRPTPDUToOutputStream);
+  CPPUNIT_TEST_SUITE_END();
+
+ public:
+  void testSimpleRPTPDU();
+  void testSimpleRPTPDUToOutputStream();
+
+  void setUp() {
+    ola::InitLogging(ola::OLA_LOG_DEBUG, ola::OLA_LOG_STDERR);
+  }
+
+ private:
+  static const unsigned int TEST_VECTOR;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RPTPDUTest);
+
+const unsigned int RPTPDUTest::TEST_VECTOR = 39;
+
+
+/*
+ * Test that packing a RPTPDU without data works.
+ */
+void RPTPDUTest::testSimpleRPTPDU() {
+  UID source_uid(1, 2);
+  UID destination_uid(10, 20);
+  RPTHeader header(source_uid, 42, destination_uid, 99, 2370);
+  RPTPDU pdu(TEST_VECTOR, header, NULL);
+
+  OLA_ASSERT_EQ(21u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(28u, 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[UID::LENGTH];
+  source_uid.Pack(buffer, sizeof(buffer));
+  OLA_ASSERT_DATA_EQUALS(&data[7], UID::LENGTH, buffer, sizeof(buffer));
+  destination_uid.Pack(buffer, sizeof(buffer));
+  OLA_ASSERT_DATA_EQUALS(&data[15], UID::LENGTH, buffer, sizeof(buffer));
+  // sequence number
+  OLA_ASSERT_EQ((uint8_t) 0, data[23]);
+  OLA_ASSERT_EQ((uint8_t) 0, data[23 + 1]);
+  OLA_ASSERT_EQ((unsigned int) 9, (unsigned int) data[23 + 2]);
+  OLA_ASSERT_EQ((unsigned int) 66, (unsigned int) data[23 + 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 RPTPDUTest::testSimpleRPTPDUToOutputStream() {
+  RPTHeader header(UID(0x0102, 0x03040506), 3456,
+                   UID(0x4050, 0x60708090), 7890,
+                   2370);
+  RPTPDU pdu(TEST_VECTOR, header, NULL);
+
+  OLA_ASSERT_EQ(21u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(28u, pdu.Size());
+
+  IOQueue output;
+  OutputStream stream(&output);
+  pdu.Write(&stream);
+  OLA_ASSERT_EQ(28u, 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, 0x1c,
+    0, 0, 0, 39,
+    1, 2, 3, 4, 5, 6,  // source UID
+    13, 128,  // source endpoint
+    64, 80, 96, 112, 128, 144,  // dest UID
+    30, 210,  // dest endpoint
+    0, 0, 9, 66,  // sequence number
+    0  // reserved
+  };
+  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/RPTRequestPDU.cpp b/libs/acn/RPTRequestPDU.cpp
new file mode 100644
index 0000000000..577e382b11
--- /dev/null
+++ b/libs/acn/RPTRequestPDU.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *
+ * RPTRequestPDU.cpp
+ * The RPTRequestPDU
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#include "libs/acn/RPTRequestPDU.h"
+
+#include <ola/network/NetworkUtils.h>
+#include <ola/acn/ACNVectors.h>
+
+namespace ola {
+namespace acn {
+
+using ola::io::OutputStream;
+using ola::network::HostToNetwork;
+
+void RPTRequestPDU::PrependPDU(ola::io::IOStack *stack) {
+  uint32_t vector = HostToNetwork(
+      static_cast<uint32_t>(VECTOR_REQUEST_RDM_CMD));
+  stack->Write(reinterpret_cast<uint8_t*>(&vector), sizeof(vector));
+  PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true);
+}
+}  // namespace acn
+}  // namespace ola
diff --git a/libs/acn/RPTRequestPDU.h b/libs/acn/RPTRequestPDU.h
new file mode 100644
index 0000000000..97a490cd44
--- /dev/null
+++ b/libs/acn/RPTRequestPDU.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ * RPTRequestPDU.h
+ * The RPTRequestPDU class
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#ifndef LIBS_ACN_RPTREQUESTPDU_H_
+#define LIBS_ACN_RPTREQUESTPDU_H_
+
+#include <ola/io/IOStack.h>
+
+#include "libs/acn/PDU.h"
+
+namespace ola {
+namespace acn {
+
+class RPTRequestPDU : public PDU {
+ public:
+  explicit RPTRequestPDU(unsigned int vector):
+    PDU(vector, FOUR_BYTES, true) {}
+
+  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 0; }
+  bool PackData(OLA_UNUSED uint8_t *data,
+                unsigned int *length) const {
+    *length = 0;
+    return true;
+  }
+  void PackData(OLA_UNUSED ola::io::OutputStream *stream) const {}
+
+  static void PrependPDU(ola::io::IOStack *stack);
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_RPTREQUESTPDU_H_
diff --git a/libs/acn/RPTRequestPDUTest.cpp b/libs/acn/RPTRequestPDUTest.cpp
new file mode 100644
index 0000000000..d04b88712b
--- /dev/null
+++ b/libs/acn/RPTRequestPDUTest.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ *
+ * RPTRequestPDUTest.cpp
+ * Test fixture for the RPTRequestPDU class
+ * Copyright (C) 2024 Peter Newman
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <string.h>
+#include <string>
+
+#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/testing/TestUtils.h"
+#include "libs/acn/PDUTestCommon.h"
+#include "libs/acn/RPTRequestPDU.h"
+
+namespace ola {
+namespace acn {
+
+using ola::acn::RPTRequestPDU;
+using ola::io::IOQueue;
+using ola::io::IOStack;
+using ola::io::OutputStream;
+using ola::network::HostToNetwork;
+
+class RPTRequestPDUTest: public CppUnit::TestFixture {
+  CPPUNIT_TEST_SUITE(RPTRequestPDUTest);
+  CPPUNIT_TEST(testSimpleRPTRequestPDU);
+  CPPUNIT_TEST(testSimpleRPTRequestPDUToOutputStream);
+  CPPUNIT_TEST(testPrepend);
+  CPPUNIT_TEST_SUITE_END();
+
+ public:
+  void testSimpleRPTRequestPDU();
+  void testSimpleRPTRequestPDUToOutputStream();
+  void testPrepend();
+
+ private:
+  static const uint32_t TEST_VECTOR;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RPTRequestPDUTest);
+
+const uint32_t RPTRequestPDUTest::TEST_VECTOR = 39;
+
+
+/*
+ * Test that packing a RPTRequestPDU works.
+ */
+void RPTRequestPDUTest::testSimpleRPTRequestPDU() {
+  RPTRequestPDU pdu(TEST_VECTOR);
+
+  OLA_ASSERT_EQ(0u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(7u, 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 part of data[0-2] if > 255
+  OLA_ASSERT_EQ((uint8_t) bytes_used, data[2]);
+  uint32_t actual_value;
+  memcpy(&actual_value, data + 3, sizeof(actual_value));
+  OLA_ASSERT_EQ(HostToNetwork(TEST_VECTOR), actual_value);
+
+  // 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 RPTRequestPDUTest::testSimpleRPTRequestPDUToOutputStream() {
+  RPTRequestPDU pdu(TEST_VECTOR);
+
+  OLA_ASSERT_EQ(0u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(7u, pdu.Size());
+
+  IOQueue output;
+  OutputStream stream(&output);
+  pdu.Write(&stream);
+  OLA_ASSERT_EQ(7u, 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, 0x07,
+    0, 0, 0, 39
+  };
+  OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size);
+  output.Pop(output.Size());
+  delete[] pdu_data;
+}
+
+
+void RPTRequestPDUTest::testPrepend() {
+  IOStack stack;
+  RPTRequestPDU::PrependPDU(&stack);
+
+  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, 0x07,
+    0, 0, 0, 0x01
+  };
+  OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length);
+  delete[] buffer;
+}
+}  // namespace acn
+}  // namespace ola

From 5dabc8b464c2651cc6d5e0e3c610afd097944eba Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 24 Feb 2024 23:14:06 +0000
Subject: [PATCH 09/17] Tidy the whitespace on RPTPDU.h

(cherry picked from commit 218c4c7cd836cd345d1619c8a2f6b1ea5055b788)
---
 libs/acn/RPTPDU.h | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/libs/acn/RPTPDU.h b/libs/acn/RPTPDU.h
index eb04809915..c359855402 100644
--- a/libs/acn/RPTPDU.h
+++ b/libs/acn/RPTPDU.h
@@ -32,33 +32,33 @@ namespace acn {
 
 class RPTPDU: public PDU {
  public:
-    RPTPDU(unsigned int vector,
-           const RPTHeader &header,
-           const PDU *pdu):
-      PDU(vector, FOUR_BYTES, true),
-      m_header(header),
-      m_pdu(pdu) {}
-    ~RPTPDU() {}
+  RPTPDU(unsigned int vector,
+         const RPTHeader &header,
+         const PDU *pdu):
+    PDU(vector, FOUR_BYTES, true),
+    m_header(header),
+    m_pdu(pdu) {}
+  ~RPTPDU() {}
 
-    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;
+  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;
+  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::rdm::UID &source_uid,
-                           uint16_t source_endpoint,
-                           const ola::rdm::UID &destination_uid,
-                           uint16_t destination_endpoint,
-                           uint32_t sequence_number);
+  static void PrependPDU(ola::io::IOStack *stack,
+                         uint32_t vector,
+                         const ola::rdm::UID &source_uid,
+                         uint16_t source_endpoint,
+                         const ola::rdm::UID &destination_uid,
+                         uint16_t destination_endpoint,
+                         uint32_t sequence_number);
 
  private:
-    RPTHeader m_header;
-    const PDU *m_pdu;
+  RPTHeader m_header;
+  const PDU *m_pdu;
 };
 }  // namespace acn
 }  // namespace ola

From 8af9777ed5953dd237d04f9309c2ea9774b89b34 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Wed, 28 Feb 2024 17:48:51 +0000
Subject: [PATCH 10/17] Add the BrokerFetchClientListPDU class

(cherry picked from commit e00322deed9baf2f4e9a015c1790fbf58f0a4cde)
---
 libs/acn/BrokerFetchClientListPDU.cpp     |  38 ++++++
 libs/acn/BrokerFetchClientListPDU.h       |  56 ++++++++
 libs/acn/BrokerFetchClientListPDUTest.cpp | 148 ++++++++++++++++++++++
 libs/acn/Makefile.mk                      |   3 +
 4 files changed, 245 insertions(+)
 create mode 100644 libs/acn/BrokerFetchClientListPDU.cpp
 create mode 100644 libs/acn/BrokerFetchClientListPDU.h
 create mode 100644 libs/acn/BrokerFetchClientListPDUTest.cpp

diff --git a/libs/acn/BrokerFetchClientListPDU.cpp b/libs/acn/BrokerFetchClientListPDU.cpp
new file mode 100644
index 0000000000..99e7f866e3
--- /dev/null
+++ b/libs/acn/BrokerFetchClientListPDU.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *
+ * BrokerFetchClientListPDU.cpp
+ * The BrokerFetchClientListPDU
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#include "libs/acn/BrokerFetchClientListPDU.h"
+
+#include <ola/network/NetworkUtils.h>
+#include <ola/acn/ACNVectors.h>
+
+namespace ola {
+namespace acn {
+
+using ola::network::HostToNetwork;
+
+void BrokerFetchClientListPDU::PrependPDU(ola::io::IOStack *stack) {
+  uint16_t vector = HostToNetwork(static_cast<uint16_t>(
+      VECTOR_BROKER_FETCH_CLIENT_LIST));
+  stack->Write(reinterpret_cast<uint8_t*>(&vector), sizeof(vector));
+  PrependFlagsAndLength(stack, VFLAG_MASK | HFLAG_MASK | DFLAG_MASK, true);
+}
+}  // namespace acn
+}  // namespace ola
diff --git a/libs/acn/BrokerFetchClientListPDU.h b/libs/acn/BrokerFetchClientListPDU.h
new file mode 100644
index 0000000000..df4a40e4f0
--- /dev/null
+++ b/libs/acn/BrokerFetchClientListPDU.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ * BrokerFetchClientListPDU.h
+ * The BrokerFetchClientListPDU class
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#ifndef LIBS_ACN_BROKERFETCHCLIENTLISTPDU_H_
+#define LIBS_ACN_BROKERFETCHCLIENTLISTPDU_H_
+
+#include <ola/io/IOStack.h>
+
+#include "libs/acn/PDU.h"
+
+namespace ola {
+namespace acn {
+
+class BrokerFetchClientListPDU : public PDU {
+ public:
+  explicit BrokerFetchClientListPDU(unsigned int vector):
+    PDU(vector, TWO_BYTES, true) {}
+
+  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 0; }
+  bool PackData(OLA_UNUSED uint8_t *data,
+                unsigned int *length) const {
+    *length = 0;
+    return true;
+  }
+  void PackData(OLA_UNUSED ola::io::OutputStream *stream) const {}
+
+  static void PrependPDU(ola::io::IOStack *stack);
+};
+}  // namespace acn
+}  // namespace ola
+#endif  // LIBS_ACN_BROKERFETCHCLIENTLISTPDU_H_
diff --git a/libs/acn/BrokerFetchClientListPDUTest.cpp b/libs/acn/BrokerFetchClientListPDUTest.cpp
new file mode 100644
index 0000000000..00a5ad09cf
--- /dev/null
+++ b/libs/acn/BrokerFetchClientListPDUTest.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ *
+ * BrokerFetchClientListPDUTest.cpp
+ * Test fixture for the BrokerFetchClientListPDU class
+ * Copyright (C) 2023 Peter Newman
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#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/testing/TestUtils.h"
+#include "libs/acn/PDUTestCommon.h"
+#include "libs/acn/BrokerFetchClientListPDU.h"
+
+namespace ola {
+namespace acn {
+
+using ola::acn::BrokerFetchClientListPDU;
+using ola::io::IOQueue;
+using ola::io::IOStack;
+using ola::io::OutputStream;
+using ola::network::HostToNetwork;
+
+class BrokerFetchClientListPDUTest: public CppUnit::TestFixture {
+  CPPUNIT_TEST_SUITE(BrokerFetchClientListPDUTest);
+  CPPUNIT_TEST(testSimpleBrokerFetchClientListPDU);
+  CPPUNIT_TEST(testSimpleBrokerFetchClientListPDUToOutputStream);
+  CPPUNIT_TEST(testPrepend);
+  CPPUNIT_TEST_SUITE_END();
+
+ public:
+  void testSimpleBrokerFetchClientListPDU();
+  void testSimpleBrokerFetchClientListPDUToOutputStream();
+  void testPrepend();
+
+  void setUp() {
+    ola::InitLogging(ola::OLA_LOG_DEBUG, ola::OLA_LOG_STDERR);
+  }
+
+ private:
+  static const uint16_t TEST_VECTOR;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BrokerFetchClientListPDUTest);
+
+const uint16_t BrokerFetchClientListPDUTest::TEST_VECTOR = 39;
+
+
+/*
+ * Test that packing a BrokerFetchClientListPDU works.
+ */
+void BrokerFetchClientListPDUTest::testSimpleBrokerFetchClientListPDU() {
+  BrokerFetchClientListPDU pdu(TEST_VECTOR);
+
+  OLA_ASSERT_EQ(0u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(5u, 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]);
+  uint16_t actual_value;
+  memcpy(&actual_value, data + 3, sizeof(actual_value));
+  OLA_ASSERT_EQ(HostToNetwork(TEST_VECTOR), actual_value);
+
+  // 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 BrokerFetchClientListPDUTest::
+    testSimpleBrokerFetchClientListPDUToOutputStream() {
+  BrokerFetchClientListPDU pdu(TEST_VECTOR);
+
+  OLA_ASSERT_EQ(0u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(0u, pdu.DataSize());
+  OLA_ASSERT_EQ(5u, pdu.Size());
+
+  IOQueue output;
+  OutputStream stream(&output);
+  pdu.Write(&stream);
+  OLA_ASSERT_EQ(5u, 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, 0x05,
+    0, 39
+  };
+  OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), pdu_data, pdu_size);
+  output.Pop(output.Size());
+  delete[] pdu_data;
+}
+
+
+void BrokerFetchClientListPDUTest::testPrepend() {
+  IOStack stack;
+  BrokerFetchClientListPDU::PrependPDU(&stack);
+
+  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, 0x05,
+    0, 0x06
+  };
+  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 d66b402a72..37c8d999f8 100644
--- a/libs/acn/Makefile.mk
+++ b/libs/acn/Makefile.mk
@@ -39,6 +39,8 @@ libs_acn_libolae131core_la_SOURCES = \
     libs/acn/BrokerClientRemoveInflator.h \
     libs/acn/BrokerConnectPDU.cpp \
     libs/acn/BrokerConnectPDU.h \
+    libs/acn/BrokerFetchClientListPDU.cpp \
+    libs/acn/BrokerFetchClientListPDU.h \
     libs/acn/BrokerInflator.h \
     libs/acn/BrokerNullInflator.h \
     libs/acn/BrokerNullPDU.cpp \
@@ -171,6 +173,7 @@ libs_acn_E131Tester_LDADD = \
 libs_acn_E133Tester_SOURCES = \
     libs/acn/BrokerClientEntryPDUTest.cpp \
     libs/acn/BrokerConnectPDUTest.cpp \
+    libs/acn/BrokerFetchClientListPDUTest.cpp \
     libs/acn/BrokerNullPDUTest.cpp \
     libs/acn/BrokerPDUTest.cpp \
     libs/acn/E133InflatorTest.cpp \

From eab5997c4feb3995eb47597e2b73b2c15a34c87b Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Wed, 28 Feb 2024 17:50:53 +0000
Subject: [PATCH 11/17] Add message builders for RDMCommand and
 BrokerFetchClientList packets

(cherry picked from commit 16455b1db66c9d157aade362a7142b0c0558bcfc)
---
 include/ola/e133/MessageBuilder.h |  9 ++++++++
 tools/e133/MessageBuilder.cpp     | 34 +++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/include/ola/e133/MessageBuilder.h b/include/ola/e133/MessageBuilder.h
index a94d52200c..ace419b40f 100644
--- a/include/ola/e133/MessageBuilder.h
+++ b/include/ola/e133/MessageBuilder.h
@@ -27,6 +27,7 @@
 #include <ola/e133/E133Enums.h>
 #include <ola/io/IOStack.h>
 #include <ola/io/MemoryBlockPool.h>
+#include <ola/rdm/RDMCommand.h>
 #include <string>
 
 namespace ola {
@@ -46,8 +47,16 @@ class MessageBuilder {
 
     void PrependRDMHeader(IOStack *packet);
 
+    void BuildTCPRDMCommandPDU(IOStack *packet,
+                               ola::rdm::RDMRequest *request,
+                               uint16_t source_endpoint_id,
+                               uint16_t destination_endpoint_id,
+                               uint32_t sequence_number);
+
     void BuildNullTCPPacket(IOStack *packet);
 
+    void BuildBrokerFetchClientListTCPPacket(IOStack *packet);
+
     void BuildBrokerNullTCPPacket(IOStack *packet);
 
     void BuildTCPE133StatusPDU(IOStack *packet,
diff --git a/tools/e133/MessageBuilder.cpp b/tools/e133/MessageBuilder.cpp
index eed3b85961..41d352539b 100644
--- a/tools/e133/MessageBuilder.cpp
+++ b/tools/e133/MessageBuilder.cpp
@@ -24,11 +24,14 @@
 #include "ola/acn/CID.h"
 #include "ola/e133/MessageBuilder.h"
 #include "ola/io/IOStack.h"
+#include "ola/rdm/RDMCommandSerializer.h"
 
 #include "libs/acn/BrokerPDU.h"
 #include "libs/acn/E133PDU.h"
 #include "libs/acn/RDMPDU.h"
 #include "libs/acn/RootPDU.h"
+#include "libs/acn/RPTPDU.h"
+#include "libs/acn/RPTRequestPDU.h"
 #include "libs/acn/E133StatusPDU.h"
 #include "libs/acn/PreamblePacker.h"
 
@@ -41,6 +44,7 @@ using ola::acn::BrokerPDU;
 using ola::acn::E133PDU;
 using ola::acn::PreamblePacker;
 using ola::acn::RootPDU;
+using ola::acn::RPTPDU;
 
 
 MessageBuilder::MessageBuilder(const CID &cid, const string &source_name)
@@ -60,6 +64,26 @@ void MessageBuilder::PrependRDMHeader(IOStack *packet) {
 }
 
 
+/**
+ * Build a TCP E1.33 RDM Command PDU response.
+ */
+void MessageBuilder::BuildTCPRDMCommandPDU(IOStack *packet,
+                                           ola::rdm::RDMRequest *request,
+                                           uint16_t source_endpoint_id,
+                                           uint16_t destination_endpoint_id,
+                                           uint32_t sequence_number) {
+  ola::rdm::RDMCommandSerializer::Write(*request, packet);
+  ola::acn::RDMPDU::PrependPDU(packet);
+  ola::acn::RPTRequestPDU::PrependPDU(packet);
+  RPTPDU::PrependPDU(packet, ola::acn::VECTOR_RPT_REQUEST,
+                     request->SourceUID(), source_endpoint_id,
+                     request->DestinationUID(), destination_endpoint_id,
+                     sequence_number);
+  RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_RPT, m_cid, true);
+  PreamblePacker::AddTCPPreamble(packet);
+}
+
+
 /**
  * Build a NULL TCP packet. These packets can be used for heartbeats.
  */
@@ -69,6 +93,16 @@ void MessageBuilder::BuildNullTCPPacket(IOStack *packet) {
 }
 
 
+/**
+ * Build a Broker Fetch Client List TCP packet.
+ */
+void MessageBuilder::BuildBrokerFetchClientListTCPPacket(IOStack *packet) {
+  BrokerPDU::PrependPDU(packet, ola::acn::VECTOR_BROKER_FETCH_CLIENT_LIST);
+  RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_BROKER, m_cid, true);
+  PreamblePacker::AddTCPPreamble(packet);
+}
+
+
 /**
  * Build a Broker NULL TCP packet. These packets can be used for broker heartbeats.
  */

From 58ae321b263c87a64eb42152bb0191e791594b37 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Thu, 28 Mar 2024 17:39:28 +0000
Subject: [PATCH 12/17] Try and fix the clearing an object of non-trivial type
 error

---
 libs/acn/RPTInflatorTest.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libs/acn/RPTInflatorTest.cpp b/libs/acn/RPTInflatorTest.cpp
index 931c78b062..f349e53eef 100644
--- a/libs/acn/RPTInflatorTest.cpp
+++ b/libs/acn/RPTInflatorTest.cpp
@@ -60,7 +60,9 @@ CPPUNIT_TEST_SUITE_REGISTRATION(RPTInflatorTest);
  */
 void RPTInflatorTest::testDecodeHeader() {
   RPTHeader::rpt_pdu_header header;
-  memset(&header, 0, sizeof(header));
+  // Need to cast the header before we memset as it's got a constructor to
+  // ensure the reserved value is set to zero by default
+  memset(reinterpret_cast<uint8_t*>(&header), 0, sizeof(header));
   RPTInflator inflator;
   HeaderSet header_set, header_set2;
   unsigned int bytes_used;

From da414b91a5080946dbb1ad5ddee7be049cd23096 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Fri, 5 Apr 2024 21:59:09 +0100
Subject: [PATCH 13/17] Fix some comments

(cherry picked from commit 2bfeb5510825c29071c88421298ccb9251834b30)
---
 libs/acn/RPTPDU.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/libs/acn/RPTPDU.cpp b/libs/acn/RPTPDU.cpp
index f7eaf0b422..1cbd35eb95 100644
--- a/libs/acn/RPTPDU.cpp
+++ b/libs/acn/RPTPDU.cpp
@@ -76,8 +76,9 @@ bool RPTPDU::PackHeader(uint8_t *data, unsigned int *length) const {
  * Pack the data portion.
  */
 bool RPTPDU::PackData(uint8_t *data, unsigned int *length) const {
-  if (m_pdu)
+  if (m_pdu) {
     return m_pdu->Pack(data, length);
+  }
   *length = 0;
   return true;
 }
@@ -103,8 +104,9 @@ void RPTPDU::PackHeader(OutputStream *stream) const {
  * Pack the data into a buffer
  */
 void RPTPDU::PackData(OutputStream *stream) const {
-  if (m_pdu)
+  if (m_pdu) {
     m_pdu->Write(stream);
+  }
 }
 
 

From d6a13dd9218c4f335905318b200462ac2218ed2d Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 6 Apr 2024 00:34:52 +0100
Subject: [PATCH 14/17] Fix some null pointer exceptions

(cherry picked from commit 14d56c91545ff672ab2055a2915310a0a9f5ecad)
---
 libs/acn/PDU.cpp     | 10 ++++++++
 libs/acn/PDU.h       | 11 +++++++++
 libs/acn/PDUTest.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 76 insertions(+)

diff --git a/libs/acn/PDU.cpp b/libs/acn/PDU.cpp
index 0f256eca13..33e40f7205 100644
--- a/libs/acn/PDU.cpp
+++ b/libs/acn/PDU.cpp
@@ -50,6 +50,16 @@ unsigned int PDU::Size() const {
  * @return false on error, true otherwise
  */
 bool PDU::Pack(uint8_t *buffer, unsigned int *length) const {
+  if (!buffer) {
+    OLA_WARN << "PDU::Pack: missing buffer";
+    return false;
+  }
+
+  if (!length) {
+    OLA_WARN << "PDU::Pack: missing length";
+    return false;
+  }
+
   unsigned int size = Size();
   unsigned int offset = 0;
 
diff --git a/libs/acn/PDU.h b/libs/acn/PDU.h
index 2be285dead..531a8e96b5 100644
--- a/libs/acn/PDU.h
+++ b/libs/acn/PDU.h
@@ -22,6 +22,7 @@
 #define LIBS_ACN_PDU_H_
 
 #include <stdint.h>
+#include <ola/Logging.h>
 #include <ola/acn/ACNFlags.h>
 #include <ola/io/OutputStream.h>
 #include <ola/io/OutputBuffer.h>
@@ -160,6 +161,16 @@ class PDUBlock {
  */
 template <class C>
 bool PDUBlock<C>::Pack(uint8_t *data, unsigned int *length) const {
+  if (!data) {
+    OLA_WARN << "PDUBlock::Pack: missing buffer";
+    return false;
+  }
+
+  if (!length) {
+    OLA_WARN << "PDUBock::Pack: missing length";
+    return false;
+  }
+
   bool status = true;
   unsigned int i = 0;
   typename std::vector<const C*>::const_iterator iter;
diff --git a/libs/acn/PDUTest.cpp b/libs/acn/PDUTest.cpp
index a5d86cc5d1..fd6e9de057 100644
--- a/libs/acn/PDUTest.cpp
+++ b/libs/acn/PDUTest.cpp
@@ -35,11 +35,13 @@ using ola::io::OutputStream;
 
 class PDUTest: public CppUnit::TestFixture {
   CPPUNIT_TEST_SUITE(PDUTest);
+  CPPUNIT_TEST(testPDU);
   CPPUNIT_TEST(testPDUBlock);
   CPPUNIT_TEST(testBlockToOutputStream);
   CPPUNIT_TEST_SUITE_END();
 
  public:
+    void testPDU();
     void testPDUBlock();
     void testBlockToOutputStream();
 
@@ -51,6 +53,41 @@ class PDUTest: public CppUnit::TestFixture {
 CPPUNIT_TEST_SUITE_REGISTRATION(PDUTest);
 
 
+/*
+ * Test that packing a PDUBlock works.
+ */
+void PDUTest::testPDU() {
+  MockPDU pdu(0x1234, 0x2468);
+
+  OLA_ASSERT_EQ(4u, pdu.HeaderSize());
+  OLA_ASSERT_EQ(4u, pdu.DataSize());
+  OLA_ASSERT_EQ(14u, 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);
+
+  // test null data
+  OLA_ASSERT_FALSE(pdu.Pack(NULL, &bytes_used));
+
+  // test a null length
+  OLA_ASSERT_FALSE(pdu.Pack(data, NULL));
+
+  // 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 packing a PDUBlock works.
  */
@@ -74,6 +111,24 @@ void PDUTest::testPDUBlock() {
   OLA_ASSERT_EQ(1u, *test++);
   OLA_ASSERT_EQ(2u, *test++);
   OLA_ASSERT_EQ(42u, *test);
+
+  // test null data
+  OLA_ASSERT_FALSE(block.Pack(NULL, &bytes_used));
+
+  // test a null length
+  OLA_ASSERT_FALSE(block.Pack(data, NULL));
+
+  // test undersized buffer
+  bytes_used = block_size - 1;
+  OLA_ASSERT_FALSE(block.Pack(data, &bytes_used));
+  // TODO(Peter): Work out what behaviour we want for the bytes used, it's
+  // currently the actual total used, not zero like the PDU::Pack returns
+  // OLA_ASSERT_EQ(0u, bytes_used);
+
+  // test oversized buffer
+  bytes_used = block_size + 1;
+  OLA_ASSERT(block.Pack(data, &bytes_used));
+  OLA_ASSERT_EQ(block_size, bytes_used);
   delete[] data;
 
   block.Clear();

From 9b385f6289b1a48d20d0ac2dfbe21fa0f0c871f2 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Sat, 6 Apr 2024 01:24:50 +0100
Subject: [PATCH 15/17] Fix more null pointer exceptions

(cherry picked from commit 91e014f048cdfc1ff21948b3eb1a7e5d9a3487c7)
---
 libs/acn/PDU.cpp     | 15 +++++++++++++++
 libs/acn/PDU.h       |  5 +++++
 libs/acn/PDUTest.cpp | 35 +++++++++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+)

diff --git a/libs/acn/PDU.cpp b/libs/acn/PDU.cpp
index 33e40f7205..0fa3ef744e 100644
--- a/libs/acn/PDU.cpp
+++ b/libs/acn/PDU.cpp
@@ -131,6 +131,11 @@ bool PDU::Pack(uint8_t *buffer, unsigned int *length) const {
  * Write this PDU to an OutputStream.
  */
 void PDU::Write(OutputStream *stream) const {
+  if (!stream) {
+    OLA_WARN << "PDU::Write: missing stream";
+    return;
+  }
+
   unsigned int size = Size();
 
   if (size <= TWOB_LENGTH_LIMIT && !m_force_length_flag) {
@@ -173,6 +178,11 @@ void PDU::Write(OutputStream *stream) const {
 void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output,
                                 uint8_t flags,
                                 bool force_length_flag) {
+  if (!output) {
+    OLA_WARN << "PDU::PrependFlagsAndLength: missing output";
+    return;
+  }
+
   PrependFlagsAndLength(output, output->Size(), flags, force_length_flag);
 }
 
@@ -184,6 +194,11 @@ void PDU::PrependFlagsAndLength(ola::io::OutputBufferInterface *output,
                                 unsigned int size,
                                 uint8_t flags,
                                 bool force_length_flag) {
+  if (!output) {
+    OLA_WARN << "PDU::PrependFlagsAndLength: missing output";
+    return;
+  }
+
   if (size + 2 <= TWOB_LENGTH_LIMIT && !force_length_flag) {
     size += 2;
     uint16_t flags_and_length = static_cast<uint16_t>(size);
diff --git a/libs/acn/PDU.h b/libs/acn/PDU.h
index 531a8e96b5..18193c2044 100644
--- a/libs/acn/PDU.h
+++ b/libs/acn/PDU.h
@@ -192,6 +192,11 @@ bool PDUBlock<C>::Pack(uint8_t *data, unsigned int *length) const {
  */
 template <class C>
 void PDUBlock<C>::Write(ola::io::OutputStream *stream) const {
+  if (!stream) {
+    OLA_WARN << "PDUBlock::Write: missing stream";
+    return;
+  }
+
   typename std::vector<const C*>::const_iterator iter;
   for (iter = m_pdus.begin(); iter != m_pdus.end(); ++iter) {
     // TODO(simon): optimize repeated headers & vectors here
diff --git a/libs/acn/PDUTest.cpp b/libs/acn/PDUTest.cpp
index fd6e9de057..cdeec18320 100644
--- a/libs/acn/PDUTest.cpp
+++ b/libs/acn/PDUTest.cpp
@@ -37,12 +37,14 @@ class PDUTest: public CppUnit::TestFixture {
   CPPUNIT_TEST_SUITE(PDUTest);
   CPPUNIT_TEST(testPDU);
   CPPUNIT_TEST(testPDUBlock);
+  CPPUNIT_TEST(testPDUToOutputStream);
   CPPUNIT_TEST(testBlockToOutputStream);
   CPPUNIT_TEST_SUITE_END();
 
  public:
     void testPDU();
     void testPDUBlock();
+    void testPDUToOutputStream();
     void testBlockToOutputStream();
 
     void setUp() {
@@ -136,6 +138,36 @@ void PDUTest::testPDUBlock() {
 }
 
 
+/*
+ * Test that writing a PDU to an OutputStream works.
+ */
+void PDUTest::testPDUToOutputStream() {
+  MockPDU pdu(0x1234, 0x2468);
+
+  IOQueue output;
+  OutputStream stream(&output);
+  pdu.Write(&stream);
+  OLA_ASSERT_EQ(14u, output.Size());
+
+  uint8_t *data = new uint8_t[output.Size()];
+  unsigned int data_size = output.Peek(data, output.Size());
+  OLA_ASSERT_EQ(output.Size(), data_size);
+
+  uint8_t EXPECTED[] = {
+    0x70, 0x0e, 0, 0,
+    0, 0x2b,
+    0x34, 0x12, 0, 0,
+    0x68, 0x24, 0, 0
+  };
+  OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), data, data_size);
+  output.Pop(output.Size());
+
+  // test null stream
+  pdu.Write(NULL);
+  delete[] data;
+}
+
+
 /*
  * Test that writing to an OutputStream works.
  */
@@ -164,6 +196,9 @@ void PDUTest::testBlockToOutputStream() {
   };
   OLA_ASSERT_DATA_EQUALS(EXPECTED, sizeof(EXPECTED), block_data, block_size);
   output.Pop(output.Size());
+
+  // test null stream
+  block.Write(NULL);
   delete[] block_data;
 }
 }  // namespace acn

From 564dcb67d961def7a7d87a801dd39fd2c60169ad Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Thu, 11 Apr 2024 15:00:13 +0100
Subject: [PATCH 16/17] RPTPDU test PrependPDU and fix a NPE error in that

(cherry picked from commit 4bdfdbd956dc4456e784b604b685f73773577252)
---
 libs/acn/RPTPDU.cpp     |  5 +++++
 libs/acn/RPTPDUTest.cpp | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/libs/acn/RPTPDU.cpp b/libs/acn/RPTPDU.cpp
index 1cbd35eb95..3a3330e023 100644
--- a/libs/acn/RPTPDU.cpp
+++ b/libs/acn/RPTPDU.cpp
@@ -117,6 +117,11 @@ void RPTPDU::PrependPDU(ola::io::IOStack *stack,
                         const ola::rdm::UID &destination_uid,
                         uint16_t destination_endpoint,
                         uint32_t sequence_number) {
+  if (!stack) {
+    OLA_WARN << "RPTPDU::PrependPDU: missing stack";
+    return;
+  }
+
   RPTHeader::rpt_pdu_header header;
   source_uid.Pack(header.source_uid, sizeof(header.source_uid));
   header.source_endpoint = HostToNetwork(source_endpoint);
diff --git a/libs/acn/RPTPDUTest.cpp b/libs/acn/RPTPDUTest.cpp
index 075b2b52e3..422f6a2152 100644
--- a/libs/acn/RPTPDUTest.cpp
+++ b/libs/acn/RPTPDUTest.cpp
@@ -22,6 +22,7 @@
 
 #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"
@@ -34,6 +35,7 @@ namespace ola {
 namespace acn {
 
 using ola::io::IOQueue;
+using ola::io::IOStack;
 using ola::io::OutputStream;
 using ola::network::HostToNetwork;
 using ola::rdm::UID;
@@ -42,11 +44,13 @@ class RPTPDUTest: public CppUnit::TestFixture {
   CPPUNIT_TEST_SUITE(RPTPDUTest);
   CPPUNIT_TEST(testSimpleRPTPDU);
   CPPUNIT_TEST(testSimpleRPTPDUToOutputStream);
+  CPPUNIT_TEST(testPrepend);
   CPPUNIT_TEST_SUITE_END();
 
  public:
   void testSimpleRPTPDU();
   void testSimpleRPTPDUToOutputStream();
+  void testPrepend();
 
   void setUp() {
     ola::InitLogging(ola::OLA_LOG_DEBUG, ola::OLA_LOG_STDERR);
@@ -148,5 +152,40 @@ void RPTPDUTest::testSimpleRPTPDUToOutputStream() {
   output.Pop(output.Size());
   delete[] pdu_data;
 }
+
+
+void RPTPDUTest::testPrepend() {
+  IOStack stack;
+  RPTPDU::PrependPDU(&stack,
+                     TEST_VECTOR,
+                     UID(0x0102, 0x03040506), 3456,
+                     UID(0x4050, 0x60708090), 7890,
+                     2370);
+
+  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, 0x1c,
+    0, 0, 0, 39,
+    1, 2, 3, 4, 5, 6,  // source UID
+    13, 128,  // source endpoint
+    64, 80, 96, 112, 128, 144,  // dest UID
+    30, 210,  // dest endpoint
+    0, 0, 9, 66,  // sequence number
+    0  // reserved
+  };
+  OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length);
+
+  // test null stack
+  RPTPDU::PrependPDU(NULL,
+                     TEST_VECTOR,
+                     UID(0x0102, 0x03040506), 3456,
+                     UID(0x4050, 0x60708090), 7890,
+                     2370);
+
+  delete[] buffer;
+}
 }  // namespace acn
 }  // namespace ola

From 10d264ab92e82f20689ea87b2aa7d02bd0518834 Mon Sep 17 00:00:00 2001
From: Peter Newman <peterjnewman@gmail.com>
Date: Thu, 11 Apr 2024 15:06:15 +0100
Subject: [PATCH 17/17] RPTRequestPDU fix a NPE error in PrependPDU

(cherry picked from commit b1dbef002908158a01a28b7bd68854efaedd6f67)
---
 libs/acn/RPTRequestPDU.cpp     | 5 +++++
 libs/acn/RPTRequestPDUTest.cpp | 4 ++++
 2 files changed, 9 insertions(+)

diff --git a/libs/acn/RPTRequestPDU.cpp b/libs/acn/RPTRequestPDU.cpp
index 577e382b11..db61797554 100644
--- a/libs/acn/RPTRequestPDU.cpp
+++ b/libs/acn/RPTRequestPDU.cpp
@@ -30,6 +30,11 @@ using ola::io::OutputStream;
 using ola::network::HostToNetwork;
 
 void RPTRequestPDU::PrependPDU(ola::io::IOStack *stack) {
+  if (!stack) {
+    OLA_WARN << "RPTRequestPDU::PrependPDU: missing stack";
+    return;
+  }
+
   uint32_t vector = HostToNetwork(
       static_cast<uint32_t>(VECTOR_REQUEST_RDM_CMD));
   stack->Write(reinterpret_cast<uint8_t*>(&vector), sizeof(vector));
diff --git a/libs/acn/RPTRequestPDUTest.cpp b/libs/acn/RPTRequestPDUTest.cpp
index d04b88712b..f40b30b7ac 100644
--- a/libs/acn/RPTRequestPDUTest.cpp
+++ b/libs/acn/RPTRequestPDUTest.cpp
@@ -139,6 +139,10 @@ void RPTRequestPDUTest::testPrepend() {
     0, 0, 0, 0x01
   };
   OLA_ASSERT_DATA_EQUALS(expected_data, sizeof(expected_data), buffer, length);
+
+  // test null stack
+  RPTRequestPDU::PrependPDU(NULL);
+
   delete[] buffer;
 }
 }  // namespace acn