From 64b1639e2fb2917e5803c05e5f9f2bacf27852c5 Mon Sep 17 00:00:00 2001
From: feyris-tan <4116042+feyris-tan@users.noreply.github.com>
Date: Thu, 21 May 2026 22:14:39 +0200
Subject: [PATCH] Added support for ATSC 3.0 Baseband Packet Data Stream
Protocol and parsing of ATSC 3.0 BBFRAMEs.
---
.../Atsc/A322/AtscPlpBasebandParser.cs | 71 ++++++++++
.../A324/BasebandPacketDataStreamReceiver.cs | 134 ++++++++++++++++++
skyscraper8/Atsc/A324/StltpReceiver.cs | 2 -
.../Skyscraper/Net/Pcap/TcpdumpNetworkType.cs | 11 +-
4 files changed, 215 insertions(+), 3 deletions(-)
create mode 100644 skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs
create mode 100644 skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs
diff --git a/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs b/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs
new file mode 100644
index 0000000..e4e1587
--- /dev/null
+++ b/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs
@@ -0,0 +1,71 @@
+using skyscraper5.Skyscraper.IO;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using static skyscraper5.Dvb.SystemSoftwareUpdate.SystemSoftwareUpdateInfo;
+
+namespace skyscraper8.Atsc.A322
+{
+ internal class AtscPlpBasebandParser
+ {
+ internal void PushBbframe(byte[] payload)
+ {
+ MemoryStream ms = new MemoryStream(payload);
+ byte a = ms.ReadUInt8();
+ bool mode = ((a >> 7) & 0x01) != 0;
+ int pointer = (a & 0x7f);
+ int addToPointer;
+
+ if (!mode)
+ {
+ //1 byte done.
+ addToPointer = 1;
+ }
+ else
+ {
+ addToPointer = 2;
+ byte b = ms.ReadUInt8();
+ pointer |= (((b >> 2) & 0x3F) << 7);
+ OfiDescription ofi = (OfiDescription)(b & 0x3);
+ switch(ofi)
+ {
+ case OfiDescription.NoExtensionMode:
+ break;
+ case OfiDescription.LongExtensionMode:
+ byte lc = ms.ReadUInt8();
+ addToPointer++;
+ int extType = (lc >> 5) & 0x7;
+ int extLen = (lc) & 0x1F;
+ byte ld = ms.ReadUInt8();
+ addToPointer++;
+ extLen |= (((ld) & 0xFF) << 5);
+ // see A322-2026-04-Physical-Layer-Protocol.pdf, Table 5.2
+ //0 = counter
+ //7 = padding
+ byte[] extensionFiled = ms.ReadBytes(extLen);
+ addToPointer += extLen;
+ if (extType != 7)
+ {
+ throw new NotImplementedException(String.Format("ATSC 3.0 BBFRAME Padding Type {0} not yet implemented.", extType));
+ }
+ break;
+ default:
+ throw new NotImplementedException(String.Format("{0} is not yet implemented in the ATSC 3.0 BBFrame Parser.", ofi));
+ }
+ }
+
+ throw new NotImplementedException("Actually do the ATSC packets here.")
+ }
+ }
+
+ internal enum OfiDescription
+ {
+ NoExtensionMode,
+ ShortExtensionMode,
+ LongExtensionMode,
+ MixedExtensionMode,
+ }
+}
diff --git a/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs b/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs
new file mode 100644
index 0000000..cb59528
--- /dev/null
+++ b/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs
@@ -0,0 +1,134 @@
+using log4net;
+using skyscraper5.Ietf.Rfc768;
+using skyscraper5.Ietf.Rfc971;
+using skyscraper5.Skyscraper.IO;
+using skyscraper5.Skyscraper.Net.Pcap;
+using skyscraper5.Skyscraper.Plugins;
+using skyscraper5.Skyscraper.Scraper;
+using skyscraper8.Atsc.A322;
+using skyscraper8.Ietf.Rfc3550_RTP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace skyscraper8.Atsc.A324
+{
+ [SkyscraperPlugin]
+ internal class BasebandPacketDataStreamReceiver : ISkyscraperMpePlugin
+ {
+ private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
+ private readonly IPAddress BPPS_IP_ADDRESS = IPAddress.Parse("239.0.51.48");
+ private byte[] plpIndexHelpBuffer = new byte[2];
+
+ public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
+ {
+ if (internetHeader.Protocol != 0x11)
+ return false;
+
+ if (ipv4Packet[8] != 0x80)
+ return false;
+
+ if (!internetHeader.DestinationAddress.Equals(BPPS_IP_ADDRESS))
+ return false;
+
+ if (BitConverter.IsLittleEndian)
+ {
+ plpIndexHelpBuffer[0] = ipv4Packet[3];
+ plpIndexHelpBuffer[1] = ipv4Packet[2];
+ }
+ else
+ {
+ plpIndexHelpBuffer[0] = ipv4Packet[2];
+ plpIndexHelpBuffer[1] = ipv4Packet[3];
+ }
+
+ ushort dstPort = BitConverter.ToUInt16(plpIndexHelpBuffer, 0);
+
+ if (dstPort < 30000)
+ return false;
+
+ if (dstPort > 30066)
+ return false;
+
+
+ return true;
+ }
+
+ public void ConnectToStorage(object[] connector)
+ {
+ throw new NotImplementedException();
+ }
+
+ private bool warnedAboutNonMarkedPackets;
+ public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
+ {
+ UserDatagram udpPacket = new UserDatagram(ipv4Packet);
+ ushort plp = (ushort)(udpPacket.DestinationPort - 30000);
+ RtpPacket rtpPacket = new RtpPacket(udpPacket.Payload);
+
+ if (!rtpPacket.RtpMarker)
+ {
+ if (!warnedAboutNonMarkedPackets)
+ {
+ logger.WarnFormat("ATSC 3.0 BBFRAMEs spanning multiple packets are not supported yet. It would be great if you could share a sample of this, so this can be implemented.");
+ warnedAboutNonMarkedPackets = true;
+ }
+ return;
+ }
+
+ if (plp == 64)
+ {
+ HandlePreambleData(rtpPacket.RtpPayload);
+ return;
+ }
+ else if (plp == 65)
+ {
+ HandleTimingAndManagementData(rtpPacket.RtpPayload);
+ return;
+ }
+ else if (plp <= 63)
+ {
+ HandlePlp(plp, rtpPacket.RtpPayload);
+ return;
+ }
+ }
+
+ private AtscPlpBasebandParser[] plps;
+ private void HandlePlp(ushort plp, byte[] rtpPayload)
+ {
+ if (plps == null)
+ {
+ plps = new AtscPlpBasebandParser[64];
+ }
+ if (plps[plp] == null)
+ {
+ plps[plp] = new AtscPlpBasebandParser();
+ }
+ plps[plp].PushBbframe(rtpPayload);
+ }
+
+ private void HandleTimingAndManagementData(byte[] rtpPayload)
+ {
+ throw new NotImplementedException();
+ }
+
+ private void HandlePreambleData(byte[] rtpPayload)
+ {
+ throw new NotImplementedException();
+ }
+
+ private SkyscraperContext context;
+ public void SetContext(DateTime? currentTime, object skyscraperContext)
+ {
+ context = skyscraperContext as SkyscraperContext;
+ }
+
+ public bool StopProcessingAfterThis()
+ {
+ return true;
+ }
+ }
+}
diff --git a/skyscraper8/Atsc/A324/StltpReceiver.cs b/skyscraper8/Atsc/A324/StltpReceiver.cs
index d626c16..02bf987 100644
--- a/skyscraper8/Atsc/A324/StltpReceiver.cs
+++ b/skyscraper8/Atsc/A324/StltpReceiver.cs
@@ -114,8 +114,6 @@ namespace skyscraper8.Atsc.A324
}
break;
case 4: //Deliver the packet
- currentIpPacketBuffer[20] = 0x69;
- currentIpPacketBuffer[21] = 0x69;
context.OnIpDatagram(0x1fff, currentIpPacketBuffer.AsSpan().Slice(0, currentIpPacketBufferOffset).ToArray());
stateMachineState = 1;
break;
diff --git a/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs b/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs
index 42c2cce..ac557ee 100644
--- a/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs
+++ b/skyscraper8/Skyscraper/Net/Pcap/TcpdumpNetworkType.cs
@@ -13,6 +13,15 @@ namespace skyscraper5.Skyscraper.Net.Pcap
PPPoE = 51,
RawIp = 101,
WLAN = 105,
- UserDefined = 147
+ FibreChannel = 122,
+ DocsisMac = 143,
+ ///
+ /// Voile uses DLT_USER0 for LLC PDUs
+ ///
+ UserDefined = 147,
+ ///
+ /// Voile uses DLT_USER1 for RTP packets without any Transport layer headers.
+ ///
+ UserDefined2 = 148,
}
}