diff --git a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs index caf8aa8..c551c4e 100644 --- a/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs +++ b/skyscraper8/Dvb/DataBroadcasting/MultiprotocolEncapsulationDecoder.cs @@ -134,7 +134,7 @@ namespace skyscraper5.Dvb.DataBroadcasting { if (!announcedLlcTraffic) { - logger.InfoFormat("Detected LLC/SNAP traffic on PID {0:x4}", sourcePid); + //logger.InfoFormat("Detected LLC/SNAP traffic on PID {0:x4}", sourcePid); announcedLlcTraffic = true; } diff --git a/skyscraper8/Ietf/FLUTE/CdsCompletionPollRequestExtension.cs b/skyscraper8/Ietf/FLUTE/CdsCompletionPollRequestExtension.cs new file mode 100644 index 0000000..b27a8a9 --- /dev/null +++ b/skyscraper8/Ietf/FLUTE/CdsCompletionPollRequestExtension.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Ietf.FLUTE +{ + internal class CdsCompletionPollRequestExtension : Validatable + { + public CdsCompletionPollRequestExtension(byte[] extensionBuffer) + { + MemoryStream ms = new MemoryStream(extensionBuffer, false); + if (ms.GetAvailableBytes() < 1) + { + Valid = false; + return; + } + + byte byteA = ms.ReadUInt8(); + this.Version = (byteA & 0xf0) >> 4; + + if (ms.GetAvailableBytes() < 1) + { + Valid = false; + return; + } + + byte byteB = ms.ReadUInt8(); + PollSequence = (byteA & 0x0f) << 8; + PollSequence += byteB; + + if (ms.GetAvailableBytes() < 4) + { + Valid = false; + return; + } + + PollMask = ms.ReadUInt32BE(); + Valid = true; + } + + public uint PollMask { get; set; } + + public int PollSequence { get; set; } + + public int Version { get; private set; } + } +} diff --git a/skyscraper8/Ietf/FLUTE/FecHeader.cs b/skyscraper8/Ietf/FLUTE/FecHeader.cs index 423d599..c60969e 100644 --- a/skyscraper8/Ietf/FLUTE/FecHeader.cs +++ b/skyscraper8/Ietf/FLUTE/FecHeader.cs @@ -9,13 +9,28 @@ namespace skyscraper8.Ietf.FLUTE { internal class FecHeader { - public FecHeader(MemoryStream ms) + public FecHeader(MemoryStream ms, byte codepoint) { - SourceBlockNumber = ms.ReadUInt16BE(); - EncodingSymbolId = ms.ReadUInt16BE(); + switch (codepoint) + { + case 0: + SourceBlockNumber = ms.ReadUInt16BE(); + EncodingSymbolId = ms.ReadUInt16BE(); + break; + case 129: + SourceBlockNumber = ms.ReadUInt32BE(); + SourceBlockLength = ms.ReadUInt16BE(); + EncodingSymbolId = ms.ReadUInt16BE(); + break; + default: + throw new NotImplementedException(String.Format("Unknown ALC CodePoint value {0}", codepoint)); + } + } - public ushort SourceBlockNumber { get; } + public ushort? SourceBlockLength { get; set; } + + public uint SourceBlockNumber { get; } public ushort EncodingSymbolId { get; } } } diff --git a/skyscraper8/Ietf/FLUTE/FluteListener.cs b/skyscraper8/Ietf/FLUTE/FluteListener.cs index a0abaec..9c17179 100644 --- a/skyscraper8/Ietf/FLUTE/FluteListener.cs +++ b/skyscraper8/Ietf/FLUTE/FluteListener.cs @@ -72,13 +72,13 @@ namespace skyscraper8.Ietf.FLUTE } if (blocks == null) - blocks = new Dictionary, FluteBlock>(); + blocks = new Dictionary, FluteBlock>(); if (lctFrame.Atsc3Compliant) { if (blocks.Count == 0) { - Tuple fakeKey = new Tuple(12345, 6789); + Tuple fakeKey = new Tuple(12345, 6789); FluteBlock fakeBlock = new FluteBlock(0, 0, new byte[transferLength]); blocks.Add(fakeKey, fakeBlock); } @@ -92,7 +92,7 @@ namespace skyscraper8.Ietf.FLUTE if (lctFrame.FecHeader == null) return; - ushort sbn = lctFrame.FecHeader.SourceBlockNumber; + uint sbn = lctFrame.FecHeader.SourceBlockNumber; ushort esi = lctFrame.FecHeader.EncodingSymbolId; //FluteBlock? fluteBlock = blocks.Find(x => @@ -122,7 +122,7 @@ namespace skyscraper8.Ietf.FLUTE blocksDeduplicated++; }*/ - Tuple coordinate = new Tuple(sbn, esi); + Tuple coordinate = new Tuple(sbn, esi); if (blocks.ContainsKey(coordinate)) { blocksDeduplicated++; @@ -222,19 +222,19 @@ namespace skyscraper8.Ietf.FLUTE } private long transferLength; - private Dictionary,FluteBlock> blocks; + private Dictionary,FluteBlock> blocks; private uint blocksDeduplicated; public class FluteBlock { - public FluteBlock(ushort sourceBlockNumer, ushort encodingSymbolId, byte[] payload) + public FluteBlock(uint sourceBlockNumer, ushort encodingSymbolId, byte[] payload) { SourceBlockNumer = sourceBlockNumer; EncodingSymbolId = encodingSymbolId; Payload = payload; } - public ushort SourceBlockNumer; + public uint SourceBlockNumer; public ushort EncodingSymbolId; public byte[] Payload; diff --git a/skyscraper8/Ietf/FLUTE/LctFrame.cs b/skyscraper8/Ietf/FLUTE/LctFrame.cs index 25adab9..b18009e 100644 --- a/skyscraper8/Ietf/FLUTE/LctFrame.cs +++ b/skyscraper8/Ietf/FLUTE/LctFrame.cs @@ -53,7 +53,7 @@ namespace skyscraper8.Ietf.FLUTE //DVB-NIP sample: "Z:\Persönliches\Satellitescommunity\Skyscraper Test Fixture\astra1_11141h_gse_nip.ts" if (!isAtsc3) { - this.FecHeader = new FecHeader(ms); + this.FecHeader = new FecHeader(ms, this.Codepoint); } else { diff --git a/skyscraper8/Ietf/FLUTE/LctHeader.cs b/skyscraper8/Ietf/FLUTE/LctHeader.cs index 39d83cb..2d7d1fa 100644 --- a/skyscraper8/Ietf/FLUTE/LctHeader.cs +++ b/skyscraper8/Ietf/FLUTE/LctHeader.cs @@ -83,6 +83,9 @@ namespace skyscraper8.Ietf.FLUTE case 64: this.FecObjectTransmissionInformation = new FecObjectTransmissionInformation(extensionBuffer); break; + case 65: + this.CdsCompletionPollRequest = new CdsCompletionPollRequestExtension(extensionBuffer); + break; case 68: this.NipActualCarrierInformation = new NipActualCarrierInformation(extensionBuffer); break; @@ -98,10 +101,14 @@ namespace skyscraper8.Ietf.FLUTE Valid = true; } + public CdsCompletionPollRequestExtension CdsCompletionPollRequest { get; set; } + private ulong ReadField(Stream stream, int bits) { switch(bits) { + case 0: + return 0; case 16: return stream.ReadUInt16BE(); case 32: diff --git a/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs b/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs new file mode 100644 index 0000000..a0fe450 --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper8.Ietf.FLUTE; + +namespace skyscraper8.ThreemediaOtt +{ + internal class ThreemediaContentFragment : IDisposable + { + public void IngestPacket(LctFrame lctFrame) + { + int totalBlocks = lctFrame.FecHeader.SourceBlockLength.Value; + if (blocks == null) + blocks = new byte[totalBlocks][]; + + int targetBlock = lctFrame.FecHeader.EncodingSymbolId; + if (targetBlock >= blocks.Length) + return; + if (blocks[targetBlock] != null) + return; + + blocks[targetBlock] = lctFrame.Payload; + } + + private byte[][] blocks; + + public void Dispose() + { + if (blocks != null) + { + for (int i = 0; i < blocks.Length; i++) + { + blocks[i] = null; + } + } + + blocks = null; + } + + public bool IsComplete() + { + if (blocks == null) + return false; + + for (int i = 0; i < blocks.Length; i++) + { + if (blocks[i] == null) + return false; + } + + return true; + } + } +} diff --git a/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs b/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs new file mode 100644 index 0000000..338a050 --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using log4net; +using skyscraper5.Ietf.Rfc768; +using skyscraper5.Ietf.Rfc971; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper8.Ietf.FLUTE; + +namespace skyscraper8.ThreemediaOtt +{ + [SkyscraperPlugin] + internal class ThreemediaOttHandler : ISkyscraperMpePlugin + { + private static readonly IPAddress THREEMDIA_DESTINATION_IP = IPAddress.Parse("224.1.2.1"); + private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + + public void ConnectToStorage(object[] connector) + { + throw new NotImplementedException(); + } + + private SkyscraperContext context; + public void SetContext(DateTime? currentTime, object skyscraperContext) + { + if (context != null) + { + context = skyscraperContext as SkyscraperContext; + } + } + + public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + if (!internetHeader.IsDestinationMulticast) + return false; + + if (!internetHeader.DestinationAddress.Equals(THREEMDIA_DESTINATION_IP)) + return false; + if (internetHeader.Protocol != 17) + return false; + + if (ipv4Packet[2] != 0x04) + return false; + if (ipv4Packet[3] != 0x61) + return false; + if (ipv4Packet[8] != 0x10) + return false; + + return true; + } + + private Dictionary sessions; + public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet) + { + UserDatagram udpPacket = new UserDatagram(ipv4Packet); + LctFrame lctFrame = new LctFrame(udpPacket.Payload, false); + if (lctFrame.CloseSessionFlag) + { + AssembleSegment(udpPacket.DestinationPort); + return; + } + else + { + IngestPacket(udpPacket.DestinationPort, lctFrame); + return; + } + } + + private void IngestPacket(ushort udpPacketDestinationPort, LctFrame lctFrame) + { + if (sessions == null) + sessions = new Dictionary(); + + ThreemediaSession selectedSession = null; + if (sessions.ContainsKey(udpPacketDestinationPort)) + { + selectedSession = sessions[udpPacketDestinationPort]; + } + else + { + selectedSession = new ThreemediaSession(); + sessions[udpPacketDestinationPort] = selectedSession; + } + + selectedSession.IngestPacket(lctFrame); + } + + private void AssembleSegment(ushort destinationPort) + { + if (sessions == null) + return; + + if (!sessions.ContainsKey(destinationPort)) + { + return; + } + + ThreemediaSession selectedSession = sessions[destinationPort]; + if (!selectedSession.IsComplete()) + { + logger.WarnFormat(String.Format("The session on Port {0} is incomplete and can not be recovered.", destinationPort)); + selectedSession.Dispose(); + return; + } + + throw new NotImplementedException(); + } + + public bool StopProcessingAfterThis() + { + return true; + } + } +} diff --git a/skyscraper8/ThreemediaOtt/ThreemediaSession.cs b/skyscraper8/ThreemediaOtt/ThreemediaSession.cs new file mode 100644 index 0000000..35c0c8f --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaSession.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper8.Ietf.FLUTE; + +namespace skyscraper8.ThreemediaOtt +{ + internal class ThreemediaSession : IDisposable + { + public void IngestPacket(LctFrame lctFrame) + { + int numFragments = lctFrame.LctHeader.CdsCompletionPollRequest.PollSequence; + if (fragments == null) + fragments = new ThreemediaContentFragment[numFragments]; + + int currentFragment = (int)lctFrame.LctHeader.TransportObjectIdentifier; + currentFragment--; + if (fragments[currentFragment] == null) + fragments[currentFragment] = new ThreemediaContentFragment(); + + fragments[currentFragment].IngestPacket(lctFrame); + } + + private ThreemediaContentFragment[] fragments; + + public bool IsComplete() + { + if (fragments == null) + return false; + + for (int i = 0; i < fragments.Length; i++) + { + if (fragments[i] == null) + return false; + + if (!fragments[i].IsComplete()) + return false; + } + + return true; + } + + public void Dispose() + { + if (fragments != null) + { + for (int i = 0; i < fragments.Length; i++) + { + fragments[i].Dispose(); + fragments[i] = null; + } + } + + fragments = null; + } + } +}