From e8893f85ce39135ec1146491f72fac3639c8a665 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:11:12 +0200 Subject: [PATCH] Added support for LLDP in ULE's bridged frame mode. --- .gitignore | 1 + .../TSDuck-Samples/experiment2/run.sh | 22 +++ .../TSDuck-Samples/experiment2/tdt.xml | 4 + skyscraper8.Manual/skyscraper8.Manual.tex | 48 +++++- skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs | 13 ++ skyscraper8/Ieee802_1AB/LldpFrame.cs | 154 +++++++++++++++++ .../Ieee802_1AB/Model/LldpCapabilities.cs | 36 ++++ skyscraper8/Ieee802_1AB/Model/LldpChassis.cs | 57 +++++++ .../Model/LldpMacPhyConfigurationStatus.cs | 27 +++ .../Model/LldpManagementAddress.cs | 37 ++++ .../Model/LldpMediaCapabilities.cs | 28 ++++ skyscraper8/Ieee802_1AB/Model/LldpPort.cs | 79 +++++++++ skyscraper8/Program.cs | 2 +- skyscraper8/Properties/launchSettings.json | 2 +- skyscraper8/Skyscraper/MpeEject.cs | 158 ++++++++++++++++++ .../Skyscraper/Scraper/SkyscraperContext.cs | 21 ++- .../Scraper/SkyscraperContextEvent.cs | 3 +- 17 files changed, 685 insertions(+), 7 deletions(-) create mode 100644 Documentation/TSDuck-Samples/experiment2/run.sh create mode 100644 Documentation/TSDuck-Samples/experiment2/tdt.xml create mode 100644 skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs create mode 100644 skyscraper8/Ieee802_1AB/LldpFrame.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpChassis.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs create mode 100644 skyscraper8/Ieee802_1AB/Model/LldpPort.cs create mode 100644 skyscraper8/Skyscraper/MpeEject.cs diff --git a/.gitignore b/.gitignore index c9f2959..8130e63 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ imgui.ini /skyscraper8.Manual/skyscraper8.Manual.synctex.gz /.vs/skyscraper8/CopilotIndices/17.14.1231.31060 /.vs/skyscraper8/CopilotIndices/17.14.1290.42047 +/Documentation/TSDuck-Samples/experiment2/*.ts diff --git a/Documentation/TSDuck-Samples/experiment2/run.sh b/Documentation/TSDuck-Samples/experiment2/run.sh new file mode 100644 index 0000000..50e2a1b --- /dev/null +++ b/Documentation/TSDuck-Samples/experiment2/run.sh @@ -0,0 +1,22 @@ +BITRATE=6000000 +PCR_DISTANCE=6000 +PCR_PID=1001 + +tsp -v -b $BITRATE \ + -I null \ + -P regulate --packet-burst 14 \ + -P filter --every $PCR_DISTANCE --set-label 1 \ + -P craft --only-label 1 --pid $PCR_PID --no-payload --pcr 0 \ + -P continuity --pid $PCR_PID --fix \ + -P pcradjust --pid $PCR_PID \ + -P pat -c -a 1/1000 \ + -P pmt -c -i 1 --pcr-pid $PCR_PID -p 1000 -a 1002/0x0d -a 1003/0x0d -a 1004/0x0d \ + --add-stream-identifier --set-data-broadcast-id 1002/5 \ + --set-data-broadcast-id 1003/5 --set-data-broadcast-id 1004/5 \ + -P inject tdt.xml --pid 0x14 --bitrate 2000 --stuffing \ + -P timeref --system-synchronous \ + -P mpeinject -p 1002 -l 127.0.0.2 2000 --max-queue 8192 \ + -P mpeinject -p 1003 -l 127.0.0.2 3000 --max-queue 8192 \ + -P history \ + -P until -s 60 \ + -O file --max-size 100000000 experiment2-clean-60s.ts diff --git a/Documentation/TSDuck-Samples/experiment2/tdt.xml b/Documentation/TSDuck-Samples/experiment2/tdt.xml new file mode 100644 index 0000000..7f831e4 --- /dev/null +++ b/Documentation/TSDuck-Samples/experiment2/tdt.xml @@ -0,0 +1,4 @@ + + + + diff --git a/skyscraper8.Manual/skyscraper8.Manual.tex b/skyscraper8.Manual/skyscraper8.Manual.tex index 025035a..ff9d052 100644 --- a/skyscraper8.Manual/skyscraper8.Manual.tex +++ b/skyscraper8.Manual/skyscraper8.Manual.tex @@ -6,6 +6,7 @@ \usepackage{amssymb} \usepackage{xcolor} \usepackage{pifont,mdframed} +\usepackage{verbatim} \usepackage{geometry} @@ -186,12 +187,55 @@ If you are using Microsoft Windows, you can also Drag’n’Drop a TS file onto \section{How skyscraper8 was made} skyscraper8 is developed in the C\# programming language, using Microsoft Visual Studio 2022. Therefore it requires a Microsoft .NET runtime, which is included in Microsoft Windows and freely available for most Linux distributions and Apple's macOS. -The .NET assembly of skyscraper8 is not obfuscated or protected in any way. This is on purpose. If you want to study how skyscraper8 works under the hood, I hereby allow you to use tools like RedGate's Reflector or JetBrains' dotPeek to inspect the skyscraper8.dll file. I plan to release the clear source code at a later date. +The .NET assembly of skyscraper8 is not obfuscated or protected in any way. This is on purpose. If you want to study how skyscraper8 works under the hood, I hereby allow you to use tools like RedGate's Reflector or JetBrains' dotPeek to inspect the skyscraper8.dll file. I plan to release the clear source code at a later date. \\ + +Although I can't and won't force you to give credit, if you find some of my source code inspiring or helpful and want to use it in your own projects, it would be great if you give credit to where you got it from. \\ + +This document was typeset in \LaTeX{}, using the TeX Live distribution, and while I wrote it myself, I did use GPT-5 for proofreading the initial version of this document. The proofreading of this document is the only part of skyscraper8 for which an LLM was used. No part of the actual skyscraper8 codebase itself was written by an LLM. \\ + +For generating artificial TS recordings to test on, TSDuck was used. \\ + +\subsection{Experiments conducted using TSDuck} + +\subsubsection {Creating a TS that carried multiple sub-TS using MPE} + +Since skyscraper8 public release 12, it's possible to have skyscraper8 extract one or more TS carried via Multiprotocol Encapsulation. Unfortunately the dish I have at home is not aimed at satellites carrying such a stream. So I used TSDuck to build one artificially. \\ + +The following script was used to to run \codeword{tsp}: + +\verbatiminput{../Documentation/TSDuck-Samples/experiment2/run.sh} + +Contents of tdt.xml: + +\verbatiminput{../Documentation/TSDuck-Samples/experiment2/tdt.xml} + +Before executing that script, run this beforehand, to get a stream of Stingray Spa, of course you can use any other IPTV station you'd like: + +\begin{verbatim} +tsp -v -I hls https://lotus.stingray.com/manifest/ose-122ads-montreal/samsungtvplus/master.m3u8 \ + -P regulate -P history -O ip 127.0.0.2:3000 +\end{verbatim} + +For the other stream, I used Offener Kanal Berlin, like so: + +\begin{verbatim} +tsp -v -I hls https://alex-stream.rosebud-media.de/bounce/alexlivetv50.smil/index.m3u8 \ + -P regulate -P history -O ip 127.0.0.2:2000 +\end{verbatim} + +Finally, run the script above to get a clean TS containing two other TS encapsulated in MPE. -This document was typeset in \LaTeX{}, using the TeX Live distribution, and while I wrote it myself, I did use GPT-5 for proofreading the initial version of this document. The proofreading of this document is the only part of skyscraper8 for which an LLM was used. No part of the actual skyscraper8 codebase itself was written by an LLM. \subsection{Personal remarks and some useless bonus information} +\subsubsection{Credits} +skyscraper8 uses some external libraries: +\begin{itemize} + \item One of the external libraries is Ionic.Zlib.Core.dll, which is part of the \href{https://github.com/DinoChiesa/DotNetZip-2025}{DotNetZip} tool set by Dino Chiesa. + \item Also used is Newtonsoft.Json.dll a.k.a. \href{https://github.com/JamesNK/Newtonsoft.Json}{Json.NET} by James Newton-King. + \item Some of the GS handling code got inspired from the \href{https://github.com/newspaperman/bbframe-tools}{bbframe-tools} written by newspaperman. +\end{itemize} + \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. \\ diff --git a/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs b/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs new file mode 100644 index 0000000..3d2487e --- /dev/null +++ b/skyscraper8/Ieee802_1AB/ILldpFrameHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Ieee802_1AB +{ + internal interface ILldpFrameHandler + { + void OnLldpFrame(LldpFrame frame); + } +} diff --git a/skyscraper8/Ieee802_1AB/LldpFrame.cs b/skyscraper8/Ieee802_1AB/LldpFrame.cs new file mode 100644 index 0000000..d9492b1 --- /dev/null +++ b/skyscraper8/Ieee802_1AB/LldpFrame.cs @@ -0,0 +1,154 @@ +using System.Text; +using skyscraper5.Skyscraper.IO; +using skyscraper8.Ieee802_1AB.Model; + +namespace skyscraper8.Ieee802_1AB; + +public class LldpFrame +{ + public LldpFrame(byte[] contents) + { + MemoryStream ms = new MemoryStream(contents, false); + while (ms.Position < ms.Length) + { + ushort tlv = ms.ReadUInt16BE(); + int type = (tlv & 0xfe00) >> 9; + int length = (tlv & 0x01ff); + byte[] value = ms.ReadBytes(length); + switch (type) + { + case 1: //Chassis + this.Chassis = new LldpChassis(value); + break; + case 2: //Port + this.Port = new LldpPort(value); + break; + case 3: //Time to Live + this.TimeToLive = (value[0] << 8) | value[1]; + this.TimeToLive--; + break; + case 4: + this.PortDescription = Encoding.UTF8.GetString(value); + break; + case 5: + this.SystemName = Encoding.UTF8.GetString(value); + break; + case 6: + this.SystemDescription = Encoding.UTF8.GetString(value); + break; + case 7: + this.Capabilities = new LldpCapabilities(value); + break; + case 8: + this.ManagementAddress = new LldpManagementAddress(value); + break; + case 127: + string OrganizationUniqueCode = BitConverter.ToString(value, 0, 3); + byte OrganizationallyDefinedSubtype = value[3]; + MemoryStream payload = new MemoryStream(value, 4, value.Length - 4); + switch (OrganizationUniqueCode) + { + case "00-80-C2": //IEEE 802.1 Chair + switch (OrganizationallyDefinedSubtype) + { + case 6: //VLAN ID + this.PortVlanIdentifier = ms.ReadUInt16BE(); + break; + default: + throw new NotImplementedException(String.Format("Unknown IEEE 802.1 Subtype {0}! Please share a sample of this stream.",OrganizationallyDefinedSubtype)); + } + break; + case "00-12-0F": //IEEE 802.3 Chair + switch (OrganizationallyDefinedSubtype) + { + //see 802.1AB-2009.pdf, Annex F + case 1: //MAC/PHY configuration/status TLV format + this.MacPhyConfigurationStatus = new LldpMacPhyConfigurationStatus(payload); + break; + default: + throw new NotImplementedException(String.Format("Unknown IEEE 802.3 Subtype {0}! Please share a sample of this stream.", OrganizationallyDefinedSubtype)); + } + break; + case "00-12-BB": + switch (OrganizationallyDefinedSubtype) //TR-41. Don't know which document yet. + { + case 1: //Media Capabilities + this.MediaCapabilities = new LldpMediaCapabilities(payload); + break; + default: + throw new NotImplementedException(String.Format("Unknown TR-41 Subtype {0}! Please share a sample of this stream.", OrganizationallyDefinedSubtype)); +} + break; + default: + throw new NotImplementedException(String.Format("Unknown Organization Unique Code {0}. Please share a sample of this stream.",OrganizationUniqueCode)); + } + + break; + case 0: //End of LLDP PDU + return; + default: + throw new NotImplementedException( String.Format("LLDP Option {0} not yet supported. Please share a sample of this stream.",type)); + } + } + } + + public LldpMediaCapabilities MediaCapabilities { get; set; } + + public LldpMacPhyConfigurationStatus MacPhyConfigurationStatus { get; set; } + + public ushort? PortVlanIdentifier { get; set; } + + public LldpManagementAddress ManagementAddress { get; private set; } + + public LldpCapabilities Capabilities { get; set; } + + public string SystemDescription { get; set; } + + public string SystemName { get; set; } + + public string PortDescription { get; set; } + + public int? TimeToLive { get; set; } + + public LldpPort Port { get; set; } + + public LldpChassis Chassis { get; private set; } + + protected bool Equals(LldpFrame other) + { + return Port.Equals(other.Port) && Chassis.Equals(other.Chassis); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((LldpFrame)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Port, Chassis); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + if (!string.IsNullOrEmpty(SystemName)) + { + sb.Append(SystemName); + } + else + { + sb.Append(Chassis.ToString()); + } + + sb.Append(", "); + sb.Append(Port.ToString()); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs b/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs new file mode 100644 index 0000000..80e0a88 --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpCapabilities.cs @@ -0,0 +1,36 @@ +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model; + +public class LldpCapabilities +{ + private readonly ushort capabilities; + private readonly ushort enabledCapabilities; + + public LldpCapabilities(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + this.capabilities = ms.ReadUInt16BE(); + this.enabledCapabilities = ms.ReadUInt16BE(); + } + + public bool HasOther => (capabilities & 0x0001) != 0; + public bool HasRepeater => (capabilities & 0x0002) != 0; + public bool HasBridge => (capabilities & 0x0004) != 0; + public bool HasWlanAccessPoint => (capabilities & 0x0008) != 0; + public bool HasRouter => (capabilities & 0x0010) != 0; + public bool HasTelephone => (capabilities & 0x0020) != 0; + public bool HasDocsis => (capabilities & 0x0040) != 0; + public bool IsStation => (capabilities & 0x0080) != 0; + public bool HasCVLan => (capabilities & 0x0100) != 0; + public bool HasSVLan => (capabilities & 0x0200) != 0; + public bool HasTpmr => (capabilities & 0x0400) != 0; + + public bool EnabledOther => (enabledCapabilities & 0x0001) != 0; + public bool EnabledRepeater => (enabledCapabilities & 0x0002) != 0; + public bool EnabledBridge => (enabledCapabilities & 0x0004) != 0; + public bool EnabledWlanAccessPoint => (enabledCapabilities & 0x0008) != 0; + public bool EnabledRouter => (enabledCapabilities & 0x0010) != 0; + public bool EnabledTelephone => (enabledCapabilities & 0x0020) != 0; + public bool EnabledDocsis => (enabledCapabilities & 0x0040) != 0; +} \ No newline at end of file diff --git a/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs b/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs new file mode 100644 index 0000000..69b96ce --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpChassis.cs @@ -0,0 +1,57 @@ +using System.Net.NetworkInformation; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model; + +public class LldpChassis +{ + public LldpChassis(byte[] value) + { + MemoryStream ms = new MemoryStream(value); + ChassisSubtype = ms.ReadUInt8(); + switch (ChassisSubtype) + { + case 4: + MacAddress = new PhysicalAddress(ms.ReadBytes(6)); + break; + default: + throw new NotImplementedException(string.Format("Chassis Subtype {0} not yet supported. Please share a sample of this stream.", ChassisSubtype)); + } + } + + public PhysicalAddress MacAddress { get; set; } + + public byte ChassisSubtype { get; private set; } + + protected bool Equals(LldpChassis other) + { + return MacAddress.Equals(other.MacAddress) && ChassisSubtype == other.ChassisSubtype; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((LldpChassis)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(MacAddress, ChassisSubtype); + } + + public override string ToString() + { + switch (ChassisSubtype) + { + case 4: + return MacAddress.ToString(); + default: + return String.Format("Chassis Subtype {0}", ChassisSubtype); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs b/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs new file mode 100644 index 0000000..5f603e5 --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpMacPhyConfigurationStatus.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model +{ + public class LldpMacPhyConfigurationStatus + { + private readonly byte autoNegotioationStatus; + private readonly ushort pmdAutoNegotiation; + + public LldpMacPhyConfigurationStatus(MemoryStream ms) + { + autoNegotioationStatus = ms.ReadUInt8(); + pmdAutoNegotiation = ms.ReadUInt16BE(); + OperationalMauType = ms.ReadUInt16BE(); + } + + public ushort OperationalMauType { get; private set; } + + public bool AutonegotiationSupported => (autoNegotioationStatus & 0x01) != 0; + public bool AutonegotiationStatus => (autoNegotioationStatus & 0x02) != 0; + } +} diff --git a/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs b/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs new file mode 100644 index 0000000..b78ccd2 --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpManagementAddress.cs @@ -0,0 +1,37 @@ +using System.Net; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model; + +public class LldpManagementAddress +{ + public LldpManagementAddress(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer, false); + byte managementAddressStringLength = ms.ReadUInt8(); + ManagementAddressSubtype = ms.ReadUInt8(); + managementAddressStringLength--; + + if (ManagementAddressSubtype != 1) + { + throw new NotImplementedException("Unknown management address subtype. Please share a sample of this stream."); + } + + ManagementAddress = new IPAddress(ms.ReadBytes(managementAddressStringLength)); + InterfaceNumberingSubtype = ms.ReadUInt8(); + InterfaceNumber = ms.ReadUInt32BE(); + + byte oidStringLength = ms.ReadUInt8(); + ObjectIdentifier = ms.ReadBytes(oidStringLength); + } + + public byte[] ObjectIdentifier { get; set; } + + public uint InterfaceNumber { get; set; } + + public byte InterfaceNumberingSubtype { get; set; } + + public byte ManagementAddressSubtype { get; set; } + + public IPAddress ManagementAddress { get; set; } +} \ No newline at end of file diff --git a/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs b/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs new file mode 100644 index 0000000..acad66d --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpMediaCapabilities.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model +{ + public class LldpMediaCapabilities + { + private readonly ushort capabilities; + public byte NetworkConnectivity { get; set; } + + public LldpMediaCapabilities(MemoryStream ms) + { + capabilities = ms.ReadUInt16BE(); + NetworkConnectivity = ms.ReadUInt8(); + } + + public bool LldpMedCapable => (capabilities & 0x0001) != 0; + public bool NetworkPolicyCapable => (capabilities & 0x0002) != 0; + public bool LocationIdentificationCapable => (capabilities & 0x0004) != 0; + public bool ExtendedPowerViaMdiPse => (capabilities & 0x0008) != 0; + public bool ExtendedPowerViaMdiPd => (capabilities & 0x0010) != 0; + public bool InventoryCapable => (capabilities & 0x0020) != 0; + } +} diff --git a/skyscraper8/Ieee802_1AB/Model/LldpPort.cs b/skyscraper8/Ieee802_1AB/Model/LldpPort.cs new file mode 100644 index 0000000..8643bc4 --- /dev/null +++ b/skyscraper8/Ieee802_1AB/Model/LldpPort.cs @@ -0,0 +1,79 @@ +using System.Net.NetworkInformation; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ieee802_1AB.Model; + +public class LldpPort +{ + public LldpPort(byte[] buffer) + { + MemoryStream ms = new MemoryStream(buffer); + Subtype = ms.ReadUInt8(); + switch (Subtype) + { + case 3: + MacAddress = new PhysicalAddress(ms.ReadBytes(6)); + break; + case 5: + PortId = ms.ReadUTF8FixedLength((int)ms.GetAvailableBytes()); + break; + default: + throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype)); + } + } + + public PhysicalAddress MacAddress { get; set; } + public string PortId { get; set; } + public byte Subtype { get; private set; } + + protected bool Equals(LldpPort other) + { + switch (Subtype) + { + case 3: + return MacAddress.Equals(other.MacAddress) && Subtype == other.Subtype; + case 5: + return PortId == other.PortId && Subtype == other.Subtype; + default: + throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype)); + } + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((LldpPort)obj); + } + + public override int GetHashCode() + { + switch (Subtype) + { + case 3: + return HashCode.Combine(PortId, MacAddress); + case 5: + return HashCode.Combine(PortId, Subtype); + default: + throw new NotImplementedException(String.Format("Unknown Port Subtype {0}. Please share a sample of this stream.", Subtype)); + } + + } + + public override string ToString() + { + switch (Subtype) + { + case 3: + return MacAddress.ToString(); + case 5: + return PortId; + default: + return String.Format("Port Subtype {0}", Subtype); + } + } +} \ No newline at end of file diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index 8ff1613..cff18c6 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 = 11; + private const int PUBLIC_RELEASE = 12; private static void IntegrationTest() { /*List ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList(); diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json index 251e8d8..5d5c369 100644 --- a/skyscraper8/Properties/launchSettings.json +++ b/skyscraper8/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "skyscraper8": { "commandName": "Project", - "commandLineArgs": "subts-on", + "commandLineArgs": "\"D:\\sorglos-iww-rww-aca-oca.ts\"", "remoteDebugEnabled": false }, "Container (Dockerfile)": { diff --git a/skyscraper8/Skyscraper/MpeEject.cs b/skyscraper8/Skyscraper/MpeEject.cs new file mode 100644 index 0000000..881b9cb --- /dev/null +++ b/skyscraper8/Skyscraper/MpeEject.cs @@ -0,0 +1,158 @@ +using System.Net; +using log4net; +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.Plugins; +using skyscraper8.Skyscraper.Net; +using skyscraper8.Skyscraper.Scraper; +using skyscraper8.Skyscraper.Scraper.Storage; + +namespace skyscraper8.Skyscraper; + +/// +/// This plugin does the opposite of what TSDuck's MpeInject does. +/// +[SkyscraperPlugin] +public class MpeEject : ISkyscraperMpePlugin +{ + private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + + public void ConnectToStorage(object[] connector) + { + throw new NotImplementedException(); + } + + private ISubTsHandler subTsHandler; + public void SetContext(DateTime? currentTime, object skyscraperContext) + { + subTsHandler = skyscraperContext as ISubTsHandler; + if (subTsHandler == null) + { + logger.ErrorFormat("Couldn't get a handle for the SubTsHandler. Deencapsulation will not work."); + } + } + + public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (internetHeader.Protocol != 0x11) + return false; + + int payloadLength = ipv4Packet.Length - 8; + int tsCheck = payloadLength % 188; + return tsCheck == 0; + } + + private Dictionary, MpeEjectSession> sessions; + private bool lastOutputSucessful; + public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (subTsHandler == null) + { + lastOutputSucessful = false; + return; + } + + UserDatagram userDatagram = new UserDatagram(ipv4Packet); + IPEndPoint sourceEndpoint = new IPEndPoint(internetHeader.SourceAddress, userDatagram.SourcePort); + IPEndPoint targetEndpoint = new IPEndPoint(internetHeader.DestinationAddress, userDatagram.DestinationPort); + Tuple sessionPointer = new Tuple(sourceEndpoint, targetEndpoint); + + if (sessions == null) + sessions = new Dictionary, MpeEjectSession>(); + + MpeEjectSession session = null; + if (sessions.ContainsKey(sessionPointer)) + { + session = sessions[sessionPointer]; + if (session.Bootstrapped && !session.Valid) + { + lastOutputSucessful = false; + return; + } + } + else + { + session = new MpeEjectSession(sourceEndpoint, targetEndpoint); + sessions.Add(sessionPointer, session); + } + + if (userDatagram.Payload.Length == 188) + { + if (!session.Bootstrapped) + { + lastOutputSucessful = false; + return; + } + + if (!session.Valid) + { + lastOutputSucessful = false; + return; + } + + subTsHandler.OnSubTsPacket(session,userDatagram.Payload); + lastOutputSucessful = true; + return; + } + + for (int i = 0; i < userDatagram.Payload.Length; i += 188) + { + if (userDatagram.Payload[i] != 0x47) + { + if (session.Valid) + { + logger.WarnFormat("Lost TS sync on stream for: {0} -> {1}", sourceEndpoint, targetEndpoint); + } + session.Bootstrapped = true; + session.Valid = false; + lastOutputSucessful = false; + return; + } + } + + session.ValidDatagrams++; + if (session.ValidDatagrams >= 10) + { + session.Bootstrapped = true; + session.Valid = true; + byte[] packetBuffer = new byte[188]; + for (int i = 0; i < userDatagram.Payload.Length; i += 188) + { + Array.Copy(userDatagram.Payload, i, packetBuffer, 0, 188); + subTsHandler.OnSubTsPacket(session, packetBuffer); + lastOutputSucessful = true; + } + } + else + { + lastOutputSucessful = false; + } + } + + public bool StopProcessingAfterThis() + { + return lastOutputSucessful; + } +} + +public class MpeEjectSession +{ + public IPEndPoint SourceEndpoint { get; } + public IPEndPoint DestinationEndpoint { get; } + + public MpeEjectSession(IPEndPoint sourceEndpoint, IPEndPoint destinationEndpoint) + { + SourceEndpoint = sourceEndpoint; + DestinationEndpoint = destinationEndpoint; + } + public bool Bootstrapped { get; set; } + public bool Valid { get; set; } + + public override string ToString() + { + return String.Format("{0} -> {1}", SourceEndpoint, DestinationEndpoint).SanitizeFileName(); + } + + public int ValidDatagrams { get; set; } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs index 0bc58f0..46a3a2f 100644 --- a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs @@ -81,6 +81,7 @@ using skyscraper8.Abertis; using skyscraper8.Experimentals.NdsSsu; using skyscraper8.Experimentals.OtvSsu; using skyscraper8.GSE; +using skyscraper8.Ieee802_1AB; using skyscraper8.InteractionChannel.Model2; using skyscraper8.Skyscraper.Net; using skyscraper8.Skyscraper.Scraper; @@ -97,7 +98,7 @@ namespace skyscraper5.Skyscraper.Scraper UpdateNotificationEventHandler, DataCarouselEventHandler, RdsEventHandler, IScte35EventHandler, IAutodetectionEventHandler, IRstEventHandler, IRntEventHandler, IMultiprotocolEncapsulationEventHandler, ObjectCarouselEventHandler, T2MIEventHandler, IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler, - InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler, OtvSsuHandler, NdsSsuHandler, ISubTsHandler + InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler, OtvSsuHandler, NdsSsuHandler, ISubTsHandler, ILldpFrameHandler { public const bool ALLOW_STREAM_TYPE_AUTODETECTION = true; public const bool ALLOW_FFMPEG_FRAMEGRABBER = true; @@ -2930,6 +2931,11 @@ namespace skyscraper5.Skyscraper.Scraper return; case 0x3e30: //Something proprietary. No idea. + return; + case 0x88cc: + //Link Layer Discovery Protocol + LldpFrame lldpFrame = new LldpFrame(contents); + OnLldpFrame(lldpFrame); return; default: throw new NotImplementedException(String.Format("EtherType: 0x{0:X4}", etherType)); @@ -3076,6 +3082,17 @@ namespace skyscraper5.Skyscraper.Scraper //TODO: Implement this. } - + + private HashSet lldpFrames; + public void OnLldpFrame(LldpFrame frame) + { + if (lldpFrames == null) + lldpFrames = new HashSet(); + + if (lldpFrames.Add(frame)) + { + LogEvent(SkyscraperContextEvent.EthernetLinkLayerDiscovery, frame.ToString()); + } + } } } diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs index 8d3661a..cdb8139 100644 --- a/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContextEvent.cs @@ -88,6 +88,7 @@ NdsSsuProgress, TerminalBurstTimePlan2, FrameComposition2, - BroadcastConfiguration + BroadcastConfiguration, + EthernetLinkLayerDiscovery } }