From d33a0003ef90192f5fb2259479dda925d446a1f2 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Wed, 20 May 2026 22:34:41 +0200 Subject: [PATCH] Added an option to receive ATSC 3.0 STLTP. --- skyscraper8/Atsc/A324/StltpReceiver.cs | 140 ++++++++++++++++++ .../Ietf/Rfc3550_RTP/MediaFormatEnum.cs | 32 ++++ skyscraper8/Ietf/Rfc3550_RTP/RtpPacket.cs | 64 ++++++++ skyscraper8/Properties/launchSettings.json | 2 +- .../SatIp/SessionDescriptionProtocol.cs | 31 +--- .../Net/Pcap/PcapIpTrafficHandler.cs | 2 +- 6 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 skyscraper8/Atsc/A324/StltpReceiver.cs create mode 100644 skyscraper8/Ietf/Rfc3550_RTP/MediaFormatEnum.cs create mode 100644 skyscraper8/Ietf/Rfc3550_RTP/RtpPacket.cs diff --git a/skyscraper8/Atsc/A324/StltpReceiver.cs b/skyscraper8/Atsc/A324/StltpReceiver.cs new file mode 100644 index 0000000..d626c16 --- /dev/null +++ b/skyscraper8/Atsc/A324/StltpReceiver.cs @@ -0,0 +1,140 @@ +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Skyscraper.IO; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper8.Ietf.Rfc3550_RTP; +using skyscraper8.Skyscraper.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Atsc.A324 +{ + [SkyscraperPlugin] + internal class StltpReceiver : ISkyscraperMpePlugin + { + public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (internetHeader.Protocol != 0x11) + return false; + + if (!internetHeader.IsDestinationMulticast) + return false; + + if (ipv4Packet[8] != 0x80) + return false; + + if ((ipv4Packet[9] & 0x7f) != 97) + return false; + + return true; + } + + public void ConnectToStorage(object[] connector) + { + throw new NotImplementedException(); + } + + private bool processed; + private ushort expectedRtpSequenceNumber; + private int currentIpPacketBufferOffset; + private byte[] currentIpPacketBuffer; + private int bytesNeeded; + private int stateMachineState; + public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + processed = false; + UserDatagram udpPacket = new UserDatagram(ipv4Packet); + RtpPacket rtpPacket = new RtpPacket(udpPacket.Payload); + if (rtpPacket.RtpPayloadType != MediaFormatEnum.DynamicRtpType97) + return; + + if (expectedRtpSequenceNumber != rtpPacket.RtpSequenceNumber) + { + //sync loss + currentIpPacketBufferOffset = 0; + if (currentIpPacketBuffer != null) + Array.Clear(currentIpPacketBuffer); + bytesNeeded = 0; + stateMachineState = 0; + } + expectedRtpSequenceNumber = rtpPacket.RtpSequenceNumber; + expectedRtpSequenceNumber++; + + MemoryStream ms = new MemoryStream(rtpPacket.RtpPayload); + while (ms.GetAvailableBytes() > 0) + { + switch (stateMachineState) + { + case 0: //Initial state, nothing processed yet and not yet synced either. + if (rtpPacket.RtpMarker) + { + uint packetOffset = rtpPacket.RtpSynchronizationSourceIdentifier & 0x0000ffff; + ms.Position = packetOffset; + stateMachineState = 1; + break; + } + else + { + //The packet can not be used for sync, so we can drop it. + processed = true; + return; + } + case 1: //Prepare begin reading of the first four bytes of next IP packet. + if (currentIpPacketBuffer == null) + currentIpPacketBuffer = new byte[ushort.MaxValue]; + Array.Clear(currentIpPacketBuffer); + bytesNeeded = 4; + currentIpPacketBufferOffset = 0; + stateMachineState = 2; + break; + case 2: //Read the first four byte of the next IP packet. + int readAmount = ms.Read(currentIpPacketBuffer, currentIpPacketBufferOffset, bytesNeeded); + bytesNeeded -= readAmount; + currentIpPacketBufferOffset += readAmount; + if (bytesNeeded == 0) + { + //Initial four bytes completed, now we know how long the packet is. + bytesNeeded = currentIpPacketBuffer[2] << 8 | currentIpPacketBuffer[3]; + bytesNeeded -= 4; + stateMachineState = 3; + } + break; + case 3: //Read the IP packet until it's complete + int readAmount2 = ms.Read(currentIpPacketBuffer, currentIpPacketBufferOffset, bytesNeeded); + bytesNeeded -= readAmount2; + currentIpPacketBufferOffset += readAmount2; + if (bytesNeeded == 0) + { + //Packet complete! + stateMachineState = 4; + } + break; + case 4: //Deliver the packet + currentIpPacketBuffer[20] = 0x69; + currentIpPacketBuffer[21] = 0x69; + context.OnIpDatagram(0x1fff, currentIpPacketBuffer.AsSpan().Slice(0, currentIpPacketBufferOffset).ToArray()); + stateMachineState = 1; + break; + default: + throw new NotImplementedException(string.Format("Unknown state in STLTP Receiver finite automata: {0}", stateMachineState)); + } + } + processed = true; + } + + private SkyscraperContext context; + public void SetContext(DateTime? currentTime, object skyscraperContext) + { + this.context = skyscraperContext as SkyscraperContext; + } + + public bool StopProcessingAfterThis() + { + return processed; + } + } +} diff --git a/skyscraper8/Ietf/Rfc3550_RTP/MediaFormatEnum.cs b/skyscraper8/Ietf/Rfc3550_RTP/MediaFormatEnum.cs new file mode 100644 index 0000000..13c36b8 --- /dev/null +++ b/skyscraper8/Ietf/Rfc3550_RTP/MediaFormatEnum.cs @@ -0,0 +1,32 @@ +namespace skyscraper8.Ietf.Rfc3550_RTP +{ + public enum MediaFormatEnum + { + PCMU = 0, + GSM = 3, + G723 = 4, + DVI4_32kbit = 5, + DVI4_64kbit = 6, + LPC = 7, + PCMA = 8, + G722 = 9, + L16Stereo = 10, + L16Mono = 11, + QCELP = 12, + ComfortNoise = 13, + MPA = 14, + G728 = 15, + DVI4_44kbit = 16, + DVI4_88kbit = 17, + G729 = 18, + CellB = 25, + Jpeg = 26, + NV = 28, + H261 = 31, + MPV = 32, + M2T = 33, + H263 = 34, + DynamicRtpType97 = 97 + } + +} diff --git a/skyscraper8/Ietf/Rfc3550_RTP/RtpPacket.cs b/skyscraper8/Ietf/Rfc3550_RTP/RtpPacket.cs new file mode 100644 index 0000000..2cdb843 --- /dev/null +++ b/skyscraper8/Ietf/Rfc3550_RTP/RtpPacket.cs @@ -0,0 +1,64 @@ +using skyscraper5.Skyscraper.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Ietf.Rfc3550_RTP +{ + internal class RtpPacket + { + public RtpPacket(byte[] payload) + { + MemoryStream ms = new MemoryStream(payload, false); + byte byte1 = ms.ReadUInt8(); + + this.RtpVersion = new Version((byte1 & 0xc0) >> 6, 0); + this.RtpPadding = (byte1 & 0x20) != 0; + bool rtpExtension = (byte1 & 0x10) != 0; + int csrcCount = (byte1 & 0x0f); + + byte byte2 = ms.ReadUInt8(); + this.RtpMarker = (byte2 & 0x80) != 0; + this.RtpPayloadType = (MediaFormatEnum)(byte2 & 0x7f); + + this.RtpSequenceNumber = ms.ReadUInt16BE(); + + this.RtpTimestamp = ms.ReadUInt32BE(); + + this.RtpSynchronizationSourceIdentifier = ms.ReadUInt32BE(); + + if (csrcCount != 0) + { + CsrcIdentifiers = new uint[csrcCount]; + for (int i = 0; i < csrcCount; i++) + { + CsrcIdentifiers[i] = ms.ReadUInt32BE(); + } + } + + if (rtpExtension) + { + this.ExtensionHeaderId = ms.ReadUInt16BE(); + ushort extensionHeaderLength = ms.ReadUInt16BE(); + extensionHeaderLength *= 4; + this.ExtensionHeaderData = ms.ReadBytes(extensionHeaderLength); + } + + this.RtpPayload = ms.ReadBytes(ms.GetAvailableBytes()); + } + + public Version RtpVersion { get; } + public bool RtpPadding { get; } + public bool RtpMarker { get; } + public MediaFormatEnum RtpPayloadType { get; } + public ushort RtpSequenceNumber { get; } + public uint RtpTimestamp { get; } + public uint RtpSynchronizationSourceIdentifier { get; } + public uint[] CsrcIdentifiers { get; private set; } + public ushort? ExtensionHeaderId { get; } + public byte[] ExtensionHeaderData { get; } + public byte[] RtpPayload { get; } + } +} diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json index 43a56e5..ad853c5 100644 --- a/skyscraper8/Properties/launchSettings.json +++ b/skyscraper8/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "skyscraper8": { "commandName": "Project", - "commandLineArgs": "salvage strat1 \"C:\\Users\\Sascha Schiemann\\Downloads\\65.0W_11304.256_V_30000_(2026-05-17 09.44.02)_dump.ts\"", + "commandLineArgs": "salvage strat1 \"Z:\\Persönliches\\Satellitescommunity\\Skyscraper Test Fixture\\65.0W_11304.256_V_30000_(2026-05-17 09.44.02)_dump.ts\"", "remoteDebugEnabled": false }, "Container (Dockerfile)": { diff --git a/skyscraper8/SatIp/SessionDescriptionProtocol.cs b/skyscraper8/SatIp/SessionDescriptionProtocol.cs index b011a20..81adae7 100644 --- a/skyscraper8/SatIp/SessionDescriptionProtocol.cs +++ b/skyscraper8/SatIp/SessionDescriptionProtocol.cs @@ -1,4 +1,5 @@ -using System; +using skyscraper8.Ietf.Rfc3550_RTP; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -118,32 +119,4 @@ namespace skyscraper8.SatIp Video, } - public enum MediaFormatEnum - { - PCMU = 0, - GSM = 3, - G723 = 4, - DVI4_32kbit = 5, - DVI4_64kbit = 6, - LPC = 7, - PCMA = 8, - G722 = 9, - L16Stereo = 10, - L16Mono = 11, - QCELP = 12, - ComfortNoise = 13, - MPA = 14, - G728 = 15, - DVI4_44kbit = 16, - DVI4_88kbit = 17, - G729 = 18, - CellB = 25, - Jpeg = 26, - NV = 28, - H261 = 31, - MPV = 32, - M2T = 33, - H263 = 34 - - } } diff --git a/skyscraper8/Skyscraper/Net/Pcap/PcapIpTrafficHandler.cs b/skyscraper8/Skyscraper/Net/Pcap/PcapIpTrafficHandler.cs index 6b06458..2460b79 100644 --- a/skyscraper8/Skyscraper/Net/Pcap/PcapIpTrafficHandler.cs +++ b/skyscraper8/Skyscraper/Net/Pcap/PcapIpTrafficHandler.cs @@ -27,7 +27,7 @@ namespace skyscraper8.Skyscraper.Net.Pcap { if (pcapWritersIp == null) { - pcapWritersIp = new PcapWriter[0x1fff]; + pcapWritersIp = new PcapWriter[0x2000]; EnsureOutputdirExists(); } if (pcapWritersIp[pid] == null)