339 lines
12 KiB
C#
339 lines
12 KiB
C#
using log4net;
|
|
using skyscraper5.Ietf.Rfc768;
|
|
using skyscraper5.Ietf.Rfc971;
|
|
using skyscraper5.Skyscraper.IO;
|
|
using skyscraper5.Skyscraper.Plugins;
|
|
using skyscraper8.Ietf.FLUTE;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using moe.yo3explorer.skyscraper8.DVBI.Model;
|
|
using skyscraper8.DvbI;
|
|
|
|
namespace skyscraper8.DvbNip
|
|
{
|
|
[SkyscraperPlugin]
|
|
internal class DvbNipReceiver : ISkyscraperMpePlugin, DvbNipServiceListNotifier
|
|
{
|
|
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
|
|
|
|
public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
|
|
{
|
|
if (!internetHeader.IsDestinationMulticast)
|
|
return false;
|
|
|
|
if (internetHeader.Protocol != 17)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void ConnectToStorage(object[] connector)
|
|
{
|
|
}
|
|
|
|
private bool bootstrapped;
|
|
private uint fluteHits, fluteMisses;
|
|
|
|
private Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener> flutes;
|
|
private static IPAddress dvbServiceDiscovery = IPAddress.Parse("224.0.23.14");
|
|
|
|
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
|
|
{
|
|
if (!bootstrapped)
|
|
{
|
|
bool isDvbServiceDiscovery = internetHeader.DestinationAddress.Equals(dvbServiceDiscovery);
|
|
if (isDvbServiceDiscovery)
|
|
{
|
|
UserDatagram discoveryUdpPacket = new UserDatagram(ipv4Packet);
|
|
if (discoveryUdpPacket.DestinationPort == 3937)
|
|
{
|
|
LctFrame discoveryLctFrame = new LctFrame(discoveryUdpPacket.Payload);
|
|
if (discoveryLctFrame.LctHeader.NipActualCarrierInformation != null)
|
|
{
|
|
CurrentCarrierInformation = discoveryLctFrame.LctHeader.NipActualCarrierInformation;
|
|
EventHandler.OnNipCarrierDetected(CurrentCarrierInformation);
|
|
bootstrapped = true;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
UserDatagram udpPacket = new UserDatagram(ipv4Packet);
|
|
LctFrame lctFrame = new LctFrame(udpPacket.Payload);
|
|
if (lctFrame.LctHeader.TimeExtenstion != null)
|
|
{
|
|
this.currentTime = lctFrame.LctHeader.TimeExtenstion.Sct;
|
|
}
|
|
|
|
if (flutes == null)
|
|
flutes = new Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener>();
|
|
|
|
Tuple<IPAddress, ushort, ulong, ulong> fluteCoordinate = new Tuple<IPAddress, ushort, ulong, ulong>(internetHeader.DestinationAddress, udpPacket.DestinationPort, lctFrame.LctHeader.TransportSessionIdentifier, lctFrame.LctHeader.TransportObjectIdentifier);
|
|
FluteListener fluteListener;
|
|
if (flutes.ContainsKey(fluteCoordinate))
|
|
{
|
|
fluteListener = flutes[fluteCoordinate];
|
|
fluteHits++;
|
|
}
|
|
else
|
|
{
|
|
fluteListener = new FluteListener(internetHeader.DestinationAddress, udpPacket.DestinationPort, lctFrame.LctHeader.TransportSessionIdentifier, lctFrame.LctHeader.TransportObjectIdentifier);
|
|
flutes.Add(fluteCoordinate, fluteListener);
|
|
fluteMisses++;
|
|
TryPruneCache();
|
|
}
|
|
fluteListener.PushPacket(lctFrame);
|
|
if (fluteListener.IsComplete())
|
|
{
|
|
if (fluteListener.DestinationToi == 0)
|
|
{
|
|
if (fluteListener.CarrierInformation != null)
|
|
{
|
|
CurrentCarrierInformation = fluteListener.CarrierInformation;
|
|
return;
|
|
}
|
|
|
|
Stream fluteStream = fluteListener.ToStream();
|
|
bool isValidXml = FluteUtilities.IsXmlWellFormed(fluteStream);
|
|
fluteStream.Position = 0;
|
|
if (isValidXml)
|
|
{
|
|
FDTInstanceType fdtAnnouncement = FluteUtilities.UnpackFluteFdt(fluteStream);
|
|
if (fdtAnnouncement != null)
|
|
{
|
|
SetFileAssociations(fluteListener, fdtAnnouncement);
|
|
}
|
|
}
|
|
fluteStream.Close();
|
|
fluteStream.Dispose();
|
|
flutes.Remove(fluteCoordinate);
|
|
return;
|
|
}
|
|
if (fluteListener.FileAssociation == null)
|
|
{
|
|
return;
|
|
}
|
|
if (fluteListener.FileAssociation.ContentLocation.StartsWith("urn:dvb:"))
|
|
{
|
|
HandleMetadata(fluteListener);
|
|
fluteListener.Disabled = true;
|
|
return;
|
|
}
|
|
|
|
if (CurrentCarrierInformation != null)
|
|
{
|
|
if (serviceListUrls != null)
|
|
{
|
|
if (serviceListUrls.ContainsKey(fluteListener.FileAssociation.ContentLocation))
|
|
{
|
|
string serviceListId = serviceListUrls[fluteListener.FileAssociation.ContentLocation];
|
|
Stream serviceListStream = fluteListener.ToStream();
|
|
byte[] serviceListByteArray = new byte[serviceListStream.Length];
|
|
serviceListStream.Read(serviceListByteArray, 0, (int)serviceListStream.Length);
|
|
ServiceListType serviceList = DvbIUtils.UnpackServiceList(serviceListByteArray);
|
|
EventHandler.OnServiceList(CurrentCarrierInformation, serviceListId, serviceList);
|
|
}
|
|
}
|
|
EventHandler?.FluteFileArrival(CurrentCarrierInformation, fluteListener);
|
|
fluteListener.Disabled = true;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (fluteListener.DestinationToi != 0)
|
|
{
|
|
double progress = fluteListener.DownloadProgress;
|
|
if (progress > fluteListener.LastReportedProgress)
|
|
{
|
|
EventHandler.FluteFileDownloadProgress(CurrentCarrierInformation, fluteListener.DestinationTsi, fluteListener.DestinationToi, progress, fluteListener.FileAssociation);
|
|
fluteListener.LastReportedProgress = progress;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private DateTime? currentTime;
|
|
private bool HandleMetadata(FluteListener fluteListener)
|
|
{
|
|
switch (fluteListener.FileAssociation.ContentLocation)
|
|
{
|
|
case "urn:dvb:metadata:cs:NativeIPMulticastTransportObjectTypeCS:2023:bootstrap":
|
|
MulticastGatewayConfigurationType multicastGatewayConfiguration2023 = DvbNipUtilities.UnpackMulticastGatewayConfiguration(fluteListener.ToStream());
|
|
EventHandler?.OnMulticastGatewayConfiguration(CurrentCarrierInformation, multicastGatewayConfiguration2023);
|
|
return true;
|
|
case "urn:dvb:metadata:cs:MulticastTransportObjectTypeCS:2021:gateway-configuration":
|
|
MulticastGatewayConfigurationType multicastGatewayConfiguration2021 = DvbNipUtilities.UnpackMulticastGatewayConfiguration(fluteListener.ToStream());
|
|
EventHandler?.OnMulticastGatewayConfiguration(CurrentCarrierInformation, multicastGatewayConfiguration2021);
|
|
return true;
|
|
case "urn:dvb:metadata:nativeip:PrivateDataSignalling":
|
|
PrivateDataSignallingManifestType privateDataSignallingManifest = DvbNipUtilities.UnpackPrivateDataSignallingManifest(fluteListener.ToStream());
|
|
EventHandler?.OnPrivateDataSignallingManifest(CurrentCarrierInformation, privateDataSignallingManifest);
|
|
return true;
|
|
case "urn:dvb:metadata:nativeip:dvb-i-slep":
|
|
if (currentTime.HasValue)
|
|
{
|
|
Stream rawSlepStream = fluteListener.ToStream();
|
|
byte[] rawSlepBytes = new byte[rawSlepStream.Length];
|
|
rawSlepStream.Read(rawSlepBytes, 0, (int)rawSlepStream.Length);
|
|
string rawSlepString = Encoding.UTF8.GetString(rawSlepBytes);
|
|
rawSlepString = rawSlepString.Replace("<dvbi-types:", "<");
|
|
rawSlepString = rawSlepString.Replace("</dvbi-types:", "</");
|
|
rawSlepString = rawSlepString.Replace("<dvbisdt:", "<");
|
|
rawSlepString = rawSlepString.Replace("</dvbisdt:", "</");
|
|
File.WriteAllText("slep3.xml", rawSlepString);
|
|
ServiceListEntryPoints serviceListEntryPoints = DvbIUtils.UnpackServiceListEntryPoints(rawSlepString);
|
|
EventHandler?.OnServiceListEntryPoints(CurrentCarrierInformation, serviceListEntryPoints, currentTime.Value, this);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case "urn:dvb:metadata:nativeip:TimeOffsetFile":
|
|
TimeOffsetFileType timeOffsetFile = DvbNipUtilities.UnpackTimeOffsetFile(fluteListener.ToStream());
|
|
EventHandler?.OnTimeOffsetFile(CurrentCarrierInformation, timeOffsetFile);
|
|
return true;
|
|
case "urn:dvb:metadata:nativeip:NetworkInformationFile":
|
|
NetworkInformationFileType networkInformationFile = DvbNipUtilities.UnpackNetworkInformationFile(fluteListener.ToStream());
|
|
EventHandler?.OnNetworkInformationFile(CurrentCarrierInformation, networkInformationFile);
|
|
return true;
|
|
case "urn:dvb:metadata:nativeip:ServiceInformationFile":
|
|
ServiceInformationFileType serviceInformationFile = DvbNipUtilities.UnpackServiceInformationFile(fluteListener.ToStream());
|
|
EventHandler?.OnServiceInformationFile(CurrentCarrierInformation, serviceInformationFile);
|
|
return true;
|
|
case "urn:dvb:metadata:nativeip:ServiceGuide":
|
|
//Unfortunately, the NIPServiceGuideManifest does not contain any useful information at all, which is why we ignore it.
|
|
//There doesn't seem to be a way to get the actual EIT XML from it.
|
|
return true;
|
|
default:
|
|
throw new NotImplementedException(fluteListener.FileAssociation.ContentLocation);
|
|
}
|
|
}
|
|
|
|
private void TryPruneCache()
|
|
{
|
|
DateTime now = DateTime.Now;
|
|
List<KeyValuePair<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener>> staleListeners = flutes.Where(x => x.Value.LastTouched != DateTime.MinValue)
|
|
.Where(x => x.Value.FileAssociation != null)
|
|
.Where(x => (now - x.Value.LastTouched).TotalMinutes >= 1.0)
|
|
.Where(x => DvbNipUtilities.IsContinuousFileType(x.Value.FileAssociation))
|
|
.ToList();
|
|
if (staleListeners.Count > 0)
|
|
{
|
|
int prevItems = flutes.Count;
|
|
foreach (KeyValuePair<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener> staleListener in
|
|
staleListeners)
|
|
{
|
|
staleListener.Value.Disabled = true;
|
|
flutes.Remove(staleListener.Key);
|
|
}
|
|
int nowItems = flutes.Count;
|
|
if (staleListeners.Count > 10)
|
|
{
|
|
logger.DebugFormat(String.Format(
|
|
"Removed {0} stale segments from FLUTE cache. Cache slimmed from {1} to {2} items.",
|
|
staleListeners.Count, prevItems, nowItems));
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool SetFileAssociations(FluteListener sourceListener, FDTInstanceType fdtAnnouncement)
|
|
{
|
|
bool result = false;
|
|
foreach(FileType announcedFile in fdtAnnouncement.File)
|
|
{
|
|
if (string.IsNullOrEmpty(announcedFile.TOI))
|
|
continue;
|
|
|
|
bool needed = EventHandler.IsFileNeeded(announcedFile.ContentLocation);
|
|
if (serviceListUrls != null)
|
|
{
|
|
if (serviceListUrls.ContainsKey(announcedFile.ContentLocation))
|
|
{
|
|
needed = true;
|
|
}
|
|
}
|
|
ulong targetToi = ulong.Parse(announcedFile.TOI);
|
|
Tuple<IPAddress, ushort, ulong, ulong> fluteCoordinate = new Tuple<IPAddress, ushort, ulong, ulong>(sourceListener.DestinationAddress, sourceListener.DestinationPort, sourceListener.DestinationTsi, targetToi);
|
|
if (flutes.ContainsKey(fluteCoordinate))
|
|
{
|
|
FluteListener listener = flutes[fluteCoordinate];
|
|
if (listener.FileAssociation == null)
|
|
{
|
|
listener.FileAssociation = announcedFile;
|
|
result = true;
|
|
}
|
|
|
|
if (!needed)
|
|
listener.Disabled = true;
|
|
}
|
|
else
|
|
{
|
|
FluteListener listener = new FluteListener(sourceListener.DestinationAddress, sourceListener.DestinationPort, sourceListener.DestinationTsi, targetToi);
|
|
listener.FileAssociation = announcedFile;
|
|
flutes.Add(fluteCoordinate, listener);
|
|
result = true;
|
|
if (!needed)
|
|
listener.Disabled = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void SetContext(DateTime? currentTime, object skyscraperContext)
|
|
{
|
|
if (EventHandler == null)
|
|
{
|
|
IDvbNipEventHandler? dvbNipEventHandler = skyscraperContext as IDvbNipEventHandler;
|
|
if (dvbNipEventHandler != null)
|
|
EventHandler = dvbNipEventHandler;
|
|
}
|
|
}
|
|
|
|
public bool StopProcessingAfterThis()
|
|
{
|
|
return bootstrapped;
|
|
}
|
|
|
|
public NipActualCarrierInformation CurrentCarrierInformation { get; private set; }
|
|
public IDvbNipEventHandler EventHandler { get; private set; }
|
|
|
|
private Dictionary<string,string> serviceListUrls;
|
|
public void AddServiceListUrl(string url, string id)
|
|
{
|
|
if (serviceListUrls == null)
|
|
serviceListUrls = new Dictionary<string, string>();
|
|
if (string.IsNullOrEmpty(url))
|
|
return;
|
|
|
|
if (!serviceListUrls.ContainsKey(url))
|
|
{
|
|
serviceListUrls.Add(url, id);
|
|
|
|
KeyValuePair<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener> keyValuePair = flutes
|
|
.Where(x => x.Value.FileAssociation != null)
|
|
.Where(x => x.Value.FileAssociation.ContentLocation.Equals(url))
|
|
.FirstOrDefault();
|
|
if (keyValuePair.Key != null)
|
|
{
|
|
flutes.Remove(keyValuePair.Key);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private long sourceHash;
|
|
public void SetSourceHash(long sourceHash)
|
|
{
|
|
this.sourceHash = sourceHash;
|
|
}
|
|
}
|
|
}
|