skyscraper8/skyscraper8/DvbNip/DvbNipReceiver.cs
2025-08-06 22:05:22 +02:00

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;
}
}
}