From 1e9b321bd7bdb1908436f6c80a93912476790ce0 Mon Sep 17 00:00:00 2001 From: feyris-tan Date: Thu, 16 Oct 2025 13:33:38 +0200 Subject: [PATCH 1/3] BBframeDeencapsulator version 4 will hopefully save the day, and not break CI again. --- .gitea/workflows/demo.yaml | 5 +- skyscraper8.sln.DotSettings.user | 3 + .../MultiprotocolEncapsulationEventHandler.cs | 2 +- skyscraper8/GS/BBframeDeencapsulator3.cs | 12 +- skyscraper8/GS/GSE/GseFragmentation.cs | 44 ++++++ skyscraper8/GS/GSE/GseLabel.cs | 65 ++++++++ skyscraper8/GS/GSE/GsePacket.cs | 27 ++++ skyscraper8/GS/GSE/GseReader.cs | 145 ++++++++++++++++++ skyscraper8/GS/GsTypeDetector.cs | 38 ++++- skyscraper8/GS/POC/Pts2Bbf.cs | 2 +- skyscraper8/GS/POC/Stid135Test.cs | 6 +- skyscraper8/GS/Stid135BbFrameReader.cs | 5 +- skyscraper8/GS/TsGsType.cs | 9 ++ .../Skyscraper/Scraper/SkyscraperContext.cs | 4 +- 14 files changed, 354 insertions(+), 13 deletions(-) create mode 100644 skyscraper8/GS/GSE/GseFragmentation.cs create mode 100644 skyscraper8/GS/GSE/GseLabel.cs create mode 100644 skyscraper8/GS/GSE/GsePacket.cs create mode 100644 skyscraper8/GS/GSE/GseReader.cs create mode 100644 skyscraper8/GS/TsGsType.cs diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml index 5ba17c5..1ea1dff 100644 --- a/.gitea/workflows/demo.yaml +++ b/.gitea/workflows/demo.yaml @@ -33,7 +33,8 @@ jobs: uses: akkuman/gitea-release-action@v1 with: name: ${{ gitea.sha }} - md5sum: true - sha256sum: true + md5sum: false + sha256sum: false + tag_name: release_tag files: |- ${{ gitea.workspace }}/skyscraper8-${{ gitea.sha }}.zip \ No newline at end of file diff --git a/skyscraper8.sln.DotSettings.user b/skyscraper8.sln.DotSettings.user index 33fbc3f..ff66e63 100644 --- a/skyscraper8.sln.DotSettings.user +++ b/skyscraper8.sln.DotSettings.user @@ -1,4 +1,7 @@  + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded <data><HostParameters type="LocalHostParameters" /><Argument type="StandaloneArgument"><Arguments IsNull="False"></Arguments><FileName IsNull="False"></FileName><WorkingDirectory IsNull="False"></WorkingDirectory><Scope><ProcessFilters /></Scope></Argument><Info type="TimelineInfo" /><CoreOptions type="CoreOptions"><CoreTempPath IsNull="False"></CoreTempPath><RemoteEndPoint IsNull="False"></RemoteEndPoint><AdditionalEnvironmentVariables /></CoreOptions><HostOptions type="HostOptions"><HostTempPath IsNull="False"></HostTempPath></HostOptions></data> \ No newline at end of file diff --git a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs index b99bdaf..09fb4cb 100644 --- a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs +++ b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationEventHandler.cs @@ -7,7 +7,7 @@ using skyscraper5.Ietf.Rfc971; namespace skyscraper5.Dvb.DataBroadcasting { - interface IMultiprotocolEncapsulationEventHandler + public interface IMultiprotocolEncapsulationEventHandler { void OnIpv4PacketArrival(InternetHeader internetHeader, byte[] ipv4Packet); void OnIpDatagram(int sourcePid, byte[] payload); diff --git a/skyscraper8/GS/BBframeDeencapsulator3.cs b/skyscraper8/GS/BBframeDeencapsulator3.cs index 791235c..33996fa 100644 --- a/skyscraper8/GS/BBframeDeencapsulator3.cs +++ b/skyscraper8/GS/BBframeDeencapsulator3.cs @@ -1,9 +1,16 @@ using log4net; +using skyscraper5.Dvb.DataBroadcasting; namespace skyscraper8.GSE; public class BbframeDeencapsulator3 : IBbframeDeencapsulator { + public BbframeDeencapsulator3(IMultiprotocolEncapsulationEventHandler mpeEventHandler) + { + _mpeEventHandler = mpeEventHandler; + } + + private readonly IMultiprotocolEncapsulationEventHandler _mpeEventHandler; private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); private long numPushed; public void PushPacket(byte[] bbframe) @@ -30,7 +37,10 @@ public class BbframeDeencapsulator3 : IBbframeDeencapsulator if (mis[bbHeader.Matype2] == null) { logger.InfoFormat("Found a stream on MIS {0}",bbHeader.Matype2); - mis[bbHeader.Matype2] = new GsTypeDetector(); + mis[bbHeader.Matype2] = new GsTypeDetector(bbHeader.Matype2) + { + mpeEventHandler = this._mpeEventHandler + }; } mis[bbHeader.Matype2].PushFrame(bbHeader, new ReadOnlySpan(bbframe, 11, bbframe.Length - 11)); diff --git a/skyscraper8/GS/GSE/GseFragmentation.cs b/skyscraper8/GS/GSE/GseFragmentation.cs new file mode 100644 index 0000000..838a65a --- /dev/null +++ b/skyscraper8/GS/GSE/GseFragmentation.cs @@ -0,0 +1,44 @@ +namespace skyscraper8.GSE.GSE; + +public class GseFragmentation +{ + public GseFragmentation(GsePacket child) + { + this.ProtocolType = child.ProtocolType.Value; + this.Label = child.Label; + AddFragement(child); + } + + private List fragments; + + public void AddFragement(GsePacket packet) + { + if (fragments == null) + fragments = new List(); + + fragments.Add(packet.GseDataBytes); + } + + public GseLabel Label { get; private set; } + + public ushort ProtocolType { get; private set; } + + public bool Validate() + { + //To be implemented... + return true; + } + + public byte[] GetGseDataBytes() + { + int totalLength = fragments.Select(x => x.Length).Sum(); + byte[] buffer = new byte[totalLength]; + int offset = 0; + for (int i = 0; i < fragments.Count; i++) + { + Array.Copy(fragments[i],0,buffer,offset,fragments[i].Length); + offset += fragments[i].Length; + } + return buffer; + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GSE/GseLabel.cs b/skyscraper8/GS/GSE/GseLabel.cs new file mode 100644 index 0000000..563c871 --- /dev/null +++ b/skyscraper8/GS/GSE/GseLabel.cs @@ -0,0 +1,65 @@ +using System.Net.NetworkInformation; + +namespace skyscraper8.GSE.GSE; + +public abstract class GseLabel +{ + public abstract int Length { get; } + public abstract string ToString(); +} + +public class _6byteLabel : GseLabel +{ + public _6byteLabel(byte[] buffer) + { + MAC = new PhysicalAddress(buffer); + } + + public PhysicalAddress MAC { get; private set; } + + override public string ToString() + { + return MAC.ToString(); + } + + public override int Length => 6; +} + +public class _3byteLabel : GseLabel +{ + public _3byteLabel(byte[] buffer) + { + Label = buffer; + } + + public byte[] Label { get; private set; } + + override public string ToString() + { + return BitConverter.ToString(Label); + } + + override public int Length => 3; +} + +public class BroadcastLabel : GseLabel +{ + private BroadcastLabel() {} + + override public string ToString() + { + return ""; + } + + override public int Length => 0; + + private static BroadcastLabel _instance; + public static BroadcastLabel GetInstance() + { + if (_instance == null) + { + _instance = new BroadcastLabel(); + } + return _instance; + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GSE/GsePacket.cs b/skyscraper8/GS/GSE/GsePacket.cs new file mode 100644 index 0000000..0b1bdf5 --- /dev/null +++ b/skyscraper8/GS/GSE/GsePacket.cs @@ -0,0 +1,27 @@ +namespace skyscraper8.GSE.GSE; + +public class GsePacket +{ + public bool StartIndicator { get; } + public bool EndIndicator { get; } + public int LabelTypeIndicator { get; } + public byte? FragmentId { get; set; } + public ushort? TotalLength { get; set; } + public ushort? ProtocolType { get; set; } + public GseLabel Label { get; set; } + public byte[] GseDataBytes { get; set; } + public uint Crc32 { get; set; } + + public GsePacket(bool startIndicator, bool endIndicator, int labelTypeIndicator) + { + StartIndicator = startIndicator; + EndIndicator = endIndicator; + LabelTypeIndicator = labelTypeIndicator; + } + + public override string ToString() + { + return + $"{nameof(StartIndicator)}: {StartIndicator}, {nameof(EndIndicator)}: {EndIndicator}, {nameof(LabelTypeIndicator)}: {LabelTypeIndicator}, {nameof(FragmentId)}: {FragmentId}, {nameof(TotalLength)}: {TotalLength}, {nameof(ProtocolType)}: {ProtocolType}, {nameof(Label)}: {Label}"; + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GSE/GseReader.cs b/skyscraper8/GS/GSE/GseReader.cs new file mode 100644 index 0000000..e2272d1 --- /dev/null +++ b/skyscraper8/GS/GSE/GseReader.cs @@ -0,0 +1,145 @@ +using log4net; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.GSE.GSE; + +internal class GseReader : IMisHandler +{ + private GseLabel lastLabel; + public IMultiprotocolEncapsulationEventHandler mpeEventHandler { get; set; } + private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan) + { + MemoryStream ms = new MemoryStream(readOnlySpan.ToArray()); + + while (ms.Position < ms.Length) + { + byte a = ms.ReadUInt8(); + bool startIndicator = (a & 0x80) != 0; + bool endIndicator = (a & 0x40) != 0; + int labelTypeIndicator = (a & 0x30) >> 4; + if (!startIndicator && !endIndicator && labelTypeIndicator == 0) + { + //end of BBFrame + return; + } + else + { + int gseLength = (a & 0x0f); + gseLength <<= 8; + gseLength += ms.ReadUInt8(); + + GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator); + if (!startIndicator || !endIndicator) + { + child.FragmentId = ms.ReadUInt8(); + gseLength--; + } + + if (startIndicator && !endIndicator) + { + child.TotalLength = ms.ReadUInt16BE(); + gseLength -= 2; + } + + if (startIndicator) + { + child.ProtocolType = ms.ReadUInt16BE(); + gseLength -= 2; + if (!endIndicator) + child.TotalLength -= 2; + switch (labelTypeIndicator) + { + case 0: + child.Label = new _6byteLabel(ms.ReadBytes(6)); + gseLength -= 6; + if (!endIndicator) + child.TotalLength -= 6; + lastLabel = child.Label; + break; + case 1: + child.Label = new _3byteLabel(ms.ReadBytes(3)); + gseLength -= 3; + if (!endIndicator) + child.TotalLength -= 3; + lastLabel = child.Label; + break; + case 2: + child.Label = BroadcastLabel.GetInstance(); + break; + case 3: + child.Label = this.lastLabel; + break; + default: + throw new NotImplementedException(String.Format("Unknown label type: {0}", labelTypeIndicator)); + } + } + + if (!startIndicator && endIndicator) + gseLength -= 4; //for crc32 + + if (gseLength > ms.GetAvailableBytes()) + return; + child.GseDataBytes = ms.ReadBytes(gseLength); + + if (!startIndicator && endIndicator) + child.Crc32 = ms.ReadUInt32BE(); + + ProcessPacket(child); + } + } + } + + private bool[] warnedEthertypes; + private GseFragmentation[] fragmentations; + private void ProcessPacket(GsePacket child) + { + if (!child.StartIndicator && child.EndIndicator) + { + if (fragmentations == null) + return; + GseFragmentation currentFragmentation = fragmentations[child.FragmentId.Value]; + if (currentFragmentation == null) + return; + currentFragmentation.AddFragement(child); + if (currentFragmentation.Validate()) + { + mpeEventHandler.OnIpDatagram(0x010e,currentFragmentation.GetGseDataBytes()); + } + + fragmentations[child.FragmentId.Value] = null; + return; + } + + if (child.StartIndicator && !child.EndIndicator) + { + if (fragmentations == null) + fragmentations = new GseFragmentation[256]; + fragmentations[child.FragmentId.Value] = new GseFragmentation(child); + return; + } + + if (child.StartIndicator && child.EndIndicator) + { + switch (child.ProtocolType) + { + case 0x0800: + mpeEventHandler.OnIpDatagram(0x010e, child.GseDataBytes); + break; + default: + if (warnedEthertypes == null) + warnedEthertypes = new bool[0xffff]; + if (!warnedEthertypes[child.ProtocolType.Value]) + { + logger.WarnFormat("This GSE contains other traffic types (type {0:X4}) besides IP (type 0x0800). If it's not too much trouble, please consider submitting a sample.",child.ProtocolType.Value); + warnedEthertypes[child.ProtocolType.Value] = true; + } + break; + } + + return; + } + throw new NotImplementedException(child.ToString()); + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GsTypeDetector.cs b/skyscraper8/GS/GsTypeDetector.cs index 3ef824d..8c395e8 100644 --- a/skyscraper8/GS/GsTypeDetector.cs +++ b/skyscraper8/GS/GsTypeDetector.cs @@ -1,9 +1,45 @@ +using log4net; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper8.GSE.GSE; + namespace skyscraper8.GSE; public class GsTypeDetector : IMisHandler { + public IMultiprotocolEncapsulationEventHandler mpeEventHandler { get; set; } + private readonly byte _misId; + private readonly ILog logger; + private HashSet> _postedStreamTypes; + public GsTypeDetector(byte misId) + { + _misId = misId; + logger = LogManager.GetLogger(String.Format("{0} MIS {1}",System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name,misId)); + } + + private GseReader gseReader; public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan) { - throw new NotImplementedException(); + if (bbHeader.TsGs == 1 && bbHeader.SyncByte == 0) + { + //Looks like Continuous GSE. + if (gseReader == null) + { + gseReader = new GseReader(); + gseReader.mpeEventHandler = mpeEventHandler; + } + + gseReader.PushFrame(bbHeader, readOnlySpan); + return; + } + + //We have no idea what this is. + Tuple streamTypeToPost = new Tuple(bbHeader.TsGs, bbHeader.SyncByte); + if (_postedStreamTypes == null) + _postedStreamTypes = new HashSet>(); + if (!_postedStreamTypes.Contains(streamTypeToPost)) + { + logger.WarnFormat("This GS contains packets of type {0} ({2}) with a sync byte of {1}. This is not supported yet. If it isn't too much trouble, please consider sharing a sample of this stream.",streamTypeToPost.Item1,streamTypeToPost.Item2, (TsGsType)streamTypeToPost.Item1); + _postedStreamTypes.Add(streamTypeToPost); + } } } \ No newline at end of file diff --git a/skyscraper8/GS/POC/Pts2Bbf.cs b/skyscraper8/GS/POC/Pts2Bbf.cs index e88fd63..8ebb6d8 100644 --- a/skyscraper8/GS/POC/Pts2Bbf.cs +++ b/skyscraper8/GS/POC/Pts2Bbf.cs @@ -44,7 +44,7 @@ public class Pts2Bbf FileStream fileStream = file.OpenRead(); TsContext mpeg2 = new TsContext(); - mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(dumper)); + mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(null, dumper)); DataStorage dataStorage = new InMemoryScraperStorage(); ObjectStorage objectStorage = new NullObjectStorage(); SkyscraperContext skyscraper = new SkyscraperContext(mpeg2, dataStorage, objectStorage); diff --git a/skyscraper8/GS/POC/Stid135Test.cs b/skyscraper8/GS/POC/Stid135Test.cs index 396c001..5b6b28e 100644 --- a/skyscraper8/GS/POC/Stid135Test.cs +++ b/skyscraper8/GS/POC/Stid135Test.cs @@ -13,13 +13,13 @@ public class Stid135Test { FileStream fileStream = file.OpenRead(); - BbframeDeencapsulator3 decap = new BbframeDeencapsulator3(); - TsContext mpeg2 = new TsContext(); - mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(decap)); DataStorage dataStorage = new InMemoryScraperStorage(); ObjectStorage objectStorage = new NullObjectStorage(); SkyscraperContext skyscraper = new SkyscraperContext(mpeg2, dataStorage, objectStorage); + + mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(skyscraper)); + skyscraper.InitalizeFilterChain(); skyscraper.IngestFromStream(fileStream); diff --git a/skyscraper8/GS/Stid135BbFrameReader.cs b/skyscraper8/GS/Stid135BbFrameReader.cs index 0a1dba5..1859376 100644 --- a/skyscraper8/GS/Stid135BbFrameReader.cs +++ b/skyscraper8/GS/Stid135BbFrameReader.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting; namespace skyscraper8.GSE { @@ -13,10 +14,10 @@ namespace skyscraper8.GSE { private IBbframeDeencapsulator deencapsulator; - public Stid135BbFrameReader(IBbframeDeencapsulator deencapsulator = null) + public Stid135BbFrameReader(IMultiprotocolEncapsulationEventHandler mpeEventHandler, IBbframeDeencapsulator deencapsulator = null) { if (deencapsulator == null) - deencapsulator = new BbframeDeencapsulator3(); + deencapsulator = new BbframeDeencapsulator3(mpeEventHandler); this.deencapsulator = deencapsulator; } diff --git a/skyscraper8/GS/TsGsType.cs b/skyscraper8/GS/TsGsType.cs new file mode 100644 index 0000000..94c69cc --- /dev/null +++ b/skyscraper8/GS/TsGsType.cs @@ -0,0 +1,9 @@ +namespace skyscraper8.GSE; + +public enum TsGsType +{ + GenericPacketized, + GenericContinuous, + GseHem, + Transport +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs index 0ecfeb2..e85b738 100644 --- a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs @@ -301,7 +301,7 @@ namespace skyscraper5.Skyscraper.Scraper { if (!DvbContext.IsPidProcessorPresent(0x010e)) { - DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader()); + DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(this)); UiJunction?.SetGseMode(); LogEvent(SkyscraperContextEvent.SpecialTsMode, "STiD135 encapsulated GS detected."); SpecialTsType = 3; @@ -356,7 +356,7 @@ namespace skyscraper5.Skyscraper.Scraper { if (!DvbContext.IsPidProcessorPresent(0x010e)) { - DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader()); + DvbContext.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(this)); UiJunction?.SetGseMode(); LogEvent(SkyscraperContextEvent.SpecialTsMode, "STiD135 encapsulated GS detected."); SpecialTsType = 3; From 46798f4df1e7ad0032a26e45103e6929f4efc9e7 Mon Sep 17 00:00:00 2001 From: feyris-tan Date: Thu, 16 Oct 2025 16:04:01 +0200 Subject: [PATCH 2/3] Improved the DVB-NIP via GSE reader by a ton! --- skyscraper8.sln.DotSettings.user | 5 + skyscraper8/GS/BBHeader.cs | 8 +- skyscraper8/GS/BBframeDeencapsulator3.cs | 2 - skyscraper8/GS/GSE-HEM/GseHemReader.cs | 111 ++++++++++++++++++ skyscraper8/GS/GSE-HEM/RayBuffer.cs | 111 ++++++++++++++++++ skyscraper8/GS/GsTypeDetector.cs | 18 +++ skyscraper8/GS/POC/Stid135Test.cs | 3 +- .../Mpeg2/PacketFilter/ScrambleFilter.cs | 7 +- skyscraper8/Program.cs | 2 +- .../Scraper/Storage/NullObjectStorage.cs | 3 +- skyscraper8/log4net.config | 6 +- 11 files changed, 262 insertions(+), 14 deletions(-) create mode 100644 skyscraper8/GS/GSE-HEM/GseHemReader.cs create mode 100644 skyscraper8/GS/GSE-HEM/RayBuffer.cs diff --git a/skyscraper8.sln.DotSettings.user b/skyscraper8.sln.DotSettings.user index ff66e63..296c055 100644 --- a/skyscraper8.sln.DotSettings.user +++ b/skyscraper8.sln.DotSettings.user @@ -1,7 +1,12 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded <data><HostParameters type="LocalHostParameters" /><Argument type="StandaloneArgument"><Arguments IsNull="False"></Arguments><FileName IsNull="False"></FileName><WorkingDirectory IsNull="False"></WorkingDirectory><Scope><ProcessFilters /></Scope></Argument><Info type="TimelineInfo" /><CoreOptions type="CoreOptions"><CoreTempPath IsNull="False"></CoreTempPath><RemoteEndPoint IsNull="False"></RemoteEndPoint><AdditionalEnvironmentVariables /></CoreOptions><HostOptions type="HostOptions"><HostTempPath IsNull="False"></HostTempPath></HostOptions></data> \ No newline at end of file diff --git a/skyscraper8/GS/BBHeader.cs b/skyscraper8/GS/BBHeader.cs index bd57e77..13228a3 100644 --- a/skyscraper8/GS/BBHeader.cs +++ b/skyscraper8/GS/BBHeader.cs @@ -27,12 +27,12 @@ public class BBHeader : Validatable SyncByte = BbHeaderSpan[6]; SyncD = BbHeaderSpan[7] << 8 | BbHeaderSpan[8]; + SyncD /= 8; - ChecksumValid = DvbCrc8.Compute(BbHeaderSpan) == 0; - Valid = ChecksumValid; + //ChecksumValid = DvbCrc8.Compute(BbHeaderSpan) == 0; + //Valid = ChecksumValid; + Valid = true; } - - public bool ChecksumValid { get; private set; } public int SyncD { get; private set; } diff --git a/skyscraper8/GS/BBframeDeencapsulator3.cs b/skyscraper8/GS/BBframeDeencapsulator3.cs index 33996fa..afbbba8 100644 --- a/skyscraper8/GS/BBframeDeencapsulator3.cs +++ b/skyscraper8/GS/BBframeDeencapsulator3.cs @@ -27,8 +27,6 @@ public class BbframeDeencapsulator3 : IBbframeDeencapsulator numPushed++; BBHeader bbHeader = new BBHeader(bbframe, 1); - if (!bbHeader.ChecksumValid) - return; if (!bbHeader.Valid) return; diff --git a/skyscraper8/GS/GSE-HEM/GseHemReader.cs b/skyscraper8/GS/GSE-HEM/GseHemReader.cs new file mode 100644 index 0000000..364e6b3 --- /dev/null +++ b/skyscraper8/GS/GSE-HEM/GseHemReader.cs @@ -0,0 +1,111 @@ +using log4net; +using skyscraper5.Dvb.DataBroadcasting; +using skyscraper5.Skyscraper.IO; +using skyscraper8.GSE.GSE; + +namespace skyscraper8.GSE.GSE_HEM; + +public class GseHemReader : IMisHandler +{ + public GseHemReader(IMultiprotocolEncapsulationEventHandler mpeEventHandler) + { + rayBuffer = new RayBuffer(); + this.mpeEventHandler = mpeEventHandler; + } + + private IMultiprotocolEncapsulationEventHandler mpeEventHandler; + private GseLabel lastLabel; + private RayBuffer rayBuffer; + private const int BUFFER_THRESHOLD = 65536 * 2; //Twice the maximum PDU size of GSE. That way we can guarantee we have one full PDU in buffer. + private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + + public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan) + { + MemoryStream ms = new MemoryStream(readOnlySpan.ToArray()); + rayBuffer.Enqueue(ms, bbHeader.SyncD); + + while (rayBuffer.AvailableBytes > BUFFER_THRESHOLD) + { + byte a = rayBuffer.ReadUInt8(); + bool startIndicator = (a & 0x80) != 0; + bool endIndicator = (a & 0x40) != 0; + int labelTypeIndicator = (a & 0x30) >> 4; + if (!startIndicator && !endIndicator) + { + throw new NotImplementedException("I didn't expect a GSE Padding packet in GSE-HEM. Please share a sample of this stream, so I can implement this."); + } + else + { + GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator); + + int gseLength = (a & 0x0f) << 8; + gseLength += rayBuffer.ReadUInt8(); + if (!startIndicator || !endIndicator) + { + //According to ETSI TS 102 606-1 V1.2.1, HEM does not fragment packets. So we lost our sync! + rayBuffer.Resync(); + return; + } + + if (startIndicator && !endIndicator) + { + throw new NotImplementedException("I didn't expect an open-ended GSE Packet in GSE-HEM. Please share a sample of this stream, so I can implement this."); + } + + if (startIndicator) + { + child.ProtocolType = rayBuffer.ReadUInt16BE(); + gseLength -= 2; + switch (labelTypeIndicator) + { + case 0: + child.Label = new _6byteLabel(rayBuffer.ReadBytes(6)); + gseLength -= 6; + lastLabel = child.Label; + break; + case 1: + child.Label = new _3byteLabel(rayBuffer.ReadBytes(3)); + gseLength -= 3; + lastLabel = child.Label; + break; + case 2: + child.Label = BroadcastLabel.GetInstance(); + break; + case 3: + child.Label = lastLabel; + break; + default: + throw new NotImplementedException(String.Format("Unknown label type indicator: {0}", labelTypeIndicator)); + } + } + + if (!startIndicator && endIndicator) + gseLength -= 4; + + child.GseDataBytes = rayBuffer.ReadBytes(gseLength); + + if (!startIndicator && endIndicator) + throw new NotImplementedException("I didn't expect a GSE packet with a missing head in GSE-HEM. Please share a sample of this stream, so I can implement this."); + + HandlePacket(child); + } + } + } + + private void HandlePacket(GsePacket packet) + { + if (packet.StartIndicator && packet.EndIndicator) + { + switch (packet.ProtocolType) + { + case 0x0800: + mpeEventHandler.OnIpDatagram(0x010e,packet.GseDataBytes); + return; + default: + logger.WarnFormat("This GSE-HEM stream contains traffic other than IP. IP is type 0x0800, but we got 0x{0:X4} here.", packet.ProtocolType); + return; + } + } + throw new NotImplementedException(packet.ToString()); + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GSE-HEM/RayBuffer.cs b/skyscraper8/GS/GSE-HEM/RayBuffer.cs new file mode 100644 index 0000000..4ddf0b9 --- /dev/null +++ b/skyscraper8/GS/GSE-HEM/RayBuffer.cs @@ -0,0 +1,111 @@ +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.GSE.GSE_HEM; + +public class RayBuffer +{ + private MemoryStream currentItem; + private Queue> queue; + + public int QueuedItems + { + get + { + int result = 0; + if (currentItem != null) + result++; + if (queue != null) + result += queue.Count; + return result; + } + } + + public void Enqueue(MemoryStream ms, int syncPoint) + { + if (currentItem == null) + { + currentItem = ms; + currentItem.Position = syncPoint; + return; + } + else + { + if (queue == null) + queue = new Queue>(); + queue.Enqueue(new Tuple(ms, syncPoint)); + } + } + + public long AvailableBytes + { + get + { + if (currentItem == null) + return 0; + + long result = currentItem.GetAvailableBytes(); + if (queue != null) + { + result += queue.Select(x => x.Item1.GetAvailableBytes()).Sum(); + } + + return result; + } + } + +public byte ReadUInt8() + { + byte[] tmp = new byte[1]; + if (Read(tmp, 0, 1) != 1) + throw new IOException("ReadUInt8 failed"); + return tmp[0]; + } + + private int Read(byte[] outBuffer, int offset, int count) + { + int result = 0; + while (count > 0) + { + int stepSize = Math.Min(count, (int)currentItem.GetAvailableBytes()); + int stepResult = currentItem.Read(outBuffer, offset, stepSize); + offset += stepResult; + count -= stepResult; + result += stepResult; + if (currentItem.GetAvailableBytes() == 0) + { + currentItem = queue.Dequeue().Item1; + } + } + + return result; + } + + public ushort ReadUInt16BE() + { + byte[] tmp = new byte[2]; + if (Read(tmp, 0, 2) != 2) + throw new IOException("ReadUInt16BE failed"); + if (BitConverter.IsLittleEndian) + (tmp[0], tmp[1]) = (tmp[1], tmp[0]); + return BitConverter.ToUInt16(tmp, 0); + } + + public byte[] ReadBytes(int p0) + { + byte[] tmp = new byte[p0]; + if (Read(tmp, 0, p0) != p0) + throw new IOException(String.Format("Reading {0} bytes failed.", p0)); + return tmp; + } + + public void Resync() + { + Tuple tuple = queue.Dequeue(); + currentItem = tuple.Item1; + currentItem.Position = tuple.Item2; + if (currentItem.Position > currentItem.Length) + { + Resync(); + } + } +} \ No newline at end of file diff --git a/skyscraper8/GS/GsTypeDetector.cs b/skyscraper8/GS/GsTypeDetector.cs index 8c395e8..bd67e56 100644 --- a/skyscraper8/GS/GsTypeDetector.cs +++ b/skyscraper8/GS/GsTypeDetector.cs @@ -1,5 +1,6 @@ using log4net; using skyscraper5.Dvb.DataBroadcasting; +using skyscraper8.GSE.GSE_HEM; using skyscraper8.GSE.GSE; namespace skyscraper8.GSE; @@ -17,8 +18,14 @@ public class GsTypeDetector : IMisHandler } private GseReader gseReader; + private GseHemReader gseHemReader; public void PushFrame(BBHeader bbHeader, ReadOnlySpan readOnlySpan) { + if (readOnlySpan.Length == 0) + { + return; + } + if (bbHeader.TsGs == 1 && bbHeader.SyncByte == 0) { //Looks like Continuous GSE. @@ -31,6 +38,17 @@ public class GsTypeDetector : IMisHandler gseReader.PushFrame(bbHeader, readOnlySpan); return; } + + if (bbHeader.TsGs == 2 && bbHeader.SyncByte == 0) + { + //Looks like GSE-HEM + if (gseHemReader == null) + { + gseHemReader = new GseHemReader(mpeEventHandler); + } + gseHemReader.PushFrame(bbHeader, readOnlySpan); + return; + } //We have no idea what this is. Tuple streamTypeToPost = new Tuple(bbHeader.TsGs, bbHeader.SyncByte); diff --git a/skyscraper8/GS/POC/Stid135Test.cs b/skyscraper8/GS/POC/Stid135Test.cs index 5b6b28e..0ec03c7 100644 --- a/skyscraper8/GS/POC/Stid135Test.cs +++ b/skyscraper8/GS/POC/Stid135Test.cs @@ -1,6 +1,7 @@ using log4net; using skyscraper5.Mpeg2; using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.Storage.Filesystem; using skyscraper5.Skyscraper.Scraper.Storage.InMemory; using skyscraper8.Skyscraper.Scraper.Storage; @@ -15,7 +16,7 @@ public class Stid135Test TsContext mpeg2 = new TsContext(); DataStorage dataStorage = new InMemoryScraperStorage(); - ObjectStorage objectStorage = new NullObjectStorage(); + ObjectStorage objectStorage = new FilesystemStorage(new DirectoryInfo("nip")); SkyscraperContext skyscraper = new SkyscraperContext(mpeg2, dataStorage, objectStorage); mpeg2.RegisterPacketProcessor(0x010e, new Stid135BbFrameReader(skyscraper)); diff --git a/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs index f452b81..5a51573 100644 --- a/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs +++ b/skyscraper8/Mpeg2/PacketFilter/ScrambleFilter.cs @@ -17,7 +17,12 @@ namespace skyscraper5.src.Mpeg2.PacketFilter { protected override bool PassPacketEx(TsPacket packet) { - return packet.TSC == 0; + if (packet.TSC == 0) + return true; + else + { + return false; + } } } } diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index 09db3be..5a9c125 100644 --- a/skyscraper8/Program.cs +++ b/skyscraper8/Program.cs @@ -41,7 +41,7 @@ namespace skyscraper5 class Program { private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); - private const int PUBLIC_RELEASE = 9; + private const int PUBLIC_RELEASE = 10; private static void IntegrationTest() { /*List ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList(); diff --git a/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs index 939af83..bad78b9 100644 --- a/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs +++ b/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs @@ -74,12 +74,11 @@ namespace skyscraper8.Skyscraper.Scraper.Storage public bool DvbNipTestForFile(string announcedFileContentLocation) { - throw new NotImplementedException(); + return true; } public void DvbNipFileArrival(NipActualCarrierInformation carrier, FluteListener listener) { - throw new NotImplementedException(); } public void StoreIqGraph(Guid jobGuid, long frequency, char polarity, IqChartData plot) diff --git a/skyscraper8/log4net.config b/skyscraper8/log4net.config index 9755f52..366341b 100644 --- a/skyscraper8/log4net.config +++ b/skyscraper8/log4net.config @@ -13,15 +13,15 @@ - + + - + --> \ No newline at end of file From 3342141539baea71a7e871a6f41bdf9b13bb31a2 Mon Sep 17 00:00:00 2001 From: feyris-tan Date: Thu, 16 Oct 2025 16:34:22 +0200 Subject: [PATCH 3/3] Added a bit of skyscraper8 trivia. --- skyscraper8.Manual/skyscraper8.Manual.tex | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/skyscraper8.Manual/skyscraper8.Manual.tex b/skyscraper8.Manual/skyscraper8.Manual.tex index f27f2a4..e30f9a0 100644 --- a/skyscraper8.Manual/skyscraper8.Manual.tex +++ b/skyscraper8.Manual/skyscraper8.Manual.tex @@ -177,6 +177,15 @@ This document was typeset in \LaTeX{}, using the TeX Live distribution, and whil \subsection{Personal remarks and some useless bonus information} +\subsubsection{How and why skyscraper8 began: A young man's dream} +Ever since I was a child, I was fascinated by TV. But not actually watching it, rather understanding how it works. I grew up in the 90s, in an area where Cable TV was not really available, and terristial reception (yeah, those TVs with bunny ears!) only worked when it felt like it. So satellite was the best way to get my childish fix of cartoons! We can say the dish on my roof has always been a companion. Far more interesting than actually watching TV was scanning through the printed TV guides. I was curious. How did they make these? And how would I make one myself? I wondered. Of course my parents wouldn't know - they're not techies. \\ + +Of course, as I got older, I eventually moved onto other things. Like programming, trading cards, and video games. But just like with the TV, I eventually got more interested in how these games work, instead of actually playing them. \\ + +I got internet access much later in my life than my peers did, and when I found out that there are forums in which people discuss the technicalities about video games, it blew my mind. I used to be a frequent reader (and occasional contributor) of the XeNTaX\footnote{Unfortunately, that forum is long gone from the internet, but can still be experienced thanks to the amazing work of the people over at the Internet Archive: \url{https://web.archive.org/web/20230925120533/https://forum.xentax.com/ }} forum, from where I learned a lot! \\ + +At the time, my circle of friends was crazy about Yu-Gi-Oh! Online 3, which was an excellent simulation of the trading card game by the same name. + \subsubsection{Music!} Like a lot of programmers, I do enjoy listening to music while working. Some programmers even put song references in their software. Like how MKVToolnix' version names are actually song names, or how BSD developer fk even put \href{https://www.fabiankeil.de/nutzloseinfos.html}{a list of albums on his website} listing what albums he listened to while making it. Although this is absolutely useless, I'd liketo do this as well. Therefore, here follows a list of musical albums I listened to while developing skyscraper8 - no claim to completeness.