200 lines
5.5 KiB
C#
200 lines
5.5 KiB
C#
using log4net;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using skyscraper5.Ietf.Rfc768;
|
|
using skyscraper5.Ietf.Rfc971;
|
|
using skyscraper5.Skyscraper;
|
|
using skyscraper5.Skyscraper.Plugins;
|
|
using skyscraper5.Skyscraper.Scraper;
|
|
using skyscraper8.Atsc.A331.Schema;
|
|
using skyscraper8.Ietf.FLUTE;
|
|
|
|
namespace skyscraper8.Atsc.A331
|
|
{
|
|
[SkyscraperPlugin]
|
|
[PluginPriority(3)]
|
|
internal class Atsc3Receiver : ISkyscraperMpePlugin
|
|
{
|
|
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
|
|
private static readonly IPAddress LLS_IP = IPAddress.Parse("224.0.23.60");
|
|
private static readonly ushort LLS_PORT = 4937;
|
|
|
|
private IAtsc3EventHandler eventHandler;
|
|
private bool systimeDetected;
|
|
|
|
public void ConnectToStorage(object[] connector)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void SetContext(DateTime? currentTime, object skyscraperContext)
|
|
{
|
|
eventHandler = (IAtsc3EventHandler)skyscraperContext;
|
|
}
|
|
|
|
private bool atsc3detected;
|
|
public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
|
|
{
|
|
if (internetHeader.Protocol != 0x11)
|
|
return false;
|
|
|
|
if (atsc3detected && internetHeader.IsDestinationMulticast)
|
|
return true;
|
|
|
|
ushort destPort = ipv4Packet.GetUInt16BE(2);
|
|
if (nonAtsc3 != null)
|
|
{
|
|
IPEndPoint possibleEvildoer = new IPEndPoint(internetHeader.DestinationAddress, destPort);
|
|
if (nonAtsc3.Contains(possibleEvildoer))
|
|
return false;
|
|
}
|
|
|
|
if (internetHeader.DestinationAddress.Equals(LLS_IP))
|
|
{
|
|
if (destPort == LLS_PORT)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private HashSet<IPEndPoint> nonAtsc3;
|
|
private Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener> flutes;
|
|
private Atsc3FilenameTable filenames;
|
|
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
|
|
{
|
|
_stopProcessIndicator = false;
|
|
UserDatagram udpPacket = new UserDatagram(ipv4Packet);
|
|
IPEndPoint destination = new IPEndPoint(internetHeader.DestinationAddress, udpPacket.DestinationPort);
|
|
if (nonAtsc3 != null)
|
|
{
|
|
if (nonAtsc3.Contains(destination))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (internetHeader.DestinationAddress.Equals(LLS_IP) && udpPacket.DestinationPort == LLS_PORT)
|
|
{
|
|
//Handle LLS
|
|
LlsTable lls = new LlsTable(udpPacket.Payload);
|
|
HandleLls(lls);
|
|
return;
|
|
}
|
|
|
|
if (!atsc3detected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LctFrame lctFrame = new LctFrame(udpPacket.Payload, true);
|
|
if (lctFrame.Version != 1)
|
|
{
|
|
//Doesn't look like FLUTE, go away.
|
|
if (nonAtsc3 == null)
|
|
nonAtsc3 = new HashSet<IPEndPoint>();
|
|
nonAtsc3.Add(destination);
|
|
return;
|
|
}
|
|
|
|
ulong tsi = lctFrame.LctHeader.TransportSessionIdentifier;
|
|
ulong toi = lctFrame.LctHeader.TransportObjectIdentifier;
|
|
Tuple<IPAddress, ushort, ulong, ulong> fluteCoordinate = new Tuple<IPAddress, ushort, ulong, ulong>(internetHeader.DestinationAddress, udpPacket.DestinationPort, tsi, toi);
|
|
FluteListener targetListener;
|
|
|
|
if (flutes == null)
|
|
{
|
|
flutes = new Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener>();
|
|
filenames = new Atsc3FilenameTable();
|
|
}
|
|
|
|
if (flutes.ContainsKey(fluteCoordinate))
|
|
{
|
|
targetListener = flutes[fluteCoordinate];
|
|
}
|
|
else
|
|
{
|
|
FluteListener newListener = new FluteListener(internetHeader.DestinationAddress, udpPacket.DestinationPort, tsi, toi);
|
|
flutes.Add(fluteCoordinate, newListener);
|
|
targetListener = newListener;
|
|
}
|
|
|
|
targetListener.PushPacket(lctFrame);
|
|
_stopProcessIndicator = true;
|
|
if (targetListener.IsComplete())
|
|
{
|
|
GuessedFluteDataType guessedFluteDataType = FluteUtilities.GuessDataType(targetListener);
|
|
ProcessMetafile(guessedFluteDataType, targetListener, destination);
|
|
}
|
|
}
|
|
|
|
private bool ProcessMetafile(GuessedFluteDataType guessedFluteDataType, FluteListener targetListener, IPEndPoint destination)
|
|
{
|
|
switch (guessedFluteDataType)
|
|
{
|
|
case GuessedFluteDataType.Xml:
|
|
string contentsAsString = targetListener.GetContentsAsString();
|
|
if (contentsAsString.Contains("<FDT-Instance "))
|
|
{
|
|
Atsc3Fdt fdt = Atsc3Utilities.ParseFdt(contentsAsString);
|
|
filenames.LearnFilename(destination, fdt);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
default:
|
|
throw new NotImplementedException(guessedFluteDataType.ToString());
|
|
}
|
|
}
|
|
|
|
private void HandleLls(LlsTable lls)
|
|
{
|
|
if (!lls.Valid)
|
|
return;
|
|
|
|
_stopProcessIndicator = true;
|
|
|
|
switch (lls.LlsTableId)
|
|
{
|
|
case 0x01:
|
|
//SLT
|
|
if (!atsc3detected)
|
|
{
|
|
atsc3detected = true;
|
|
eventHandler.OnAtsc3Detected();
|
|
}
|
|
sltType slt = Atsc3Utilities.UnpackSlt(lls.Payload);
|
|
eventHandler.OnAtsc3ServiceList(slt);
|
|
break;
|
|
case 0x03:
|
|
SysTimeType systime = Atsc3Utilities.UnpackSystime(lls.Payload);
|
|
if (systime.ptpPrepend != 0 && !systimeDetected)
|
|
{
|
|
logger.WarnFormat("Found valid ATSC A/331 SYSTIME, however it is not implemented yet. ");
|
|
systimeDetected = true;
|
|
}
|
|
break;
|
|
case 0xff:
|
|
//User defined, can be removed.
|
|
break;
|
|
default:
|
|
logger.InfoFormat(
|
|
"Encountered unknown ATSC A/331 LLS Table: 0x{0:X2} This means that this stream uses a version of ATSC 3 than skyscraper8 knows about. It would be great if you could share a recording of this stream so this can be implemented.", lls.LlsTableId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private bool _stopProcessIndicator;
|
|
public bool StopProcessingAfterThis()
|
|
{
|
|
return _stopProcessIndicator;
|
|
}
|
|
}
|
|
}
|